Metamethods can be used to define how Hollywood's operators shall behave when used with tables. Normally, you cannot use any of Hollywood's operators with tables as operands. For example, the following is not possible:
table_A = {1, 2, 3, 4, 5} table_B = {5, 4, 3, 2, 1} result = table_A + table_B ; generates compiler error! |
The code above tries to add table_A
to table_B
but this does not work
because tables may contain any random data (functions, subtables, strings,
etc.) so there is no generic way of saying how the add operator should
behave on a table. This is where metamethods come into play. Metamethods
allow you to define how an operator shall behave when it receives a table
operand. In other words, metamethods allow you to define a function that
gets executed whenever an operator is used with a table operand. This
function then computes the result and it is called a metamethod.
Metamethods are not a global setting but they are private to every table. When you create a table it will not have any metamethods attached. Thus, trying to use an operator on this table will fail because it does not have any metamethods. To assign metamethods to a table you need to use the SetMetaTable() command. A metatable is a table containing a set of metamethods. SetMetaTable() accepts two table argument: The first argument is the table whose metamethods you would like to set, and the second argument is the actual metatable, i.e. the table that contains the metamethods that you would like to set.
Let's have a look at an example now. We will rewrite the code from above using metamethods so that we can add the two tables.
mt = {} ; create our metatable Function mt.__add(a, b) Local sizeA = ListItems(a) ; number of elements in table A Local sizeB = ListItems(b) ; number of elements in table B Local result = {} ; create resulting table For Local k = 0 To Min(sizeA, sizeB) - 1 result[k] = a[k] + b[k] ; add elements Next Return(result) ; return resulting table EndFunction table_A = {1, 2, 3, 4, 5} table_B = {5, 4, 3, 2, 1} SetMetaTable(table_A, mt) ; set "mt" as table_A's metatable result = table_A + table_B |
The resulting table will have five elements that are all set to 6. Now what
did we do in the code above? We first create an empty table that serves as
our metatable. Then we add a function called __add
(using two underscores)
to that table. This function will be the metamethod for the + operator. Note
that we must use the name __add
for this function because Hollywood uses
the function name to detect the operator that is served by the metamethod.
Using __add
as name defines a metamethod for the add (+) operator. The code
in our metamethod simply calculates the length of the two tables, adds the
table elements, and stores them in a resulting table that it returns.
Note that the implementation of our __add
metamethod above requires that
both arguments are tables. And the tables must only contain numbers (or
strings that can be converted to numbers). E.g. the following expressions
would not work using the above metamethod implementation:
result = table_A + 10 ; --> error because "10" is not a table result = table_A + "Hello" ; --> same error |
Of course, it is possible to write metamethods which can handle these situations. You would just have to check the types of the parameters that are passed to your metamethod and then you can take custom actions depending on the variable types specified.
Now we have covered the metamethod for the add (+) operator only. Of course, you can set a metamethod for every other Hollywood operator, too. You can also create metamethods for all relational operators (= <> < > <= >=) so that you can compare tables directly. All you need to know is the correct name for the metamethod of the operator so that you can install it. Here is a list of all available metamethods and to their corresponding operators:
As you can see, there are no metamethods for the >, >=, and <> operators. This is because Hollywood handles them by simply reformulating the condition in the following way:
a <> b is the same as Not (a = b) a > b is the same as b < a a >= b is the same as b <= a |
If you would like to compare two tables that both have associated metatables,
but you would like to compare them without invoking the __eq
metamethod, you
have to use the RawEqual() function. This function will compare both tables
just by reference without invoking any metamethod.
Differing metatables with binary operators
As you have seen above every table has its own private metatable setting. When using binary operators, however, it could happen that the two operands do not use the same metatable but different ones. So how does Hollywood choose the metatable for the operator now? This depends on several conditions:
Limitations of the relational metamethods
You have already read above that the relational metamethods will only be called if the two operands use the same metatables. However, there is another limitation when using relational metamethods: They will only be called if the two operands are tables. It is not possible to compare a table with a number, or comparing a string with a table, etc. The arithmetic and bitwise metamethods can be made to work with any variable type but the relational metamethods are limited to comparisons of tables.
Advanced metamethods
So far we have only covered the relational, arithmetic, bitwise and
concatenation metamethods. There are, however, a few more metamethods that
you can use, namely __index
, __newindex
, __call
and __tostring
.
Here is a detailed description of these metamethods:
__index:
mt = {} Function mt.__index(t, idx) Return(0) EndFunction t = {x = 10, y = 20} SetMetaTable(t, mt) NPrint(t.x, t.y, t.z) ; --> prints 10 20 0 |
Without our metatable, the call to NPrint() would fail because
z
has not been initialized. By using the metatable, however,
z
will automatically fall back to 0 because it does not exist.
Sometimes it might become necessary to read from a table without invoking any metamethod. You can do this using the RawGet() function. RawGet() will never invoke any metamethod. If an index does not exist it will return Nil to you.
__newindex:
mt = {} Function mt.__newindex(t, idx, val) NPrint("Blocked writing", val, "at index", idx) EndFunction t = {x = 10, y = 20} SetMetaTable(t, mt) t.z = 45 ; --> "Blocked writing 45 at index z" |
The code above sets table t
as write-protected. You will not
be able to make any modifications to the table.
Sometimes it might become necessary to write to a table without invoking any metamethod. You can do this using the RawSet() function. RawSet() will never invoke any metamethod. You could even write to write-protected tables using the RawSet() function.
__call:
mt = {} Function mt.__call(t) Local c = ListItems(t) Local sum = 0 For Local k = 0 To c - 1 Do sum = sum + t[k] Return(sum / c) EndFunction t = {10, 23, 45, 5, 107, 45, 18, 46} SetMetaTable(t, mt) NPrint(t()) ; --> 37.375 |
The code above will return 37.375 which is the average of the
eight values stored in table t
.
__tostring:
__tostring
metamethod, however,
you can easily change this behaviour. Here is a metamethod which
creates a string representation of a table:
mt = {} Function mt.__tostring(t) Local r$ For Local k=0 To ListItems(t)-1 Do r$=r$..t[k].." " Return(r$) EndFunction t = {"Jeff", "Andy", "Mike", "Dave"} SetMetaTable(t, mt) NPrint(t) ; --> Jeff Andy Mike Dave |
The code above prints "Jeff Andy Mike Dave" because our __tostring
metamethod has simply concatenated all elements of the table.