Extending Lua's Behavior with Metamethods - Beginning Lua Programming (2007)

Beginning Lua Programming (2007)

Chapter 8. Extending Lua's Behavior with Metamethods

You use metatables and metamethods mainly to create what can, loosely speaking, be described as user-defined types. For example, if you have defined a table that represents a number-like value, metamethods let you use arithmetical operators with it as though it really were a number. In this chapter, you learn how to do the following:

· Use operators with types that they normally don’t work with

· Control what happens when a table is indexed or a table field is assigned to

· Customize how a value is converted to a string

Using Concatenation and Arithmetical Operators on Tables

If you use tables as operands to the concatenation operator, you get the following error:

> A, B = {}, {}

> print(A .. B)

stdin:1: attempt to concatenate global 'A' (a table value)

stack traceback:

stdin:1: in main chunk

[C]: ?

There’s a way to override this, though, so the concatenation operator is handled by a function that you define.

Try It Out

Splicing Two Arrays with the Concatenation Operator

1. Define the function MakeSpliceArr and its upvalue SpliceMeta as follows (notice that SpliceMeta ’s field__concat starts with two underscores):

do

local SpliceMeta -- SpliceMeta needs to be in

-- SpliceMeta.__concat's scope.

SpliceMeta = {

-- Makes a new array by splicing two arrays together:

__concat = function(ArrA, ArrB)

assert(type(ArrA) == "table" and type(ArrB) == "table")

local Ret = setmetatable({}, SpliceMeta)

for I, Elem in ipairs(ArrA) do

Ret[I] = Elem

end

local LenA = #ArrA

for I, Elem in ipairs(ArrB) do

Ret[LenA + I] = Elem

end

return Ret

end}

-- Takes an array, returns that array after giving it a

-- metamethod that makes it "spliceable" with the

-- concatenation operator:

function MakeSpliceArr(Arr)

return setmetatable(Arr, SpliceMeta)

end

end

2. Use it to make two spliceable arrays:

-- Note -- curly braces, not parentheses:

A = MakeSpliceArr{"alpha", "bravo", "charlie"}

B = MakeSpliceArr{"x-ray", "yankee", "zulu"}

3. Concatenate them and look at the result:

C = A .. B

for I, Elem in ipairs(C) do

print(I, Elem)

end

You should see the following:

1 alpha

2 bravo

3 charlie

4 x-ray

5 yankee

6 zulu

How It Works

Every table in Lua (actually every value, but more on that later) can have something called a metatable. By default, a table does not have a metatable. The built-in setmetatable function gives its first argument a metatable (its second argument), and then returns the first argument. Usingsetmetatable, the MakeSpliceArr function sets the SpliceMeta table as the metatable of the array given to it:

function MakeSpliceArr(Arr)

return setmetatable(Arr, SpliceMeta)

end

A and B therefore both have the same metatable. When Lua sees you concatenating A and B, it checks to see if they are both strings or numbers. Because they aren’t, normal string concatenation can’t be done, so it looks to see if the first operand has a metatable, and if that metatable has a field called__concat (two underscores), and if that field contains a function. Because this all checks out, it calls__concat with the first and second operands as arguments, and uses__concat’s result as the result of the .. operator.

A metatable’s__concat field is an example of a metamethod.

The__concat function defined in the example splices the two arrays given to it, creating a new array that has all elements of the first, then the second array, in order. This new array is given the SpliceMeta metatable, so that it will have the same behavior with the concatenation operator:

> for I, Elem in ipairs(C .. C) do

>> print(I, Elem)

>> end

1 alpha

2 bravo

3 charlie

4 x-ray

5 yankee

6 zulu

7 alpha

8 bravo

9 charlie

10 x-ray

11 yankee

12 zulu

This is why local SpliceMeta and the assignment of the table to SpliceMeta are separated into two statements. If they were one statement, then SpliceMeta ’s scope wouldn’t begin until the next statement, and the SpliceMeta used inside__concat would be global.

Because the first operand’s__concat metamethod is used if it has one, the second operand doesn’t have to have one. For example:

> for I, Elem in ipairs(A .. {"delta", "echo", "foxtrot"}) do

>> print(I, Elem)

>> end

1 alpha

2 bravo

3 charlie

4 delta

5 echo

6 foxtrot

If the first operand has no__concat metamethod but the second one does, then that metamethod is used (but the left operand is still the metamethod’s first argument), as follows:

> for I, Elem in ipairs({"uniform", "victor", "whiskey"} .. B) do

>> print(I, Elem)

>> end

