[[PageOutline]] = Metamethods = Often you will want to create your own objects which will behave like distinct types. MiniD allows you to customize the behavior of objects through what are called metamethods. Lua users will find this term familiar, and MiniD's metamethods are similar in purpose to those in Lua. However, there is no concept of a metatable in MiniD as in Lua. Metamethods, broadly, are a set of methods that are called on objects when certain "special" operations are performed. This covers not only the familiar case of operator overloading, but also other operations such as foreach loop iteration, conversion to a string, and interception of method calls. == Types Which Can Have Metamethods == All types in MiniD can have metamethods. However, only a few can have metamethods defined for them ''from within MiniD code''. For example, `char`, `string`, and `array` have some metamethods defined for them by the standard libraries, but you can't add metamethods of your own to these types. The two main types of objects which you can customize from within MiniD are `table`s and `instance`s. Note that `table`s have some metamethods defined for them by the standard libraries; these methods are available to all table objects. But you can also define metamethods for individual instances of tables. == Table Metamethods == To define a metamethod for a table, simply put a function in the table. We'll use `toString` as an example; this metamethod is called when the object needs to be converted to a string. By default, when you print out the string representation of a table, you get something that's not very useful to humans. {{{ #!minid local t = { x = 5, y = 10 } writeln(t) // prints something like "table 0x00AA0B20" }}} That is, it just prints out the word "table" followed by its memory address. So you want to make this print out a little nicer. We can define a `toString` metamethod to do just that. We can do this in one of two ways: define the method in the table, or add it as a member after the fact. We'll define this one in the table. {{{ #!minid local t = { x = 5, y = 10, function toString() = format("Table x = ", :x, " y = ", :y) } writeln(t) }}} Now the table prints out as "Table x = 5 y = 10". Much better. Keep in mind that when a metamethod is called, the object on which it's being called is passed as the `this` parameter to the metamethod (see [wiki:LanguageSpec2/Functions Functions] for info on the `this` parameter). We then take advantage of that to be able to access the called table's `x` and `y` fields from within toString(). This table is the only one which has this `toString` metamethod. If you wanted many tables to have this method, you could write a function to add it to the table. {{{ #!minid function addToString(t) t.toString = function() = format("Table x = ", :x, " y = ", :y) local t = { x = 5, y = 10 } addToString(t) writeln(t) }}} == Instance Metamethods == The other main type of object which you can give metamethods is the `instance` type. You do this by defining the metamethods in a class declaration, or by adding them after the fact. This is very similar to how it's done with tables. {{{ #!minid class C { x = 5 y = 10 function toString() = format("C x = ", :x, " y = ", :y) } local c = C() writeln(c) }}} Now every instance of C will have the `toString` metamethod defined for it. == A Note on the "Assign" Metamethods == There are a number of metamethod pairs which have a "getter" version and a "setter" version, where the "setter" is denoted by ending with "Assign". For these metamethods, if the target object is the left-hand-side of an operation assignment, such as `a[0] += 5`, the following will happen: 1. `a.opIndex(0)` is called to retrieve the value which is put into a temporary variable 2. The operation assignment is performed on the temporary 3. `a.opIndexAssign(0, temp)` is called to assign the new value back into `a` Note how both `opIndex` and `opIndexAssign` are called as part of the same operation. This happens with all pairs of metamethods that look like this. == The Metamethods == This is a list of all the metamethods which can be defined, what they do, and some examples of their use. == '''`string toString()`''' == We've already met this method in the examples. This metamethod is called when an object needs to be turned into a string, such as when it's being written out with a formatted writing function (`writefln` and its kind), or when the `toString` baselib function is called on it. `toString` takes no parameters and is expected to return a single string. The examples above show how to define and use `toString`. == '''`value opIndex(index)`''' == The `opIndex` metamethod is called when an object is indexed; that is, when you use the square brackets (`a[5]`). `opIndex` is passed the index, and is expected to return some value. Any value is fine, including null. The convention in MiniD is to throw an exception when given an invalid index, but you can do whatever you want. Tables are sort of special in two ways. For tables (and '''only''' tables), field access (like `t.x`) is functionally equivalent to indexing (like `t["x"]`). For all other types, field access is a separate operation and is overloaded with `opField` and `opFieldAssign` instead. The other way a table is special is what happens when the table is indexed. When it is indexed, the key is looked up. If the key is found, that's the end of it. But if the key isn't found, it will try to call any `opIndex` metamethods defined in the table; that is, tables only have their `opIndex` metamethods called when a certain key doesn't exist in them. Instances will always have their `opIndex` called when an index expression is used, as they have no built-in definition for the operation. An example on how to use it is given in the next section, on its sibling method `opIndexAssign`. == '''`void opIndexAssign(index, value)`''' == This metamethod accompanies `opIndex` and is called when an object is indexed on the left side of an assignment expression. It is given two parameters: the index and the value to be assigned to this container. This metamethod is not expected to return anything, since nothing can be assigned the value of an assignment in MiniD anyway. Again, tables have special behavior with this metamethod. This is only called when assigning a key-value pair into a table which doesn't already exist in the table. Here is an example class showing how to use `opIndex` and `opIndexAssign` to emulate some container functionality. {{{ #!minid class Array { mData this(size) :mData = array.new(size) function opIndex(index) { if(index < 0 || index >= #:mData) throw format("Invalid Array index: ", index) return :mData[index] } function opIndexAssign(index, value) { if(index < 0 || index >= #:mData) throw format("Invalid Array index: ", index) :mData[index] = value; } } local a = Array(10) // These statements call opIndexAssign a[0] = 5 a[1] = 10 // These statements call opIndex writefln(a[0]) writefln(a[1]) // Try out a bad index try a[-5] = 10 catch(e) writefln("CAUGHT: ", e) }}} Here are a few examples of `opIndex` and `opIndexAssign` used with tables. {{{ #!minid // This is a Fibonacci table which memoizes its values when it's accessed. local fib = { function opIndex(index) { if(index == 0 || index == 1) this[index] = 1 else this[index] = this[index - 1] + this[index - 2] return this[index] } } // Prints out the first 10 Fibonacci numbers in reverse. // This is significant because the numbers are only calculated on fib[10]; // every subsequent access just gets the precalculated number in the table. for(i: 10 .. 0) writeln(fib[i]) local troll = { function opIndexAssign(index, value) { if(isInt(index)) writeln("I don't like int indices.") else // We use hash.set, or else we'd get infinite recursion trying // to add the new key-value pair to this. hash.set(this, index, value); } }; troll[3] = 5 // prints "I don't like int indices." troll["hi"] = 8 writefln(troll[3]) // prints null; 3 was never added writefln(troll["hi"]) // prints 8 }}} == '''`value opSlice(lo, hi)`''' == This metamethod is called when an object is sliced using the `a[x .. y]` syntax. It is expected to take two values, the low and high indices of the slice, and to return one value. The low and high indices can be of any type, but `null` has a special meaning. When you omit indices in slice expressions (such as `a[..]` or `a[]`), they are passed as `null`. So a `null` slice index should be interpreted as "to the end" (if that even makes sense for your object). The returned value can be anything. An example of use is shown in the next section, on `opSliceAssign`. == '''`void opSliceAssign(lo, hi, value)`''' == This metamethod is called when an object is sliced on the left-hand side of an assignment (such as `a[x .. y] = z`). This is the sibling function of `opSlice`. It takes three parameters: the low index, the high index, and the value to be assigned. Notice that the order of the parameters are flipped from D -- indices first, and then the value. This metamethod is not expected to return any value. The indices are like in `opSlice`, where `null` indicates an omitted index. Here is a class which implements some slicing functionality. Notice that the keys are not even integers; slicing doesn't have to be done just on built-in arrays. {{{ #!minid class FloatArray { mData mMin = null mMax = null this() :mData = {} function opIndex(index) = :mData[index] function opIndexAssign(index, value) { if(:mMin is null || index < :mMin) :mMin = index if(:mMax is null || index > :mMax) :mMax = index :mData[index] = value } function opSlice(lo = :mMin, hi = :mMax + 1) { local ret = FloatArray() if(#:mData == 0) return ret foreach(k, v; :mData) if(k >= lo && k < hi) ret[k] = v return ret } function opSliceAssign(lo = :mMin, hi = :mMax + 1, value) { if(#:mData == 0) return foreach(k, v; :mData) if(k >= lo && k < hi) :mData[k] = value } function toString() { local s = StringBuffer("FloatArray [") foreach(v; :mData.keys().sort()) s.append("(", v, ": ", :mData[v], ") ") s ~= "]" return s.toString() } } local a = FloatArray() a[0.0] = 7 a[1.4] = 3 a[3.5] = 8 a[5.1] = 2 a[7.9] = 6 a[10.3] = 9 writefln(a) writefln(a[2.5 .. 10.0]) a[3.3 .. 9.1] = 5 writefln(a) }}} == '''`value opField(name: string)`''' == This is a metamethod which is exclusive to instances. Tables, as mentioned before, see field access as the same as indexing, and cannot overload it separately. All other types, however, see field access as separate from indexing, and thus you can overload the two separately for instances. When `opField` is called is very similar to how `opIndex` is called on tables. When you access a field out of an instance like `o.x`, it looks for a field named "x" in the instance, and if it exists anywhere in the instance or its base class chain, it returns the value of that field. If it doesn't exist, then `opField` is attempted. `opField` takes the name of the field to retrieve as a string and is expected to return any value. An example of use is in the section after the next. == '''`void opFieldAssign(name: string, value)`''' == Just like indexing and slicing, field access has an assignment version as well. `opFieldAssign` is called on an instance when you assign a field into an instance and that field doesn't already exist. It takes the name of the field as a string, followed by the value that is being assigned into the field. An example showing how to use `opField` and `opFieldAssign`, as well as `opMethod` is given in the following section. == '''`anything opMethod(name: string, vararg)`''' == This method is just cool. When you attempt to call a method on an object and that method either doesn't exist or isn't a function, you'll get an error. However, sometimes you'd like to intercept this behavior, and that's what `opMethod` is for. If an instance defines `opMethod` and you attempt to call a method which doesn't exist, `opMethod` will be called instead with the method's name as the first parameter and any other parameters following it. `opMethod` is allowed to return any number of values. In this way `opMethod` acts as a perfect replacement for the method that was supposed to have been called. Using the `opField`, `opFieldAssign`, and `opMethod` metamethods, we can now make a ''mock class'', which is a class which pretends like it's another class. However since we are intercepting all the field accesses and method calls, we can do interesting things, like perform logging or convert method calls and field accesses into more interesting operations such as database accesses or remote procedure calls. The mock class we create here is just a simple one which prints some debugging info upon each field access and method call. {{{ #!minid class Mock { mObj this(obj: instance) :mObj = obj function opField(name: string) { writefln("Getting field {}", name) return :mObj.(name) } function opFieldAssign(name: string, value) { writefln("Setting field {} to {}", name, value) :mObj.(name) = value } function opMethod(name: string, vararg) { writefln("Calling method {}", name) return :mObj.(name)(vararg) } } class Test { x = 5 y = 10 function foo(a, b) { writefln("a = {}, b = {}", a, b) return :x, :y } } function testObject(o) { writefln("x = {}, y = {}", o.x, o.y) o.x = 12 local x, y = o.foo(3, 4) writefln("x = {}, y = {}", x, y) } testObject(Test()) writeln() testObject(Mock(Test())) }}} This outputs: {{{ x = 5, y = 10 a = 3, b = 4 x = 12, y = 10 Getting field x Getting field y x = 5, y = 10 Setting field x to 12 Calling method foo a = 3, b = 4 x = 12, y = 10 }}} == '''`value opCat(other), value opCat_r(other)`''' == These metamethods are invoked when you use the concatenation operator, "`~`". There are actually two metamethods for this operation. If you do something like "`a ~ b`" where `a` has an `opCat` metamethod, it will call `a.opCat(b)`; however, if `a` has no `opCat` metamethod, it will then attempt `b.opCat_r(a)`. This allows you to preserve order when overloading concatenation, as well as allowing you to overload situations such as `"hello" ~ x`, where the first operand is a built-in type. Both methods are expected to return one value, which is the concatenation of `this` with the value that was passed. Here is a small example. {{{ #!minid class MyString { mData this(data: string) :mData = data function opCat(s: string | MyString) { if(s as MyString) return MyString(:mData ~ s.mData) else return MyString(:mData ~ s) } function opCat_r(s: string) = MyString(s ~ :mData) function toString() = format("<{}>", :mData) } local s1 = MyString("hello") local s2 = s1 ~ " world" // MyString ~ string local s3 = "I said: " ~ s2 // string ~ MyString local s4 = MyString("After that, ") ~ s3 // MyString ~ MyString writeln(s1) // prints "" writeln(s2) // prints "" writeln(s3) // prints "" writeln(s4) // prints "" }}} Notice how the `opCat` overload takes either a string or a MyString, but the `opCat_r` overload only takes a string and no MyString. This is because if the left-hand side of a concatenation is a MyString, it'll have `opCat` called on it and `opCat_r` won't even be attempted. == '''`void opCatAssign(vararg)`''' == This is the reflexive version of `opCat`, called when you use the "`~=`" operator on an object. This method takes a variadic number of arguments instead of just one. An expression like "`a ~= b ~ c ~ d`" is turned into "`a.opCatAssign(b, c, d)`". This only happens when concatenation is on the right-hand side; other expressions will cause only one argument to be passed, so something like "`a ~= b + c + d`" becomes "`a.opCatAssign(b + c + d)`". There is one exception to this rule; if you concatenate two string literals or characters, the compiler will always join them together, as the following example shows. Another small example. {{{ #!minid class Set { mData this() :mData = {} function opCatAssign(vararg) { for(i: 0 .. #vararg) :mData[vararg[i]] = true } function contains(value) = :mData[value] !is null } local s = Set() // Add members to the set with opCatAssign. // "hi" and "bye" are concatenated by the compiler into "hibye", so this only adds // two items. s ~= 3 ~ "hi" ~ "bye" writeln(s.contains(3)) // prints true writeln(s.contains(-4.5)) // prints false writeln(s.contains("hibye")) // prints true }}} == '''`bool opIn(index)`''' == You can use the "`in`" operator in MiniD to see if a value is in a container (and the "`!in`" operator to see if it isn't). This is defined for strings, tables, arrays, and namespaces, but you can define it for your own types by defining an `opIn` metamethod. `opIn` is passed one value, which is the value on the left side of the "`in`" operator. `opIn` is called on the value on the right side of the "`in`" operator. `opIn` should return a bool; `true` if the value is in the object, and `false` otherwise. Actually `opIn` can return any value, but they will all be converted to `bool`s (using the MiniD definition of truth and fallacy -- i.e. 0, 0.0, null are false..). "`!in`" calls this metamethod, but it just inverts the truth value of the result. Extending the little Set class we made in the `opCatAssign` example, we can make the code a bit nicer-looking. All we have to do is change the name of `contains` to `opIn`. {{{ #!minid class Set { mData this() :mData = {} function opCatAssign(vararg) { for(i: 0 .. #vararg) :mData[vararg[i]] = true } function opIn(value) = :mData[value] !is null } local s = Set() // Add members to the set with opCatAssign. // "hi" and "bye" are concatenated by the compiler into "hibye", so this only adds // two items. s ~= 3 ~ "hi" ~ "bye" writeln(3 in s) // prints true writeln(-4.5 in s) // prints false writeln(-4.5 !in s); // prints true, since s.opIn(-4.5) returns false }}} == '''`int opCmp(other)`''' == This metamethod allows you to compare objects to one another. This is called whenever you use a relational (<, <=, >, >=) operator. It is passed an object to compare to, and should return an integer describing the comparison. If `this` is less than `other`, it should return a negative integer; if `this` is greater than `other`, it should return a positive integer; and if `this` is equal to `other`, it should return 0. By default, comparing some types such as tables and instances makes no sense. You can give meaning to these comparisons by defining an `opCmp` metamethod. When overloading `opCmp`, an operator that very often comes in handy is the three-way comparison operator (<=>). This operator takes two operands, compares them, and returns a comparison integer that is exactly what opCmp should return. The following example uses it. {{{ #!minid class Value { mVal this(val) :mVal = val function opCmp(other) { if(other as Value) return :mVal <=> other.mVal else return :mVal <=> other } } local v1 = Value(5) local v2 = Value(10) if(v1 < v2) writeln("less") if(v1 >= 3) writeln("greater or equal") }}} Notice that this `opCmp` implementation sees if the other value is an instance of Value so it can compare itself against instances of the same type. == '''`bool opEquals(other)`''' == This metamethod is called when you use the equality or inequality operators (== or !=). Equality is separated from comparison as for some types, ordering makes no sense while equality does (or vice versa); and often equality can be determined more efficiently than ordering. This method should return `true` if `this` is equal to `other`, and `false` otherwise. Let's just slightly modify the Value class: {{{ #!minid class Value { mVal this(val) :mVal = val function opCmp(other) { if(other as Value) return :mVal <=> other.mVal else return :mVal <=> other } function opEquals(other) { if(other as Value) return :mVal == other.mVal else return :mVal == other } } local v1 = Value(5) local v2 = Value(10) if(v1 < v2) writeln("less") if(v1 >= 3) writeln("greater or equal") if(v1 == 5) writeln("equal") if(v1 != v2) writeln("not equal") }}} == '''`function, value, value opApply([value])`''' == `opApply` is the metamethod you define if you want to be able to use your object as the container expression of a `foreach` loop. `opApply` takes an optional parameter. What this parameter does is completely up to you. When you write a foreach loop like: {{{ #!minid foreach(k, v; object, value) // ... }}} `opApply` is called on `object`, and it is passed `value` as the parameter. In this way you can define many modes of iteration for an object by passing different values into `opApply`. If no value is given, it defaults to `null`. `opApply` then should return three values in this order: the actual iterator function, the object to be iterated over (usually `this` but it can be other things), and then the first index to be passed into your iterator function. The returned iterator function will be called once per iteration of the `foreach` loop, in order to keep getting new values. The iterator function should have a signature like "`newIndex, newValue iterator(oldIndex)`". It seems complex, but it's really not that bad. Basically, the iterator is called repeatedly, using the container as the context and the previous index of iteration, and it has to return a new index and a new value. If it returns `null` as the index, the loop stops. For a somewhat more exhaustive description of how `foreach` loops work, see the section on it [wiki:LanguageSpec2/Statements#ForeachStatements here]. Here are a few examples of `opApply`. {{{ #!minid // Iterating over arrays is easy. class Array { mData this(length) :mData = array.new(length) function opIndex(index) = :mData[index] function opIndexAssign(index, value) :mData[index] = value function opApply(method) { // We'll use the method parameter to see if we should go forward or in reverse. // Remember that strings that have the same contents are the same object, so 'is' is // sufficient to check for it. if(method is "reverse") { function iterator(index) { --index; if(index < 0) return null return index, :mData[index] } // We'll start the iterator at the first invalid index after the end of the array // and then go back from there. return iterator, this, #:mData } else { function iterator(index) { ++index if(index >= #:mData) return null return index, :mData[index] } // To go forward, we'll start at -1, so that when we increment the index, it'll be // at 0. return iterator, this, -1 } } // This is a really simple metamethod; you can check it out in a later section. function opLength() = #:mData } local a = Array(5) for(i: 0 .. #a) a[i] = i + 1 // Loop through normally. foreach(i, v; a) writefln("a[{}] = {}", i, v) writeln() // Loop through in reverse. foreach(i, v; a, "reverse") writefln("a[{}] = {}", i, v) writeln() // Iterating over a table is a little trickier, since you can't just go element-by-element. // One way to do it is to get the keys array, keep that in an upvalue, and iterate over that, // returning key-value pairs from the table. In this case, we ignore the index passed to the // iterator function by MiniD, since the index that it gives us will be a table index, and not // the index into the keys array. class Map { mData this() :mData = {} function opIndex(index) = :mData[index] function opIndexAssign(index, value) :mData[index] = value function opApply(method) { // We're seeing if the method parameter is "intValues", and we're going to // only iterate through key-value pairs which have integer values if it is. if(method is "intValues") { local index = 0 local keys = :mData.keys() // Remember, the state is passed as the context to this iterator function. // We're going to ignore the index in this function. function iterator() { while(index < #keys) { local key = keys[index] local value = :mData[key] ++index if(isInt(value)) return key, value } return null } // We return the iterator function and the container to iterate (this). // We're not using the index, so we don't have to return anything. return iterator, this } else { // Regular iteration. local index = 0 local keys = :mData.keys() function iterator() { if(index < #keys) { local key = keys[index] ++index return key, :mData[key] } return null } return iterator, this } } } local m = Map() m[4] = 5 m[8] = "hi" m["x"] = 3 // Print out all the key-value pairs in the map. foreach(k, v; m) writefln("m[{}] = {}", k, v) writeln() // Print out just those pairs which have integer values. foreach(k, v; m, "intValues") writefln("m[{}] = {}", k, v) }}} == '''`anything opCall(anything)`''' == Using the `opCall` metamethod, you can make any object behave as if it were a function. Because of this, `opCall` can take any number and type of parameters, and return any number and type of values. Here is an example. {{{ #!minid // A group of functions which can all be called at once using opCall. class FuncGroup { mFuncs this() :mFuncs = {} function opCatAssign(vararg) { for(i: 0 .. #vararg) { local func = vararg[i] if(!isFunction(func)) throw format("FuncGroup -- trying to add a '{}': {}", typeof(func), func) :mFuncs[func] = func } } function opCall(vararg) { foreach(func; :mFuncs) func(vararg) } } // An imaginary GUI widget. class Widget { mOnClick this() :mOnClick = FuncGroup() function addClickHandler(func) :mOnClick ~= func; function click(x, y) { // Now we're using opCall to pretend like mOnClick is a function. // We have to put parens around :mOnClick or else it'll be parsed // as a method call. (:mOnClick)(x, y) } } local button = Widget() // Set some handlers. button.addClickHandler(function(x, y) writefln("Handler 1: {}, {}", x, y)) button.addClickHandler(function(x, y) writefln("Handler 2: {}, {}", x, y)) // Simulate a click to call the onClick handlers. button.click(4, 5) }}} == '''`any opLength()`, `void opLengthAssign(any)`''' == You can overload the length (#) operator with these two metamethods. `opLength` is called when the length is to be retrieved and should return one value; `opLengthAssign` is called when the length is to be set, takes one value, and returns nothing. They're very simple. A contrived example follows. {{{ #!minid class O { mLen = 0 function opLength() = :mLen function opLengthAssign(l: int) :mLen = l } local o = O() writeln(#o) // calls opLength and prints 0 #o = 5 // calls opLengthAssign writeln(#o) // prints 5 }}} == Unary Metamethods == There are a few unary metamethods which only take one parameter, the object on which they're operating. They are as follows: * `opNeg` (`-a`) * `opCom` (`~a`) * `opInc` (`++a` or `a++`) * `opDec` (`--a` or `a--`) All of these methods take no parameters (besides `this`). `opNeg` and `opCom` may return any value; `opInc` and `opDec` do not return a vlue. A contrived example follows. {{{ #!minid class Int { mVal this(v: int) :mVal = v function opNeg() = Int(-:mVal) function opCom() = Int(~:mVal) function opInc() :mVal++ function opDec() :mVal-- function toString() = toString(:mVal) } local i = Int(5) writeln(i) writeln(-i) writeln(~i) i++ writeln(i) i-- writeln(i) }}} == Binary Metamethods == There are many binary operators which all behave in very similar ways. Here is a table documenting each operator and its corresponding metamethods. ||'''Operator'''||'''Commutative?'''||'''Metamethod'''||'''Reverse'''|| ||'''+'''||yes||`opAdd`||`opAdd_r`|| ||'''-'''|| ||`opSub`||`opSub_r`|| ||'''*'''||yes||`opMul`||`opMul_r`|| ||'''/'''|| ||`opDiv`||`opDiv_r`|| ||'''%'''|| ||`opMod`||`opMod_r`|| ||'''&'''||yes||`opAnd`||`opAnd_r`|| ||'''|'''||yes||`opOr`||`opOr_r`|| ||'''!^'''||yes||`opXor`||`opXor_r`|| ||'''<<'''|| ||`opShl`||`opShl_r`|| ||'''>>'''|| ||`opShr`||`opShr_r`|| ||'''>>>'''|| ||`opUShr`||`opUShr_r`|| Now, to interpret this table, we need to know how binary metamethod lookup happens when we encounter an expression that needs to call metamethods. There are two cases: commutative operators and noncommutative operators. Suppose we have an expression such as "`a + b`". Addition is a commutative operation according to the above table. The metamethod lookup works as follows for addition as well as for all commutative binary operations: 1. `a.opXxx(b)` 2. `b.opXxx_r(a)` 3. `a.opXxx_r(b)` 4. `b.opXxx(a)` So for addition, it would try `a.opAdd(b)`, then `b.opAdd_r(a)`, then `a.opAdd_r(b)`, and finally `b.opAdd(a)`. If it attempts all four and fails, it's an error. What does commutativity have to do with the lookup? Let's contrast it with the metamethod lookup for something like subtraction, which isn't commutative: 1. `a.opXxx(b)` 2. `b.opXxx_r(a)` Now you can see that for noncommutative operations, it doesn't attempt the last two steps; that is, it doesn't call metamethods in the "wrong" order. All of these operations take one parameter which is the other operand of the operation, and are expected to return one value. Here is a simple example. {{{ #!minid class Vec2 { mX = 0.0 mY = 0.0 this(x: int|float = 0.0, y: int|float = 0.0) { :mX = toFloat(x) :mY = toFloat(y) } function opAdd(other: int|float|Vec2) if(other as Vec2) return Vec2(:mX + other.mX, :mY + other.mY) else return Vec2(:mX + other, :mY + other) function opSub(other: int|float|Vec2) if(other as Vec2) return Vec2(:mX - other.mX, :mY - other.mY) else return Vec2(:mX - other, :mY - other) function opSub_r(other: int|float|Vec2) if(other as Vec2) return Vec2(other.mX - :mX, other.mY - :mY) else return Vec2(other - :mX, other - :mY) function toString() = format("<{}, {}>", :mX, :mY) } local v1 = Vec2(1, 2) local v2 = Vec2(3, 4) writeln(" v1 = ", v1) writeln(" v2 = ", v2) writeln("v1 + v2 = ", v1 + v2) // v1.opAdd(v2) writeln(" 3 + v1 = ", 3 + v1) // v1.opAdd(3) writeln(" v1 - 5 = ", v1 - 5) // v1.opSub(5) writeln(" 5 - v1 = ", 5 - v1) // v1.opSub_r(5) }}} == Reflexive Metamethods == These are the operations which are the combination of a binary operator and an assignment. In these expressions, the left-hand side is taken to be both the source and the destination. These methods all work pretty much the same way: they take one value, the right-hand side, and return nothing. The possible methods are as follows: * `opAddAssign` (`a += b`) * `opSubAssign` (`a -= b`) * `opMulAssign` (`a *= b`) * `opDivAssign` (`a /= b`) * `opModAssign` (`a %= b`) * `opAndAssign` (`a &= b`) * `opOrAssign` (`a |= b`) * `opXorAssign` (`a ^= b`) * `opShlAssign` (`a <<= b`) * `opShrAssign` (`a >>= b`) * `opUShrAssign` (`a >>>= b`) Here is the vector example, expanded a bit. {{{ #!minid class Vec2 { mX = 0.0 mY = 0.0 this(x: int|float = 0.0, y: int|float = 0.0) { :mX = toFloat(x) :mY = toFloat(y) } function opAdd(other: int|float|Vec2) if(other as Vec2) return Vec2(:mX + other.mX, :mY + other.mY) else return Vec2(:mX + other, :mY + other) function opSub(other: int|float|Vec2) if(other as Vec2) return Vec2(:mX - other.mX, :mY - other.mY) else return Vec2(:mX - other, :mY - other) function opSub_r(other: int|float|Vec2) if(other as Vec2) return Vec2(other.mX - :mX, other.mY - :mY) else return Vec2(other - :mX, other - :mY) function opAddAssign(other: int|float|Vec2) { if(other as Vec2) { :mX += other.mX :mY += other.mY } else { :mX += other :mY += other } } function toString() = format("<{}, {}>", :mX, :mY) } local v1 = Vec2(1, 2) local v2 = Vec2(3, 4) writeln(" v1 = ", v1) writeln(" v2 = ", v2) writeln("v1 + v2 = ", v1 + v2) writeln(" 3 + v1 = ", 3 + v1) writeln(" v1 - 5 = ", v1 - 5) writeln(" 5 - v1 = ", 5 - v1) local v3 = Vec2(5, 10) writeln(" v3 = ", v3) v3 += 0.5 // v3.opAddAssign(0.5) writeln(" v3 = ", v3) }}}