This first edition was written for Lua 5.0. While still largely relevant for later versions, there are some differences.

The fourth edition targets Lua 5.3 and is available at Amazon and other bookstores.

By buying the book, you also help to support the Lua project.

Programming in Lua | ||

Part II. Tables and Objects Chapter 13. Metatables and Metamethods |

In this section, we 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 endTo 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 setsThe 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 endAfter 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: 00672B60 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.unionWhenever 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 + 8To 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)If we want more lucid error messages, we must check the type of the operands explicitly before attempting to perform the operation:

function Set.union (a,b) if getmetatable(a) ~= Set.mt or getmetatable(b) ~= Set.mt then error("attempt to `add' a set with a non-set value", 2) end ... -- same as before

Copyright © 2003–2004 Roberto Ierusalimschy. All rights reserved. |