1 uniform

2 victor

3 whiskey

4 x-ray

5 yankee

6 zulu

Only if neither operand has a__concat metamethod do you get an error for attempting to concatenate a table.

If more than two tables are concatenated, then (assuming no parentheses are used) at least one of the last two has to have a__concat metamethod. This works because SpliceMeta.__concat ’s result itself has a__concat metamethod. It depends on the last two operands rather than the first two because concatenation is right associative. Here’s an example of multiple concatenated tables:

> A, B, C, D = {"a"}, {"b"}, {"c"}, {"d"}

> Test = MakeSpliceArr(A) .. B .. C .. D

> A, B, C, D = {"a"}, {"b"}, {"c"}, {"d"}

stdin:1: attempt to concatenate global 'C (a table value)

stack traceback:

stdin:1: in main chunk

[C]: ?

> Test = A .. MakeSpliceArr(B) .. C .. D

stdin:1: attempt to concatenate global 'C' (a table value)

stack traceback:

stdin:1: in main chunk

[C]: ?

> Test = A .. B .. MakeSpliceArr(C) .. D

> Test = A .. B .. C .. MakeSpliceArr(D)

To take away a value’s metatable, give setmetatable a second argument of nil:

> A = MakeSpliceArr{"alpha", "bravo", "charlie"}

> setmetatable(A, nil)

> A2 = A .. A

stdin:1: attempt to concatenate global 'A' (a table value)

stack traceback:

stdin:1: in main chunk

To retrieve a value’s metatable, use getmetatable (it returns nil if its argument has no metatable):

> A = MakeSpliceArr{"alpha", "bravo", "charlie"}

> SpliceMeta = getmetatable(A)

> for K, V in pairs(SpliceMeta) do print(K, V) end

__concat function: 0x807ce78

Don’t make the terminological mistake of saying that a table is a metatable when you mean that it has a metatable. (For example, MakeSpliceArr does not return a metatable — it returns a table that has a metatable.) A table and its metatable are generally two different tables, though there’s nothing preventing a table from having itself as a metatable.

There are metamethods for all of Lua’s operators except the Boolean ones (and, or, and not). In the next Try It Out, you use arithmetical metamethods to define a simple implementation of rational numbers. Unlike the floating-point numbers that Lua normally uses, rational numbers can represent quantities like f   and f   with perfect accuracy.

Try It Out

Defining Rational Numbers

1. Save the following as rational.lua:

-- A basic rational number implementation.

-- Returns A and B's greatest common divisor:

local function GetGcd(A, B)

local Remainder = A % B

return Remainder == 0 and B or GetGcd(B, Remainder)

end

-- Metamethods:

local function Add(A, B)

return MakeRat(A.Numer * B.Denom + B.Numer * A.Denom,

A.Denom * B.Denom)

end

local function Sub(A, B)

return A + -B

end

local function Mul(A, B)

return MakeRat(A.Numer * B.Numer, A.Denom * B.Denom)

end

local function Div(A, B)

assert(B.Numer ~= 0, "Divison by zero")

return A * MakeRat(B.Denom, B.Numer)

end

local function Unm(A)

return MakeRat(-A.Numer, A.Denom)

end

local function ToString(Rat)

return Rat.Numer .. "/" .. Rat.Denom

end

local RatMeta = {

_add = Add,

_sub = Sub,

_mul = Mul,

_div = Div,

_unm = Unm,

_tostring = ToString,

}

-- The three global functions supplied by this library:

-- Instantiates a rational number:

function MakeRat(Numer, Denom)

Numer, Denom = tonumber(Numer), tonumber(Denom)

assert(Denom ~= 0, "Denominator must be nonzero")

assert(Numer == math.floor(Numer) and Denom == math.floor(Denom),

"Numerator and denominator must be integers")

-- Make sure the denominator is positive:

if Denom < 0 then

Numer, Denom = -Numer, -Denom

end

-- Reduce the fraction to its lowest terms:

local Gcd = GetGcd(Numer, Denom)

local Rat = {

Numer = Numer / Gcd,

Denom = Denom / Gcd}

return setmetatable(Rat, RatMeta) end

-- Instantiates a rational number from a string of the form

-- "numerator/denominator":

function r(Str)

local Numer, Denom = string.match(Str, "^(%-?%d+)%/(%-?%d+)$")

assert(Numer, "Couldn't parse rational number")

return MakeRat(Numer, Denom)

end

-- Converts a rational to a (floating-point) number:

function RatToNumber(Rat)

return Rat.Numer / Rat.Denom

end

