Usually, each value in Lua has a quite predictable set of operations. We can add numbers, we can concatenate strings, we can insert key–value pairs into tables, and so on. However, we cannot add tables, we cannot compare functions, and we cannot call a string. Unless we use metatables.
Metatables allow us to change the behavior of a value
when confronted with an unknown operation.
For instance, using metatables,
we can define how Lua computes the expression a + b
,
where a
and b
are tables.
Whenever Lua tries to add two tables,
it checks whether either of them has a metatable
and whether this metatable has an __add
field.
If Lua finds this field, it calls the corresponding value
—the so-called metamethod, which should be a function—
to compute the sum.
We can think about metatables as a restricted kind of classes, in object-oriented terminology. Like classes, metatables define the behavior of its instances. However, metatables are more restricted than classes, because they can only give behavior to a predefined set of operations; also, metatables do not have inheritance. Nevertheless, we will see in Chapter 21, Object-Oriented Programming how to build a quite complete class system on top of metatables.
Each value in Lua can have a metatable. Tables and userdata have individual metatables; values of other types share one single metatable for all values of that type. Lua always creates new tables without metatables:
t = {} print(getmetatable(t)) --> nil
We can use setmetatable
to set or change the
metatable of a table:
t1 = {} setmetatable(t, t1) print(getmetatable(t) == t1) --> true
From Lua, we can set the metatables only of tables; to manipulate the metatables of values of other types we must use C code or the debug library. (The main reason for this restriction is to curb excessive use of type-wide metatables. Experience with older versions of Lua has shown that those global settings frequently lead to non-reusable code.) The string library sets a metatable for strings; all other types by default have no metatable:
print(getmetatable("hi")) --> table: 0x80772e0 print(getmetatable("xuxu")) --> table: 0x80772e0 print(getmetatable(10)) --> nil print(getmetatable(print)) --> nil
Any table can be the metatable of any value; a group of related tables can share a common metatable, which describes their common behavior; a table can be its own metatable, so that it describes its own individual behavior. Any configuration is valid.
In this section, we will introduce a running example to explain the basics of metatables. Suppose we have a module that uses tables to represent sets, with functions to compute set union, intersection, and the like, as shown in Figure 20.1, “A simple module for sets”.
Figure 20.1. A simple module for sets
local Set = {} -- create a new set with the values of a given list function Set.new (l) local set = {} for _, v in ipairs(l) do set[v] = 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 -- presents a set as a string function Set.tostring (set) local l = {} -- list to put all elements from the set for e in pairs(set) do l[#l + 1] = tostring(e) end return "{" .. table.concat(l, ", ") .. "}" end return Set
Now, we want to use the addition operator to compute the union of two sets. For that, we will arrange for all tables representing sets to share a metatable. This metatable will define how they react to the addition operator. Our first step is to create a regular table, which we will use as the metatable for sets:
local mt = {} -- metatable for sets
The next step is to modify Set.new
,
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 (l) -- 2nd version local set = {} setmetatable(set, mt) for _, v in ipairs(l) do set[v] = 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: 0x00672B60 print(getmetatable(s2)) --> table: 0x00672B60
Finally, we add to the metatable the metamethod __add
,
a field that describes how to perform the addition:
mt.__add = Set.union
After that,
whenever Lua tries to add two sets,
it will call Set.union
,
with the two operands as arguments.
With the metamethod in place, we can use the addition operator to do set unions:
s3 = s1 + s2 print(Set.tostring(s3)) --> {1, 10, 20, 30, 50}
Similarly, we may set the multiplication operator to perform set intersection:
mt.__mul = Set.intersection print(Set.tostring((s1 + s2)*s1)) --> {10, 20, 30, 50}
For each arithmetic operator there is a
corresponding metamethod name.
Besides addition and multiplication,
there are metamethods for subtraction (__sub
),
float division (__div
),
floor division (__idiv
),
negation (__unm
),
modulo (__mod
),
and exponentiation (__pow
).
Similarly,
there are metamethods for all bitwise operations:
bitwise AND (__band
),
OR (__bor
),
exclusive OR (__bxor
),
NOT (__bnot
),
left shift (__shl
),
and right shift (__shr
).
We may define also a behavior for the concatenation operator,
with the field __concat
.
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
When looking for a metamethod,
Lua performs the following steps:
if the first value has a metatable with the required metamethod,
Lua uses this metamethod,
independently of the second value;
otherwise,
if the second value has a metatable with the required metamethod,
Lua uses it;
otherwise, Lua raises an error.
Therefore, the last example will call Set.union
,
as will the expressions 10 + s
and "hello" + s
(because both numbers and strings
do not have a metamethod __add
).
Lua does not care about these mixed types,
but our implementation does.
If we run the s = s + 8
example,
we will get an error inside the function 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, for instance with code like this:
function Set.union (a, b)
if getmetatable(a) ~= mt or getmetatable(b) ~= mt then
error("attempt to 'add' a set with a non-set value", 2)
end
as before
Remember that the second argument to error
(2, in this example)
sets the source location in the error message
to the code that called the operation.
Metatables also allow us to give meaning to
the relational operators,
through the metamethods __eq
(equal to),
__lt
(less than),
and __le
(less than or equal to).
There are no separate metamethods for the
other three relational operators:
Lua translates a ~= b
to not (a == b)
,
a > b
to b < a
, and a >= b
to b <= a
.
In older versions,
Lua translated all order operators to a single one,
by translating a <= b
to not (b < a)
.
However, this translation is incorrect when we have a
partial order,
that is, when not all elements in our type are properly ordered.
For instance,
most machines do not have
a total order for floating-point numbers,
because of the value Not a Number (NaN).
According to the IEEE 754 standard,
NaN represents undefined values,
such as the result of 0/0.
The standard specifies that any comparison that
involves NaN should result in false.
This means that NaN <= x
is always false,
but x < NaN
is also false.
It also means that the translation from a <= b
to not (b < a)
is not valid in this case.
In our example with sets, we have a similar problem.
An obvious (and useful) meaning for <=
in sets is set containment:
a <= b
means that a
is a subset of b
.
With this meaning,
it is possible that a <= b
and
b < a
are both false.
Therefore, we must implement both
__le
(less or equal, the subset relation)
and __lt
(less than, the proper subset relation):
mt.__le = function (a, b) -- subset for k in pairs(a) do if not b[k] then return false end end return true end mt.__lt = function (a, b) -- proper subset return a <= b and not (b <= a) end
Finally, we can define set equality through set containment:
mt.__eq = function (a, b) return a <= b and b <= a end
After these definitions, we are ready to compare sets:
s1 = Set.new{2, 4} s2 = Set.new{4, 10, 2} print(s1 <= s2) --> true print(s1 < s2) --> true print(s1 >= s1) --> true print(s1 > s1) --> false print(s1 == s2 * s1) --> true
The equality comparison has some restrictions. If two objects have different basic types, the equality operation results in false, without even calling any metamethod. So, a set will always be different from a number, no matter what its metamethod says.
So far, all the metamethods we have seen are for the Lua core. It is the virtual machine who detects that the values involved in an operation have metatables with metamethods for that particular operation. However, because metatables are regular tables, anyone can use them. So, it is a common practice for libraries to define and use their own fields in metatables.
The function tostring
provides a typical example.
As we saw earlier, tostring
represents tables in a
rather simple format:
print({}) --> table: 0x8062ac0
The function print
always calls tostring
to format its output.
However, when formatting any value,
tostring
first checks whether the value has a
__tostring
metamethod.
In this case,
tostring
calls the metamethod to do its job,
passing the object as an argument.
Whatever this metamethod returns is the result of tostring
.
In our example with sets,
we have already defined a function to present a set as a string.
So, we need only to set the __tostring
field in the metatable:
mt.__tostring = Set.tostring
After that, whenever we call print
with a set as its argument,
print
calls tostring
that calls Set.tostring
:
s1 = Set.new{10, 4, 5} print(s1) --> {4, 5, 10}
The functions setmetatable
and getmetatable
also use a metafield,
in this case to protect metatables.
Suppose we want to protect our sets,
so that users can neither see nor change their metatables.
If we set a __metatable
field in the metatable,
getmetatable
will return the value of this field,
whereas setmetatable
will raise an error:
mt.__metatable = "not your business" s1 = Set.new{} print(getmetatable(s1)) --> not your business setmetatable(s1, {}) stdin:1: cannot change protected metatable
Since Lua 5.2, pairs
also have a metamethod,
so that we can modify the way a table is traversed
and add a traversal behavior to non-table objects.
When an object has a __pairs
metamethod,
pairs
will call it to do all its work.
The metamethods for arithmetic, bitwise, and relational operators all define behavior for otherwise erroneous situations; they do not change the normal behavior of the language. Lua also offers a way to change the behavior of tables for two normal situations, the access and modification of absent fields in a table.
We saw earlier that,
when we access an absent field in a table,
the result is nil.
This is true, but it is not the whole truth.
Actually, such accesses trigger the interpreter to look
for an __index
metamethod:
if there is no such method, as usually happens,
then the access results in nil;
otherwise, the metamethod will provide the result.
The archetypal example here is inheritance. Suppose we want to create several tables describing windows. Each table must describe several window parameters, such as position, size, color scheme, and the like. All these parameters have default values and so we want to build window objects giving only the non-default parameters. A first alternative is to provide a constructor that fills in the absent fields. A second alternative is to arrange for the new windows to inherit any absent field from a prototype window. First, we declare the prototype:
-- create the prototype with default values prototype = {x = 0, y = 0, width = 100, height = 100}
Then, we define a constructor function, which creates new windows sharing a metatable:
local mt = {} -- create a metatable -- declare the constructor function function new (o) setmetatable(o, mt) return o end
Now, we define the __index
metamethod:
mt.__index = function (_, key) return prototype[key] end
After this code, we create a new window and query it for an absent field:
w = new{x=10, y=20} print(w.width) --> 100
Lua detects that w
does not have the requested field,
but has a metatable with an __index
field.
So, Lua calls this __index
metamethod,
with arguments w
(the table)
and "width"
(the absent key).
The metamethod then indexes the prototype with the given key
and returns the result.
The use of the __index
metamethod for inheritance is so common
that Lua provides a shortcut.
Despite being called a method,
the __index
metamethod does not need to be a function:
it can be a table, instead.
When it is a function,
Lua calls it with the table and the absent key as its arguments,
as we have just seen.
When it is a table, Lua redoes the access in this table.
Therefore,
in our previous example,
we could declare __index
simply like this:
mt.__index = prototype
Now, when Lua looks for the metatable’s __index
field,
it finds the value of prototype
, which is a table.
Consequently, Lua repeats the access in this table, that is,
it executes the equivalent of prototype["width"]
.
This access then gives the desired result.
The use of a table as an __index
metamethod provides
a fast and simple way of implementing single inheritance.
A function, although more expensive, provides more flexibility:
we can implement multiple inheritance,
caching, and several other variations.
We will discuss these forms of inheritance
in Chapter 21, Object-Oriented Programming,
when we will cover object-oriented programming.
When we want to access a table without
invoking its __index
metamethod,
we use the function rawget
.
The call rawget(t, i)
does a
raw access to table t
,
that is, a primitive access without considering metatables.
Doing a raw access will not speed up our code
(the overhead of a function call kills any gain we could have),
but sometimes we need it, as we will see later.
The __newindex
metamethod does for table updates what
__index
does for table accesses.
When we assign a value to an absent index in a table,
the interpreter looks for a __newindex
metamethod:
if there is one, the interpreter calls it
instead of making the assignment.
Like __index
, if the metamethod is a table,
the interpreter does the assignment in this table,
instead of in the original one.
Moreover, there is a raw function that
allows us to bypass the metamethod:
the call rawset(t, k, v)
does the equivalent to t[k] = v
without invoking any metamethod.
The combined use of the __index
and __newindex
metamethods
allows several powerful constructs in Lua,
such as read-only tables,
tables with default values,
and inheritance for object-oriented programming.
In this chapter, we will see some of these uses.
Object-oriented programming has its own chapter, later.
The default value of any field in a regular table is nil. It is easy to change this default value with metatables:
function setDefault (t, d) local mt = {__index = function () return d end} setmetatable(t, mt) end tab = {x=10, y=20} print(tab.x, tab.z) --> 10 nil setDefault(tab, 0) print(tab.x, tab.z) --> 10 0
After the call to setDefault
,
any access to an absent field in tab
calls its __index
metamethod,
which returns zero
(the value of d
for this metamethod).
The function setDefault
creates a new closure plus
a new metatable for each table that needs a default value.
This can be expensive
if we have many tables that need default values.
However,
the metatable has the default value d
wired into its metamethod,
so we cannot use a single metatable for tables with
different default values.
To allow the use of a single metatable for all tables,
we can store the default value of each table in the table itself,
using an exclusive field.
If we are not worried about name clashes,
we can use a key like "___"
for our exclusive field:
local mt = {__index = function (t) return t.___ end} function setDefault (t, d) t.___ = d setmetatable(t, mt) end
Note that now we create the metatable mt
and its corresponding metamethod
only once,
outside SetDefault
.
If we are worried about name clashes, it is easy to ensure the uniqueness of the special key. All we need is a new exclusive table to use as the key:
local key = {} -- unique key local mt = {__index = function (t) return t[key] end} function setDefault (t, d) t[key] = d setmetatable(t, mt) end
An alternative approach for associating each table with its default value is a technique I call dual representation, which uses a separate table where the indices are the tables and the values are their default values. However, for the correct implementation of this approach, we need a special breed of table called weak tables, and so we will not use it here; we will return to the subject in Chapter 23, Garbage.
Another alternative is to memorize metatables in order to reuse the same metatable for tables with the same default. However, that needs weak tables too, so that again we will have to wait until Chapter 23, Garbage.
Suppose we want to track every single access to a certain table.
Both __index
and __newindex
are relevant only when
the index does not exist in the table.
So, the only way to catch all accesses to a table
is to keep it empty.
If we want to monitor all accesses to a table,
we should create a proxy for the real table.
This proxy is an empty table,
with proper metamethods
that track all accesses and redirect them to the original table.
The code in Figure 20.2, “Tracking table accesses” implements this idea.
Figure 20.2. Tracking table accesses
function track (t) local proxy = {} -- proxy table for 't' -- create metatable for the proxy local mt = { __index = function (_, k) print("*access to element " .. tostring(k)) return t[k] -- access the original table end, __newindex = function (_, k, v) print("*update of element " .. tostring(k) .. " to " .. tostring(v)) t[k] = v -- update original table end, __pairs = function () return function (_, k) -- iteration function local nextkey, nextvalue = next(t, k) if nextkey ~= nil then -- avoid last value print("*traversing element " .. tostring(nextkey)) end return nextkey, nextvalue end end, __len = function () return #t end } setmetatable(proxy, mt) return proxy end
The following example illustrates its use:
> t = {} -- an arbitrary table > t = track(t) > t[2] = "hello" --> *update of element 2 to hello > print(t[2]) --> *access to element 2 --> hello
The metamethods __index
and __newindex
follow the guidelines that we set,
tracking each access and then redirecting it to the original table.
The __pairs
metamethod
allows us to traverse the proxy as if it were the original table,
tracking the accesses along the way.
Finally, the __len
metamethod gives the length operator
through the proxy:
t = track({10, 20}) print(#t) --> 2 for k, v in pairs(t) do print(k, v) end --> *traversing element 1 --> 1 10 --> *traversing element 2 --> 2 20
If we want to monitor several tables, we do not need a different metatable for each one. Instead, we can somehow map each proxy to its original table and share a common metatable for all proxies. This problem is similar to the problem of associating tables to their default values, which we discussed in the previous section, and allows the same solutions. For instance, we can keep the original table in a proxy’s field, using an exclusive key, or we can use a dual representation to map each proxy to its corresponding table.
It is easy to adapt the concept of
proxies to implement read-only tables.
All we have to do is to raise an error whenever
we track any attempt to update the table.
For the __index
metamethod,
we can use a table
—the original table itself—
instead of a function,
as we do not need to track queries;
it is simpler and rather more efficient to redirect all queries
to the original table.
This use demands a new metatable for each read-only proxy,
with __index
pointing to the original table:
function readOnly (t) local proxy = {} local mt = { -- create metatable __index = t, __newindex = function (t, k, v) error("attempt to update a read-only table", 2) end } setmetatable(proxy, mt) return proxy end
As an example of use, we can create a read-only table for weekdays:
days = readOnly{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"} print(days[1]) --> Sunday days[2] = "Noday" --> stdin:1: attempt to update a read-only table
Exercise 20.1:
Define a metamethod __sub
for sets
that returns the difference of two sets.
(The set a - b is the set of elements
from a that are not in b.)
Exercise 20.2:
Define a metamethod __len
for sets
so that #s
returns the number of elements
in the set s
.
Exercise 20.3:
An alternative way to implement read-only tables
might use a function as the __index
metamethod.
This alternative makes accesses more expensive,
but the creation of read-only tables is cheaper,
as all read-only tables can share a single metatable.
Rewrite the function readOnly
using this approach.
Exercise 20.4:
Proxy tables can represent other kinds of objects besides tables.
file as array
Write a function fileAsArray
that takes the
name of a file and returns a proxy to that file,
so that after a call t = fileAsArray("myFile")
,
an access to t[i]
returns the i
-th byte of that file,
and an assignment to t[i]
updates its i
-th byte.
Exercise 20.5:
Extend the previous example to allow us to
traverse the bytes in the file with pairs(t)
and
get the file length with #t
.
Personal copy of Eric Taylor <jdslkgjf.iapgjflksfg@yandex.com>