Topic 09-meta.md

Metatables

Any table or userdata may have a metatable associated with it. Metatables specify the behaviour of their tables, in a similar way to how classes specify the behaviour of their objects in a traditional object-orientated language. Metatables do this by defining metamethods. In other respects, metatables are plain tables.

Custom String Representation

tostring will try to convert a value into a string; it is used by print when presenting values. It is not particularly useful for tables,

 obj = {label = "hello"}
 print(obj) --> table: 0x80e91f8

tostring does let an object decide how to show itself. If the object has a metatable, and that metatable contains a function called __tostring, it will use that function to get the string:

 local MT = {}

 MT.__tostring = function(obj)
     return obj.label
 end

 setmetatable(obj,MT)

 print(obj) --> hello

A more realistic example involves making tables print out their contents. In the specialized case of an array or list, it would look like this:

 List = {}
 List.__tostring = function(list)
     local res = {}
     for i = 1,#list do
         res[i] = tostring(list[i])
     end
     return '{'..table.concat(res,',')..'}'
 end

 function new_list(t)
     return setmetatable(t or {},List)
 end

 l1 = new_list{10,20,30}
 print(l2) ---> {10,20,30}

This is genuinely useful when working with lists. The __tostring implementation is a little more complicated than it should be, because table.concat itself does not use tostring and needs a table of strings or numbers. (This is actually deliberate, since table.concat is the way to build large strings for output in Lua. If you make a mistake and pass a table value or nil, then it is better for this to be a runtime error than to have to look through ten pages of output for the mistake.)

Making a Table Callable and Overriding Equality

The ‘constructor’ new_list takes an existing table, or makes a new table. (t or {} is a common Lua idiom for specifying a default value for the case when something may be nil.) It would be more elegant if List could be used as a constructor, like List{10,20,30}. The __call metamethod makes a table callable. When you try to ‘call a table’ then Lua looks for a function with this name in the metatable, passing the original table:

 setmetatable(List,{
     __call = function(C,t) return new_list(t) end
 })

 l2 = List{10,20,30}
 print(l2) --> {10,20,30}

It would be useful if lists could be compared element-by-element when using the equality operator ==. By defining the metamethod __eq, the usual behaviour is overriden.

 List.__eq = function(list1,list2)
     if #list1 ~= #list2 then return false end
     for i = 1,#list1 do
         if list1[i] ~= list2[i] then return false end
     end
     return true
 end

 print(li1 == l2)   --> true

List is starting to resemble what people call a class in other languages; it serves both as a factory for making new List objects, and defines shared behaviour in all these objects.

When a Key is not Found in a Table

The basic table access operations using a key are getting and setting values. If a key is not found in the table, then nil is returned. Sometimes this is not an appropriate default value, or you want this case to really be an error. The __index metamethod is called if Lua cannot find a key in a table. It is passed the table and the key:

 local ErrorMT = {}

 function ErrorMt.__index(t,key)
     error("Cannot find '"..key.."' in table",2)
 end

 function new_strict_table(t)
     return setmetatable(t or {},ErrorMT)
 end

 t = new_strict_table {fred = 1}
 --> case-sensitive!  Throws an error "Cannot find 'Fred' in table"
 print(t.Fred)

Consider counting unique words in a table:

 for i = 1,#words do
     local word = words[i]
     local count = count_of[word]
     if not count then
         count_of[word] = 0
     end
     count_of[word] = count + 1
 end

Having to check for the first occurance of a word is irritating; if the key is not found, the value should be zero.

 ZeroMT = {
     __index = function(t,key) return 0 end
 }

Then tables with this metatable can be used like this:

 for i,word = ipairs(words) do
     count_of[word] = count_of[word] + 1
 end

Going back to the List example, it would be nice if lists had some methods. The extend method will append all the elements of another table:

 local methods = {}

 List.__index = function(list,k) -- *note*
     return methods[k]
 end

 function methods.extend(self,list)
     local k = #self
     for i = 1,#list do
         k = k + 1
         self[k] = list[i]
     end
     return self
 end

 ls = List{10,20,30}
 ls:extend {40,50}
 print(ls) --> {10,20,30,40,50}

That is, if we can’t find a key such as extend then __index assumes that it will be inside a table of methods.