2. Require the file and multiply two rational numbers:

> require("rational")

> print(r"2/3" * r"1/10")

1/15

3. Add the floating-point approximation of f   to itself 300000 times:

> FloatAcc = 0

> for I = 1, 300000 do

>> FloatAcc = FloatAcc + 1 / 3

>> end

> print(FloatAcc)

99999.999999689

4. Add the rational number f   to itself 300000 times:

> RatAcc = r"0/1"

> OneThird = r"1/3"

> for I = 1, 300000 do

>> RatAcc = RatAcc + OneThird

>> end

> print(RatAcc)

100000/1

5. Verify that the result is exactly 100000:

> print(RatToNumber(RatAcc) == 100000)

true

6. Print the rational number f 15/100:

> Rat = r"15/100"

> print(Rat) -- Will be reduced to lowest terms.

7. Print the negative version of the previous rational number:

> print(-Rat)

-3/20

How It Works

A rational number is just a fraction — a numerator over a denominator. It is represented as a table with Numer and Denom fields and a metatable with__add (addition),__sub (subtraction), mul (multiplication),__div (division), and__unm (unary minus) metamethods. (Like__concat, these and all other metamethods start with two underscores.)

In the following part of the Try It Out, r"2/3" and r"1/10" create the rational numbers two thirds and one tenth:

> print(r"2/3" * r"1/10")

1/15

The function is called r to minimize the typing needed for rational “literals.” Remember also that r"2/3" means the same thing as r("2/3") .

When these two rational numbers are multiplied, what happens is basically the same as what happened in the previous concatenation example: Lua sees that they are tables (rather than numbers), so it looks for a __mul metamethod, finds one in the left operand, and calls it with both operands as arguments. This metamethod returns the result (one fifteenth). As it does with all values, print runs this result through tostring, which sees the__tostring metamethod and uses it to do the conversion. (More on_tostring later.)

The FloatAcc section of the example initializes an accumulator to 0 and increments it 300000 times by the floating-point approximation of f   The inaccuracy snowballs, as shown by the final value of FloatAcc:

> FloatAcc = 0

> for I = 1, 300000 do

>> FloatAcc = FloatAcc + 1 / 3

>> end

> print(FloatAcc)

99999.999999689

The RatAcc section, on the other hand, shows no loss of accuracy:

> RatAcc = r"0/1"

> OneThird = r"1/3"

> for I = 1, 300000 do

>> RatAcc = RatAcc + OneThird

>> end

> print(RatAcc)

100000/1

> print(RatToNumber(RatAcc) == 100000)

true

You may have noticed that the RatAcc loop took longer to run than the FloatAcc loop. Floating-point numbers will always be faster than rational numbers because your computer includes dedicated hardware to do floating-point math, but in the interest of simplicity, this rational number library makes no special effort to narrow the speed gap.

The__add,__sub, __mul, and__div metamethods all handle binary operators, so they take two arguments. The __unm metamethod handles a unary operator, so it takes one argument:

> Rat = r"15/100"

> print(Rat) -- Will be reduced to lowest terms.

3/20

> print(-Rat)

-3/20

The __pow,__mod, and __len metamethods define the behavior of the ^ (exponentiation), % (modulo), and # (length) operators. All of these metamethods (the arithmetical ones,__concat, and__len) are not for redefining behavior that already exists, but for adding new behavior. Normally, you can’t use a table as an operand to an arithmetical operator or the concatenation operator, so you would use metamethods to define its behavior with these operators. But a table’s behavior with the length operator is already defined, so giving a table a __len metamethod has no effect, as shown here:

> T = {"one", "two", "three", [1000] = "one thousand"}

> print(table.maxn(T))

1000

> setmetatable(T, {

>> __len = function(T) return table.maxn(T) end})

