H. Conrad Cunningham
5 October 2016
Acknowledgements: These slides are adapted from slide set 10 for the course Programming in Lua created by Fabio Mascarenhas, Federal University of Rio de Janeiro, Brazil. It was presented at Nankai University, P. R. China, in July 2013.
Advisory: The HTML version of this document may require use of a browser that supports the display of MathML. A good choice as of September 2016 is a recent version of Firefox from Mozilla.
A metatable modifies behavior of another table
enable use of arithmetic, concatenation, relational operators
override behavior of ==
, ~=
, and #
operators
override behavior of builtins tostring
, pairs
, ipairs
give values for missing fields, intercept creation of new fields
enable calling table as function
Each table can have own metatable -- own behavior
Several tables can share a metatable -- similar behaviors
setmetatable
changes metatable of table, returns table
getmetatable
returns metatable of table (nil
if none)
Bad style: Changing metatable after assigning it to table (gives poor performance)
Set metamethods to specify effect of metatable
Associate function with specially named field
Lua 5.2 has 19 metamethods -- names begin with two underscores
__add, __sub, __mul, __div, __mod, __pow, __unm,
__concat, __len, __eq, __lt, __le, __index, __newindex,
__call, __tostring, __ipairs, __pairs, __gc
Lua 5.3 deprecates __ipairs
, adds new integer/bit metamethods
Set functions as metamethod values
Can also use tables for __index
, and __newindex
Implement single inheritance using table for __index
Augment earlier complex number module with operations:
Addition of complex and real numbers
Structural comparison for equality
Modulus with #
Pretty-printing with tostring
Create table private to module as metatable for each complex number
local mt = {}
local function new(r, i)
return setmetatable({ real = r or 0, im = i or 0 }, mt)
end
Define query accessor for complex numbers
local function is_complex(v)
return getmetatable(v) == mt
end
+
with __add
(1)Recall previously defined complex.add
function
local function add(c1, c2)
return new(c1.real + c2.real, c1.im + c2.im)
end
Define metamethod __add
to reference add
, now +
works for complex
mt.__add = add
> c1 = complex.new(2, 3)
> c2 = complex.new(1, 5)
> print(complex.tostring(c1 + c2))
3+8i
+
with __add
(2)Try adding real to complex
> c3 = c1 + 5
.\complex.lua:20: attempt to index local 'c2' (a number value)
stack traceback:
.\complex.lua:20: in function '__add'
stdin:1: in main chunk
[C]: in ?
What is happening?
Calls __add
metamethod of complex number!
If left operand has __add
metamethod, Lua calls it -- modify complex.add
local function add(c1, c2)
if not is_complex(c2) then
return new(c1.real + c2, c1.im)
end
return new(c1.real + c2.real, c1.im + c2.im)
end
Now adding real to complex works:
> c1 = complex.new(2, 3)
> c3 = c1 + 5
> print(complex.tostring(c3))
7+3i
What about adding a complex to a real?
> c3 = 5 + c1
.\complex.lua:20: attempt to index local 'c1' (a number value)
stack traceback:
.\complex.lua:20: in function '__add'
stdin:1: in main chunk
[C]: in ?
What is happening?
Again calls __add
metamethod of complex number!
If left operand has no __add
metamethod but second has, Lua calls it -- modify complex.add
local function add(c1, c2)
if not is_complex(c1) then
return new(c2.real + c1, c2.im)
end
if not is_complex(c2) then
return new(c1.real + c2, c1.im)
end
return new(c1.real + c2.real, c1.im + c2.im)
end
Now adding complex to real works
> c3 = 5 + c1
> print(complex.tostring(c3))
7+3i
Metamethod __eq
controls both ==
and ~=
Slightly different rule from arithmetic -- calls metamethod if both operands have same metatable
local function eq(c1, c2)
return (c1.real == c2.real) and (c1.im == c2.im)
end
mt.__eq = eq
> c1 = complex.new(1, 2)
> c2 = complex.new(2, 3)
> c3 = complex.new(3, 5)
> print(c1 + c2 == c3)
true
> print(c1 ~= c2)
true
Complex/real comparison false
even if imaginary part 0
#
and tostring
Metamethods __len
and __tostring
work similarly -- receive table, return result
local function modulus(c)
return math.sqrt(c.real * c.real + c.im * c.im)
end
mt.__len = modulus
local function tos(c)
return tostring(c.real) .. "+" .. tostring(c.im) .. "i"
end
mt.__tostring = tos
> c1 = complex.new(3, 4)
> print(#c1)
5
> print(tostring(c1))
3+4i
> print(c1) -- print uses tostring
3+4i
Metamethod __lt
for <
works like arithmetic metamethods
For >
Lua uses __lt
with operands reversed
Metamethod __le
for <=
works like arithmetic metamethods
But <=
uses __lt
if __le
not available, with operands reversed and negating
Why two metamethods? -- for partial orders
local function le(c1, c2)
if not is_complex(c1) then
return (c1 <= c2.real) and (c2.im >= 0)
end
if not is_complex(c2) then
return (c1.real <= c2) and (c2.im <= 0)
end
return (c1.real <= c2.real) and (c1.im <= c2.im)
end
mt.__le = le
local function lt(c1, c2)
return c1 <= c2 and not (c2 <= c1)
end
mt.__lt = lt
__index
and __newindex
Calls __index
metamethod when indexing with undefined key -- pass table and key -- return result
Calls __newindex
metamethod when assigning to undefined key -- passes table, key, and value
Use with empty table as proxy for another table -- catch indexing and/or assignment operations, delegate to other table
Both __index
and __newindex
can be tables instead of functions -- redo indexing operation on the table
local mt = {} > proxy = require "proxy"
> t = proxy.track({})
function mt.__index(t, k) > t.foo = 5
t.__READS = t.__READS + 1 > print(t.foo)
return t.__TABLE[k] 5
end > t.foo = 2
> print(t.__READS, t.__WRITES)
function mt.__newindex(t, k, v) 1 2
t.__WRITES = t.__WRITES + 1
t.__TABLE[k] = v
end
local function track(t)
local proxy = { __TABLE = t, __READS = 0, __WRITES = 0}
return setmetatable(proxy, mt)
end
return { track = track }
We can try to work around the limitation of __eq
so we can have complex.new(2,0) == 2
by making complex.new
return a real if the imaginary part is 0.
Which operations will continue to work with this change, and which will not work anymore?
We try to work around the limitation of __eq
so we can have complex.new(2,0) == 2
by making complex.new
return a real if the imaginary part is 0.
Which operations will continue to work with this change, and which will not work anymore?
It breaks #
.