|
| 25 Jul 2012 07:20 PM |
I will introduce a simple example to explain how to use metatables. Suppose we are using tables to represent sets, with functions to compute the union of two sets, intersection, and the like. As we did with lists, we store these functions inside a table and we define a constructor to create new sets:
Set = {} function Set.new (t) local set = {} for _, l in ipairs(t) do set[l] = true end return set end function Set.union (a,b) local res = Set.new{} for k in pairs(a) do res[k] = true end for k in pairs(b) do res[k] = true end return res end function Set.intersection (a,b) local res = Set.new{} for k in pairs(a) do res[k] = b[k] end return res end
To help checking our examples, we also define a function to print sets: function Set.tostring (set) local s = "{" local sep = "" for e in pairs(set) do s = s .. sep .. e sep = ", " end return s .. "}" end function Set.print (s) print(Set.tostring(s)) end
Now, we want to make the addition operator (`+´) compute the union of two sets. For that, we will arrange that all tables representing sets share a metatable and this metatable will define how they react to the addition operator. Our first step is to create a regular table that we will use as the metatable for sets. To avoid polluting our namespace, we will store it in the Set table:
Set.mt = {} -- metatable for sets
The next step is to modify the Set.new function, which creates sets. The new version has only one extra line, which sets mt as the metatable for the tables that it creates: function Set.new (t) -- 2nd version local set = {} setmetatable(set, Set.mt) for _, l in ipairs(t) do set[l] = true end return set end
After that, every set we create with Set.new will have that same table as its metatable: s1 = Set.new{10, 20, 30, 50} s2 = Set.new{30, 1} print(getmetatable(s1)) --> table: 00672B90 print(getmetatable(s2)) --> table: 00672B60
Finally, we add to the metatable the so-called metamethod, a field __add that describes how to perform the union:
Set.mt.__add = Set.union
Whenever Lua tries to add two sets, it will call this function, with the two operands as arguments. With the metamethod in place, we can use the addition operator to do set unions:
s3 = s1 + s2 Set.print(s3) --> {1, 10, 20, 30, 50}
Similarly, we may use the multiplication operator to perform set intersection: Set.mt.__mul = Set.intersection Set.print((s1 + s2)*s1) --> {10, 20, 30, 50}
For each arithmetic operator there is a corresponding field name in a metatable. Besides __add and __mul, there are __sub (for subtraction), __div (for division), __unm (for negation), and __pow (for exponentiation). We may define also the field __concat, to define a behavior for the concatenation operator.
When we add two sets, there is no question about what metatable to use. However, we may write an expression that mixes two values with different metatables, for instance like this:
s = Set.new{1,2,3} s = s + 8
To choose a metamethod, Lua does the following: (1) If the first value has a metatable with an __add field, Lua uses this value as the metamethod, independently of the second value; (2) otherwise, if the second value has a metatable with an __add field, Lua uses this value as the metamethod; (3) otherwise, Lua raises an error. Therefore, the last example will call Set.union, as will the expressions 10 + s and "hy" + s. Lua does not care about those mixed types, but our implementation does. If we run the s = s + 8 example, the error we get will be inside Set.union:
bad argument #1 to `pairs' (table expected, got number)
|
|
|
| Report Abuse |
|
|
| 25 Jul 2012 07:23 PM |
| ...You just copied this from the Lua website. |
|
|
| Report Abuse |
|