> print(#T)

3

Lua does pay attention to a __len metamethod if it belongs to a value for which the length operator is not already defined (i.e., something other than a table or string). Giving metatables to values other than tables will be covered later in this chapter.

Relational Metamethods

You can use metamethods to make rational numbers work with equality and inequality operators (== and ~= ) and other relational operators (<, > , <= , and >= ). To do this, add appropriate__eq (equal) and __lt (less than) metamethods, like this:

local function Unm(A)

return MakeRat(-A.Numer, A.Denom)

end

local function Eq(A, B)

-- This and Lt work because MakeRat always makes sure the

-- denominator is positive and reduces to lowest terms:

return A.Numer == B.Numer and A.Denom == B.Denom

end

local function Lt(A, B)

local Diff = A - B

return Diff.Numer < 0

end

local function ToString(Rat)

return Rat.Numer .. "/" .. Rat.Denom

end

local RatMeta = {

__add = Add,

__sub = Sub,

__mul = Mul,

__div = Div,

__unm = Unm,

__eq = Eq,

__lt = Lt,

__tostring = ToString,

}

Remember, to see these changes you’ll need to set package.loaded.rational to nil, or use dofile(“rational.lua”) instead of require(“rational”), or restart Lua.

The__eq (equal) and__lt (less than) metamethods are enough to define all the relational operators. The inequality operator produces the opposite of the __eq result, and the greater-than operator produces the __lt result after swapping the operands. For example:

> print(r"20/100" == r"2/10")

true

> print(r"20/100" ~= r"2/10")

false

> print(r"7/16" < r"1/2")

true

> print(r"7/16" > r"1/2")

false

The less-than-or-equal and greater-than-or-equal operators are also defined in terms of __lt —for example, A<=B is defined as not (B < A) — unlessthere’s an__le (less than or equal) metamethod.

Rational numbers have what mathematicians call a total order. This means that if two rational numbers are not equal, then one is less than the other. If you want to compare things for which this is not true, you need to define __le in addition to __lt . For example, imagine you’re working with values that “represent shapes on a two-dimensional surface. A handy interpretation of <= would be “is contained within,” so that A<=B means “is every point in A also in B?" This is not a total order, because if A contains points not in B and B contains points not in A, then both A<=B and B<=A are false. Therefore, such shapes would need__le (is A contained in B),__lt (is A contained in B but not identical with it), and __eq (is A identical with B).

The __eq metamethod is only consulted when the values being compared meet all of these criteria:

· They are both tables, or they are both full userdatas (discussed later in this chapter).

· They are not the same value

· They have the same__eq metamethod.

If any one of these three conditions is not true, then the values are compared as if they had no __eq metamethods.

The rawequal function does a test for equality that bypasses any __eq metamethods that may be present. For example:

> Rat1, Rat2 = r"22/7", r"22/7"

> print(Rat1 == Rat2) -- __eq says they're equal,

true

> print(rawequal(Rat1, Rat2)) -- but they're different tables.

false

The __lt and __le metamethods are only consulted when the values being compared meet all of these criteria:

· They are the same type.

· They are neither numbers nor strings.

· They have the same __lt (__le) metamethod.

If any one of these three of these conditions is not true, then an error occurs.

These conditions (for when __eq,__lt, and __le are used) are stricter than those for the other operator metamethods because the relational operators themselves are stricter. For example, all of the nonrelational binary operators can, even without metamethods, be used with mixed types (strings and numbers), but using < ,> , <= , or >= to compare a string and a number is an error.

Indexing and Call Metamethods

There are metamethods that allow you to control what happens when something is indexed, when it is on the left side of an indexing assignment, or when it is called. In the following Try It Out, you use the indexing and indexing assignment metamethods when defining a table that considers two keys the same if they differ only in case.

Try It Out

Defining Case-Insensitive Tables

1. Define the MakelgnoreCase function, along with its helper functions and metatable, as follows:

do

-- Returns Val after (if it's a string) lowercasing it:

local function Lower(Val)

if type(Val) == "string" then

Val = string.lower(Val)

end

return Val

end

local Meta = {

__newindex = function(Tbl, Key, Val)

rawset(Tbl, Lower(Key), Val)

end,

__index = function(Tbl, Key)

return rawget(Tbl, Lower(Key))

end}

-- Returns a new table with a metatable that makes its

-- keys case-insensitive:

function MakeIgnoreCase()

return setmetatable({}, Meta)

end

end

2. Make a case-insensitive table, assign a value to its abc field, and then verify that the value is accessible from the ABC, Abc, and abc fields:

> Tbl = MakeIgnoreCase()

> Tbl.abc = 1

> print(Tbl.ABC, Tbl.Abc, Tbl.abc)

1 1 1

3. Assign a different value to the table’s Abc field, and then verify that the new value is accessible from the ABC, Abc, and abc fields:

> Tbl.Abc = 2

> print(Tbl.ABC, Tbl.Abc, Tbl.abc)

2 2 2

4. Assign another value to the table’s ABC field, and then verify that the new value is accessible from the ABC, Abc, and abc fields:

> Tbl.ABC = 3

> print(Tbl.ABC, Tbl.Abc, Tbl.abc)

3 3 3

5. Loop through the table, verifying that it only has one field, not three:

> for K, V in pairs(Tbl) do print(K, V) end

abc 3

How It Works

The metamethod for indexing assignment (setting a key to a value) is__newindex. When Lua sees Tbl.abc = 1, it checks to see whether Tbl already has an abc field. It doesn’t, but it does have a __newindex metamethod, so instead of putting the abc = 1 key-value pair directly into Tbl, Lua calls__newindex with three arguments: Tbl, "abc" , and 1 . After making sure that "abc" is all lowercase, __newindex needs to set Tbl.abc to 1. It can’t do this with a regular indexing assignment (that would cause infinite recursion), so it uses the function rawset, which sets a table’s key to a value but bypasses the table’s__newindex metamethod (if present).

The metamethod for indexing (getting a key’s value) is__index. When Lua sees print(Tbl.ABC) , it checks to see whether Tbl already has an ABC field. It doesn’t, but it does have an__index metamethod, so instead of giving a result of nil, Lua calls__index with two arguments: Tbl and "ABC" . After lower-casing "ABC",__index gets the value of Tbl.abc (using rawget to avoid infinite recursion). This value is what is printed.

The pairs loop shows that even after setting the "abc", "Abc", and "ABC" keys there’s only one key in the table:

> for K, V in pairs(Tbl) do print(K, V) end

abc 3

pairs and ipairs both bypass a table’s__index metamethod. That’s the desired behavior here, but if you want something different, you can write your own replacements for pairs, next (the iterator used by pairs), and ipairs. There’s an example of this later in this chapter.

The__newindex and __index methods are only called when the given key isn’t in the table. To demonstrate this, put the following debugging statements into them:

local Meta = {

__newindex = function(Tbl, Key, Val)

print("_newindex:", Tbl, Key, Val)

rawset(Tbl, Lower(Key), Val)

end,

__index = function(Tbl, Key)

print("_index:", Tbl, Key)

return rawget(Tbl, Lower(Key))

end}

Now you can see when the__newindex and__index methods are called:

> Tbl = MakeIgnoreCase()

> Tbl.abc = 1 -- Calls __newindex because there's no Tbl.abc.

__newindex: table: 0x4961e8 abc 1

> Tbl.abc = 2 -- Doesn't call __newindex since Tbl.abc exists.

> print(Tbl.ABC) -- Calls __index because there's no Tbl.ABC.

__index: table: 0x4961e8 ABC

2

> print(Tbl.abc) -- Doesn't call __index because Tbl.abc exists.

2

This is fine for the case-insensitive table, but in other situations, you might want __newindex and __index to always be called. The way to do this is to give the metamethods to a proxy table, which is a table that always stays empty. The next example uses this technique. It’s a table that remembers the order in which things were put into it.

Try It Out

Employing Ordered Tables

1. Save the following as orderedtbl.lua:

-- "Ordered tables" -- they remember the order in which

-- things are put into them.

local RealTbls = {} -- Keys: proxy tables; values: the real

-- tables.

local NumToKeys = {} -- Keys: proxy tables; values: arrays

-- of real tables' keys, in order.

local KeyToNums = {} -- Keys: proxy tables; values: tables

-- mapping real tables' keys to their (order) numbers.

-- Does the bookkeeping necessary to add a key and its order

-- to the real table (or update/delete a key):

local function __newindex(Proxy, Key, Val)

local RealTbl = RealTbls[Proxy]

local NumToKey = NumToKeys[Proxy]

local KeyToNum = KeyToNums[Proxy]

if RealTbl[Key] == nil then

-- This is a new key. Only add it if the value's

-- non-nil:

if Val ~= nil then

-- Record the value:

RealTbl[Key] = Val

-- Record the order:

NumToKey[#NumToKey + 1] = Key

KeyToNum[Key] = #NumToKey

end

else

-- This is an already existing key.

if Val ~= nil then

-- Record the new value:

RealTbl[Key] = Val

else

-- Delete it:

RealTbl[Key] = nil

local Num = KeyToNum[Key]

KeyToNum[Key] = nil

-- table.remove will shift down anything in NumToKey

-- that's higher than Num, but it needs to be done by

-- hand for KeyToNum:

if Num < #NumToKey then

for SomeKey, SomeNum in pairs(KeyToNum) do

if SomeNum > Num then

KeyToNum[SomeKey] = SomeNum - 1

end

end

end

table.remove(NumToKey, Num)

end

end

end

-- Returns Key's value in the real table:

local function __index(Proxy, Key)

return RealTbls[Proxy][Key]

end

-- An iterator that iterates through all the real table's

-- key-value pairs in the correct order:

local function __next(Proxy, PrevKey)

assert(type(Proxy) == "table", "bad argument to 'next'")

local RealTbl = RealTbls[Proxy]

local NumToKey = NumToKeys[Proxy]

local KeyToNum = KeyToNums[Proxy]

-- This will be 0 only if PrevKey is the seed value nil:

local PrevNum = KeyToNum[PrevKey] or 0

local Key = NumToKey[PrevNum + 1]

return Key, RealTbl[Key]

end

local RealIpairs = ipairs

-- Returns an ipairs iterator for the real table:

local function __ipairs(Proxy)

return RealIpairs(RealTbls[Proxy])

end

-- The metatable:

local OrderMeta = {__newindex = __newindex, __index = __index,

__next = __next, __ipairs = __ipairs}

local RealNext = next

-- A metatable-aware replacement for next:

function next(Tbl, Key)

local Meta = getmetatable(Tbl)

if Meta and Meta.__next then

return Meta.__next(Tbl, key)

else

return RealNext(Tbl, key)

end

end

-- A metatable-aware replacement for pairs:

function ipairs(Tbl)

-- The real pairs only needs to be replaced because it

-- returns its own copy of next unaffected by our

-- replacement.

assert(type(Tbl) == "table", "bad argument to 'pairs'")

return next, Tbl, nil

-- A metatable-aware replacement for pairs:

function ipairs(Tbl)

local Meta = getmetatable(Tbl)

if Meta and Meta.__ipairs then

return Meta.__ipairs(Tbl)

else

return RealIpairs(Tbl)

end

end

-- Returns a table that remembers the order in which keys

-- are added to it:

function MakeOrderedTbl()

local RealTbl, Proxy = {}, {}

RealTbls[Proxy] = RealTbl

-- The following two tables are two complementary ways of

-- recording the order that keys are added in:

NumToKeys[Proxy] = {}

KeyToNums[Proxy] = {}

return setmetatable(Proxy, OrderMeta)

end

2. Require the file, make an ordered table, and give it some key-value pairs:

> require("orderedtbl")

> Tbl = MakeOrderedTbl()

> Tbl.John = "rhythm guitar"

> Tbl.Paul = "bass guitar"

> Tbl.George = "lead guitar" —

> Tbl.Ringo = "drumkit"

3. Verify that the pairs are looped through in the same order in which they were added:

> for Name, Instr in pairs(Tbl) do print(Name, Instr) end

John rhythm guitar

Paul bass guitar

George lead guitar

Ringo drumkit

4. Change one key's value and verify that the pairs are still in the same order:

> Tbl.George = "lead guitar, sitar"

> for Name, Instr in pairs(Tbl) do print(Name, Instr) end

John rhythm guitar

Paul bass guitar

George lead guitar, sitar

Ringo drumkit

5. Remove one key and verify that the remaining pairs are still in the same order:

> Tbl.Paul = nil -- I buried Paul.

> for Name, Instr in pairs(Tbl) do print(Name, Instr) end

John rhythm guitar

George lead guitar, sitar

Ringo drumkit

How It Works

pairs iterates through the Tbl pairs in the same order as they were added. If you modify a key’s value, the order is not changed, and you can delete a key by setting its value to nil. Most of the hard work is done by__newindex. Because the proxy table is always empty, every attempt to do indexing assignment on it is routed through__newindex, which keeps the following three tables for each proxy table:

· RealTbls[Proxy]: The real table that corresponds to the proxy table — it contains all the key-value pairs, in the usual order.

· NumToKeys[Proxy]: An array of the RealTbls[Proxy] keys, in order of addition.

· KeyToNums[Proxy] : A mapping of all the keys to their positions in NumToKeys[Proxy] .

RealTbls[Proxy] is necessary for retrieving a value given its key, NumToKeys[Proxy] is necessary for looping through the keys in order, and KeyToNums[Proxy] is necessary for finding a given key’s position in NumToKeys[Proxy] (for iteration, and also for modification or deletion of already existing pairs).

The__next metamethod is an iterator that uses these tables to iterate in the desired order. Lua itself knows nothing about a__next metamethod—the only thing that knows about it is orderedtbl.lua ’s replacement for next (which, in turn, is what the pairs replacement returns). Similarly, Lua itself neither defines nor looks for an__ipairs metamethod—the only thing that knows about it is the ipairs replacement. (There’s nothing that requires__next and__ipairs to start with two underscores, but it makes it easier to see that they’re metamethods.) These next and ipairs replacements still work with regular tables. If they don’t find the metamethods they look for, they use the real next and ipairs (stored as upvalues).

There’s a problem with orderedtbl.lua. The tables RealTbls, NumToKeys, and KeyToNums get bigger (consuming more memory) every time MakeOrderedTbl is called, but they never get smaller, even when the table returned by MakeOrderedTbl is no longer in use. This is called a memory leak, and a fix for it, using the __mode metamethod, is shown in Chapter 10.

Both__newindex and__index can be tables. As such, you can use them instead of the table being indexed like this:

> TblA, TblB = {}, {}

> setmetatable(TblB, {__newindex = TblA, __index = TblA})

> TblB[1] = "one"

> print(TblB[1])

one

> -- It's not really in TblB:

> print(rawget(TblB, 1))

nil

> -- It's really in TblA:

> print(TblA[1])

one

If__newindex and/or__index are tables that themselves have indexing metamethods, these are used. That means that one table can get some of its values from another table, which gets some of its values from another, and so on. This is called inheritance and is illustrated in the following example:

> TblA = {"one", "two", "three", "four", "five", "six"}

> TblB = {[3] = "III", [4] = "IV", [5] = "V", [6] = "VI"}

> -- Make TblB inherit from TblA:

> setmetatable(TblB, {__index = TblA})

> TblC = {[5] = "*****", [6] = "******"}

> -- Make TblC inherit from TblB:

> setmetatable(TblC, {__index = TblB})

> -- TblC thus indirectly inherits from TblA:

> for I = 1, 6 do print(I, TblC[I]) end

1 one

2 two

3 III

4 IV

5 *****

6 ******

This can twist and turn through up to 100 tables (or more if you recompile Lua with a higher value for MAXTAGLOOP).

Other than__tostring (and the__next and__ipairs metamethods defined in orderedtbl.lua), all of the metamethods you’ve seen so far are syntactical metamethods, which means they correspond to syntactical constructs (all of the operators, plus indexing and indexing assignment). Another syntactical metamethod yet to be covered is__call. This metamethod controls what happens when something other than a function is called. For instance, if a table has the__call method given in the following example, then calling it (as though it were a function) returns true if all the arguments are keys in it with true values, and false otherwise:

> CallMeta = {

>> -- Are all of the arguments true-valued keys in Tbl?

>> __call = function(Tbl, ...)

>> for I = 1, select("#", ...) do

>> if not Tbl[select(I, ...)] then

>> return false -- NON-STRUCTURED EXIT: FOUND A KEY

>> -- WITH A NON-TRUE VALUE.

>> end

>> end

>> return true

>> end}

> Tbl = {John = "rhythm guitar", Paul = "bass guitar",

>> George = "lead guitar", Ringo = "drumkit"}

> setmetatable(Tbl, CallMeta)

> -- Same truth-value as (Tbl.Paul and Tbl.John):

> print(Tbl("Paul", "John"))

true

> -- Same truth-value as (Tbl.Ringo and Tbl.Mick and Tbl.Keith):

> print(Tbl("Ringo", "Mick", "Keith"))

false

Non-Tables with Metamethods

As mentioned earlier, all datatypes (not just tables) can have metatables. The setmetatable function only works with tables. The metatables of other types must be set either from C or with the function debug.setmetatable. Unlike tables, most other types have per-type metatables. This means thatgiving a metatable to a number gives it to all numbers. In the following example, a_len metamethod is given to numbers (where making the math.abs argument positive if it’s negative is called getting the argument’s absolute value):

> print(#99)

stdin:1: attempt to get length of a number value

stack traceback:

stdin:1: in main chunk

[C]: ?

> debug.setmetatable(1, {__len = math.abs})

> print(#99)

99

> print(#-99)

99

Unlike setmetatable, debug.setmetatable returns a Boolean: ">true if it succeeded, false otherwise.

By default, types have no metatable, except for strings, whose__index metamethod is the string table, as shown here:

> StringMeta = getmetatable("")

> for Name, Meth in pairs(StringMeta) do print(Name, Meth)

> end

__index table: 0x8066f30

> print(StringMeta.__index == string)

true

This means that all the functions of the string library are available from any string. For example:

> SomeString = "some string"

> print(SomeString.char(65, 66, 67))

ABC

More usefully, it means that any string function that takes a string as its first argument (that’s almost all of them) can be used in object-oriented style, with the string as the object and the function as its method. For example:

> Greet = "Hello"

> print(Greet:upper())

HELLO

> FrmtStr = '<a href="%s">%s</a>'

> print(FrmtStr:format("http://luaforge.net", "LuaForge"))

<a href="http://luaforge.net">LuaForge</a>'.>

A literal string can be used in this way, but only if it's surrounded in parentheses like this:

> print(("a"):rep(5))

aaaaa

There’s one more type whose behavior with metatables needs special clarification. The userdata type will be covered in Chapter 13, but it’s basically a way of exposing a C datatype (or a datatype of some other language) to Lua. It comes in two varieties: full userdatas and light userdatas. Both varieties are identified by the type function as "userdata", but their behavior with metatables is different. Metatable-wise, full userdatas are similar to tables, and light userdatas are similar to nontables:

· Each full userdata can have its own metatable.

· Giving a metatable to a light userdata gives it to all light userdatas.

In Lua 5.0, only tables and full userdatas could have metatables.

Non-Syntactical Metamethods

As mentioned previously,__tostring is a non-syntactical metamethod, which means that it doesn’t control the behavior of an operator or other syntactical construct. Instead, it controls the behavior of the function tostring, which will always use a value’s__tostring metamethod, if present, to convert that value to a string. In this way,__tostring is no different from__next and__ipairs, except that it is consulted by a built-in Lua function and they are not.

One other metamethod consulted by a built-in function (or two functions, in this case) is__metatable. If getmetatable sees that a value has a__metatable metamethod, it returns that metamethod instead of the metatable itself. If setmetatable sees that a value has a__metatable metamethod, it triggers an error rather than setting the metatable. A__metatable metamethod can be any type (except nil, of course).

This is most commonly used to protect metatables from tampering. Here’s an example:

> T = setmetatable({}, { metatable = false})

> print(getmetatable(T))

false

> T = setmetatable(T, nil)

stdin:1: cannot change a protected metatable

stack traceback:

[C]: in function 'setmetatable'

stdin:1: in main chunk

[C]: ?

debug.setmetatable and debug.getmetatable both ignore__metatable.

The two remaining nonsyntactical metamethods defined by Lua both control garbage-collection, which is the automatic process of reclaiming no-longer-used memory for reuse.

The__mode metamethod is for tables. It is explained in Chapter 10 (which contains a fix for the problem with orderedtbl.lua mentioned earlier).

The__gc metamethod is for full userdatas. It is explained in Chapter 13.

Metamethod Applicability

You have seen a number of cases where metamethods are ignored. The following chart lists all the metamethods defined by Lua’s core and its built-in libraries and shows which situations they are applicable in.

8-t1

Summary

In this chapter, you were introduced to the concept of metamethods and to all the metamethods defined by Lua’s core and its built-in libraries. Here are some highlights of what you’ve learned:

· You can use metamethods to give new behavior to all of the operators except for the Boolean ones, and to table indexing, indexing assignment, and calls.

· Nonrelational binary metamethods are retrieved from the left operand or from the right operand if the left one doesn’t have the appropriate metamethod.

· You can use proxy tables to increase the power of__newindex and__index.

· __newindex and__index can themselves be tables, and they can use their own indexing metamethods (if they exist) to implement inheritance.

· You can also use metamethods to control how values are converted to strings and how they are garbage-collected.

· Metamethods serve mainly to define new behavior, not to redefine existing behavior. Even the exceptions to that rule are limited, which is why__eq only works with tables or full userdatas, and why__newindex and__index only work with nonexistent keys.

The next chapter explains something new to add to your arsenal of control flow tools (alongside control structures and functions). But before moving on, this chapter concludes with a couple of exercises (answers are in the appendix).

Exercises

1. Write a metamethod that makes the unary minus operator give a reversed copy of an array:

> Arr = setmetatable({"one", "two", "three"}, Meta)

> for I, Val in ipairs(-Arr) do print(I, Val) end

1 three

2 two

3 one

2. Write a function that makes the table given to it read-only:

> Tbl = {"Hello"}

> Protect(Tbl)

> print(Tbl[1])

Hello

> Tbl[2] = "Goodbye"

stdin:14: attempt to assign to a protected table

stack traceback:

[C]: in function 'error'

stdin:14: in function <stdin:13>

stdin:1: in main chunk

[C]: ?

> Tbl[1] = "Goodbye"

stdin:14: attempt to assign to a protected table

stack traceback:

[C]: in function 'error'

stdin:14: in function <stdin:13>

stdin:1: in main chunk

[C]: ?

> setmetatable(Tbl, nil)

stdin:1: cannot change a protected metatable

stack traceback:

[C]: in function 'setmetatable'

stdin:1: in main chunk

[C]: ?