(This is such a common pattern that Lua allows __index to be a table, so you can write the marked line as:

 List.__index = methods

A little less flexible, but faster and cleaner.)

It’s easy to keep adding methods. For instance, this works like string.sub , where the second index can be negative to refer to the end of the list:

 function methods.sub(self,i1,i2)
     if i2 == nil then
         i2 = #self
     elseif i2 < 0 then
         i2 = #self + i2 + 1
     end
     local res = List()
     for i = i1,i2 do
         res[#res + 1] = self[i]
     end
     return res
 end

 print(ls:sub(1)) --> {10,20,30,40,50}
 print(ls:sub(2,-2)) --> {20,30,40}

Such a section of a list is usually called a ‘slice’. Note that calling with a start index of 1 gives us a copy of the full list.

To make this list even more Python-like, we can implement concatenation. This is the new list made from appending the second list to the first list. l1 .. l2 can be made to do this if the __concat metamethod is defined:

 List.__concat = function(l1,l2)
     local res = self:sub(1)  --- make a copy
     return res:extend(l2)    --- and append l2
 end

 print(List{10,20} .. List{30,40} .. List{50}) --> {10,20,30,40,50}

To recap: the behaviour of any table can be changed by giving it a metatable, which contains metamethods. The most important of these is __index which allows you to handle the case where a key is not found in the table. It may simply point to a table which is used as the ‘fallback’ table which is examined after the first lookup fails. This is a common way to implement object-oriented programming in Lua, allowing the methods for all objects of a given ‘class’ to be stored in one place.

You could define l1 + l2 to mean this using the metamethod __add, but Lua programmers would not expect addition to work like that. They would expect that l1 + l2 is the list made by adding the corresponding elements of the two lists (‘element-wise’), and it would of course only work with lists where the elements could be added.

When a new Key is Initialized

The operation of setting a value can also be customized. __newindex works like _index; it is only called if the key is not in the table. It receives three arguments, the table itself, the key and the new value.

A source of frustration is Lua’s ‘undefined is nil’ behaviour. Spelling mistakes become nil, which might crash immediately – that’s the good outcome. Or these nils travel around and blow up in some other part of the program. Fortunately, Lua provides the mechanism, you decide the policy. We would still like to create global variables deliberately, so __newindex can make a reference when a new variable is created (even if it is nil). It must actually set the value using rawset .

Then __index merely has to check whether the variable has been already declared. In fact, we only need to keep those variables which are initially nil in declared.

 function strict (T)
     local declared = {}
     local mt = {}

     mt.__newindex = function(t,key,value)
         declared [variable] = true
         rawset (t,key,value)
     end

     mt.__index = function(t,variable)
         if not declared [variable] then
             error("variable '"..variable.."' is undeclared",2)
         end
     end

     return setmetatable(T,mt)
 end

 strict (_G)
 print (prnt) --> error: variable 'prnt' is not declared
 x = nil
 -- ok, because it's declared
 print(x) --> nil

No reason why we cannot use this on the standard library tables as well!

 strict(math)
 print(math.sine(1.2)) --> error: variable 'sine' is not declared

What if you want __index and __newindex to be always called? Then the keys by definition cannot be in the table itself; this is the important point about these metamethods – they only fire if the key does not already exist in the table. (Earlier in the history of Lua these functions were called fallbacks for this reason.)

Instead, the table must act as a proxy for another table. Say we have an array and want to raise a ‘out of bounds’ error instead of just silently returning nil.

 local ProxyListMT = {}

 function new_array(arr)
     return setmetatable({_data = arr},ProxyListMT)
 end

 function ProxyListMT.__index(self,k)
     if k < 1 or k > #self._data then error("out of bounds",2) end
     return self._data[k]
 end

 function ProxyListMT.__newindex(self,k,value)
     if k < 1 or k > #self._data then error("out of bounds",2) end
     self._data[k] = value
 end

That is, the object just contains a _data field which refers to the actual table; any time we access an array element, it must go through __index because there are no array elements in the object itself; it is acting as a proxy for the table. The object behaves like a non-resizable array. It is naturally not quite as efficient, but it is often more important to be correct than to be fast.

generated by LDoc 1.3