20 Metatables and Metamethods

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”.

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 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.

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>