Metamethods Tutorial |
|
This is a brief introduction to the concepts of Lua metamethods. Once you have read this you might want to read further examples of metamethod use, for example to implement classes for object oriented programming, in ObjectOrientationTutorial and LuaClassesWithMetatable. Section 2.8 of the Reference Manual [1] covers Metatables.
Metamethods
Lua has a powerful extension mechanism which allows you to overload certain operations on Lua objects. Only tables and userdata objects can use this functionality. Each overloaded object has a metatable of function metamethods associated with it; these are called when appropriate, just like operator overloads in C++. Unlike C++ though you can modify the metamethods associated with an object at runtime.
The metatable is a regular Lua table containing a set of metamethods, which are associated with events in Lua. Events occur when Lua executes certain operations, like addition, string concatenation, comparisons etc. Metamethods are regular Lua functions which are called when a specific event occurs. The events have names like "add" and "concat" (see manual section 2.8) which correspond with metamethods with names like "__add
" and "__concat
". In this case to add or concatenate two Lua objects. These are defined as usual in a table in key-value pairs.
Metatables
We use the function setmetatable()
to associate a metatable with an appropriate object. Let's have an example:
-
> x = { value = 3 } -- our object > > mt = { __add = function (a, b) >> return { value = a.value + b.value } >> end } -- metatable containing event callbacks > > a = x+x -- without a metatable this is just a regular table stdin:1: attempt to perform arithmetic on global `x' (a table value) stack traceback: stdin:1: in main chunk [C]: ? > > setmetatable(x, mt) -- attach our metamethods to our object > > a = x + x -- try again > print(a.value) 6
In the above example we create a table called x
containing a value, 3. We create a metatable, containing the event overloads we would like to attach to the table, in mt
. We are overloading the "add" event here; notice how the function receives two arguments because "add" is a binary operation. We attach the metatable mt
to the table x
and when we apply the addition operator to x (in a = x+x
) we can see that a
contains the results of the __add
metamethod.
Notice however that a
is not an instance of our "class", it is just a plain table with no metamethod associated with it.
-
> b = a+a stdin:1: attempt to perform arithmetic on global `a' (a table value) stack traceback: stdin:1: in main chunk [C]: ?
However, as long as an object with the appropriate metamethod is part of the event operation, Lua will behave properly. It does not matter which side of the +
operator our class is as Lua will resolve this.
-
> b = a+x > print(b.value) 9
We can retrieve the metatable from an object with metamethods using getmetatable(object)
:
-
> = getmetatable(x) table: 0035BC98 > = getmetatable(x).__add -- get the "__add" metamethod function: 0035C040
Note, we could attach metamethods to the returned "class" in the above example by doing the following:
-
> x = { value = 3 } > > mt = { __add = function (a, b) >> return setmetatable({ value = a.value + b.value }, mt) >> end } -- metatable > > setmetatable(x, mt) > > a = x+x > print(a.value) -- as before 6 > b = a+a > print(b.value) -- no error this time as "a" is the same "class" as "x" 12
A note on "classes"
Without the metatable attached, the table x
could be likened to a structure (struct
) in C. With the ability to overload operations that occur on the table we might say that the table x
become analogous to a class instance in C++. The reason why we're being cautious to call this a class is that we can do things with this "class instance" that we cannot do in C++, e.g. attach more event overloads, or change overloads at runtime, or manipulate the contents of the "class" dynamically (e.g. add or remove elements).
String class example
The following is a very simple string class. We define a constructor class String
to return a table, containing our string, with the metatable attached. We define a couple of functions to concatenate strings together and multiply strings.
-
> mt = {} -- metatable > > function String(s) >> return setmetatable({ value = s or '' }, mt) >> end > > function mt.__add(a, b) >> return String(a.value .. b.value) >> end > > function mt.__mul(a, b) >> return String(string.rep(a.value, b)) >> end > > s = String('hello ') > > print(s.value) hello > print( (s + String('Lua user')).value ) -- concat 2 String instances hello Lua user > print( (s*3).value ) hello hello hello > print( (((s + String('Lua user.')))*2).value ) -- use both metamethods hello Lua user.hello Lua user.
mt
after it has been referenced by the
String()
constructor. The metatable is dynamic and can be altered even after instances of the String have been created. When metamethods are added or altered this will affect all objects (in this case Strings) with the same metatable instantaneously.
More events
The following are notes on other of the metamethod events that Lua handles.
__index
Two interesting metamethods are __index
and __newindex
. When we use the +
operator Lua automatically associates this with the __add
event. If the key that we are looking for is not a built in one, we can use the __index
event to catch look ups. This event is called whenever we are looking for a key associated with an object (and it's not one of the built in ones). For example, what if we want to get the length of a string in the above String class example? We could call the string length function, but what if we wanted to treat the length as a number entry in the String instance? Following on from the previous example:
-
> print(s.length) -- no such key in the String table class nil > mt.__index = function (t,key) >> if key == 'length' then return string.len(t.value) end >> end > print(s.length) -- new __index event calls the above function 6
__index
event to the String class's metatable. The index metamethod looks for the key "length" and returns the length of the String.
__newindex
Now suppose that we want to make sure that the "value" member remains the only field in the String class. We can do this using the __newindex
metamethod. This method is called whenever we try to create a new key in the table, not look up an existing one. E.g.:
-
> print(s.value, s.length) hello 6 > mt.__newindex = function (t, key, value) >> error('sorry, the only member is "value"') >> end > s.banana = 'yellow' stdin:2: sorry, the only member is "value" stack traceback: [C]: in function `error' stdin:2: in function <stdin:1> stdin:1: in main chunk [C]: ? > s.value = 'abc' -- no error here
error()
function to create an error whenever the
__newindex
event is invoked.
Another use of __newindex
is to support derived values. Suppose I have an "operator" object, op
, that has a freq
field and a ratio
field. I want the op.freq
value to be derived by the following simple formula: op.freq == freq * op.ratio
; the operator frequency is the operator ratio times the global frequency. One way to do this would be using __index
in a manner to that given above in the section for __index
. If op.freq
is accessed frequently this might be too slow, so an alternative is to change op.freq
whenever op.ratio
is changed. Here's an extract from op
's metatable:
-
opmeta = { __newindex = function(op, k,v) if k == 'ratio' then op.freq = v * freq op.shadow[k] = v else rawset(op,k,v) end, __index = function(op, k) return rawget(op, 'shadow')[k] end }
Note how __newindex
catches attempts to write to op.ratio
and modifies op.freq
. In order that we can retrieve the operator ratio via op.ratio
we must store it somewhere, but we can't store it directly in op.ratio
because then __newindex
would not get called for subsequent attempts to set the ratio (recall that the __newindex
method only gets called when the key that is being set in the table has no existing value), so the ratio is stored in op.shadow.ratio
. The __index
method makes sure that lookups to unused slots in op
get redirected to op.shadow
.
__metatable
__metatable is for protecting metatables. If you do not want a program to change the contents of a metatable, you set its __metatable field. With that, the program cannot access the metatable (and therefore cannot change it).FindPage · RecentChanges · preferences
edit · history
Last edited May 27, 2007 8:15 am GMT (diff)
Object Orientation Tutorial |
|
Representation of classes in Lua
Lua has no built-in notion of "classes" for use in object-oriented programming. However, we can simulate many features of the classes of other languages by using just the tables and metamethods discussed previously (MetamethodsTutorial). To declare a class in Lua, we will need (1) a constructor (String:new
below), (2) a class method table (String
), and (3) a ''class metatable' (mt
).
The method table is an ordinary table containing functions. These functions constitute the methods of the class. The constructor is a function which, when called, sets up a new table (our instance object) and attaches the class metatable to it. Finally, the metatable is simply a metatable which redirects unrecognized events to the class method table (as well as possibly handling events itself).
The following is a rearranged version of the String example from the MetamethodsTutorial:
-
> String = {} > mt = {} > > function String:new(s) >> return setmetatable({ value = s or '' }, mt) >> end > > function String:print() >> print(self.value) >> end > > mt.__add = function (a,b) return String:new(a.value..b.value) end > mt.__mul = function (a,b) return String:new(string.rep(a.value, b)) end > mt.__index = String -- redirect queries to the String table > > s = String:new('hello ') > s:print() hello > a = ((String:new('hello ') + String:new('Lua user. '))*2) > a:print() hello Lua user. hello Lua user.
String
to hold our String class methods. Instead of having a functional interface (e.g.
String.print(s)
) we would like to call the object, e.g.
s:print()
.
It is also possible to eliminate mt
and use String
instead for all cases where mt
is used.
Method calling conventions
Note in the example that we use the :
operator to declare and call the class methods. This is syntactic sugar for String.print(s)
, or s.print(s)
, i.e. the colon makes Lua put the object we are calling as the first parameter in the function call. The following all have the same result by different methods:
-
> s.print(s) -- use __index metamethod but no sugar hello > s:print() -- use __index metamethod and sugar hello > String.print(s) -- call String directly, no metamethod or sugar hello
Method declarations
Likewise there is syntactic sugar for method declarations. Regardless how it is declared, a method expects that first argument passed in is the object to be acted on. If a dot is used (i.e. t.foo(self, args)
) we declare self ourselves. If a colon is used (i.e. t:foo(args)
) self
will be declared automatically for us.
-
> t = {} > function t.foo(self,x) print(self,x) end -- no sugar, explicit self > function t:foo2(x) print(self,x) end -- sugar and magic self > > t.foo(t,1) -- is the same as... table: 0035D830 1 > t:foo2(1) -- shorthand for above table: 0035D830 1
-
> t:foo(1) -- the same as... table: 0035D830 1 > t.foo2(t,1) table: 0035D830 1
Notes on the convention
The colon calling and method declaration styles are shortcuts, not a rigid style of programming; As the examples show, you can mix and match styles.
Explicitly declaring self
If you don't like the automatic appearance of the self
argument in function declarations you might choose the following style, where self
is explicitly declared:
-
> foo = { value = 0 } > function foo.add(self, x) self.value = self.value+x end > function foo.print(self) print (self.value) end > foo:print() 0 > foo:add(123) > foo:add(99) > foo:print() 222
Defining functions in a table constructor
Please note that if you declare your class methods in a table constructor you'll have to declare self
explicitly as using the colon is not an option.
-
> foo = { >> value = 0, >> print = function(self) print(self.value) end, >> add = function(self, x) self.value = self.value + x end >> } > > foo:print() 0 > foo:add(77) > foo:print() 77
See Also
- ObjectOrientedProgramming - links and discussions on using object oriented programming techniques in Lua.
FindPage · RecentChanges · preferences
edit · history
Last edited October 19, 2008 8:00 am GMT (diff)
Inheritance Tutorial |
|
This tutorial demonstrates a technique for implementing object oriented inheritance in Lua. Before continuing it is recommended that you familiarize yourself with ObjectOrientationTutorial and MetamethodsTutorial.
Simple Classes
The following example implements a class with no inheritance:
-
SimpleClass = {} SimpleClass_mt = { __index = SimpleClass } -- This function creates a new instance of SimpleClass -- function SimpleClass:create() local new_inst = {} -- the new instance setmetatable( new_inst, SimpleClass_mt ) -- all instances share the same metatable return new_inst end -- Here are some functions (methods) for SimpleClass: function SimpleClass:className() print( "SimpleClass" ) end function SimpleClass:doSomething() print( "Doing something" ) end
In the above example, SimpleClass
represents a table that holds all of our class's methods, like a class declaration. SimpleClass_mt
is the metatable we will attach to each class instance we create. The function SimpleClass:create()
creates an instance of our class SimpleClass
. Construction of a class instance involves creating an empty table and then attaching our SimpleClass
metamethods to it. The result of attaching the metamethods is that the new instance looks to the metatable we attached for its customised behaviour.
Method invocations on the instance will trigger the "index" event on the instance, causing a lookup on the "__index
" member of the instance's metatable. The __index
member is simply a reference to SimpleClass
. Therefore, method invocations on the instance will cause a lookup in the SimpleClass
table.
Here is an example:
-
> simple = SimpleClass:create() > > simple:className() SimpleClass > > simple:doSomething() Doing something
Implementing Inheritance
Now we want to create a new class SubClass
that inherits and, optionally, overrides functions from SimpleClass
.
-
-- Create a new class that inherits from a base class -- function inheritsFrom( baseClass ) -- The following lines are equivalent to the SimpleClass example: -- Create the table and metatable representing the class. local new_class = {} local class_mt = { __index = new_class } -- Note that this function uses class_mt as an upvalue, so every instance -- of the class will share the same metatable. -- function new_class:create() local newinst = {} setmetatable( newinst, class_mt ) return newinst end -- The following is the key to implementing inheritance: -- The __index member of the new class's metatable references the -- base class. This implies that all methods of the base class will -- be exposed to the sub-class, and that the sub-class can override -- any of these methods. -- if baseClass then setmetatable( new_class, { __index = baseClass } ) end return new_class end
The function inheritsFrom(baseClass)
takes a single argument, the class declaration we want to inherit from. The function returns a class declaration which we can then tailor. new_class
is the new class declaration to be returned. The nested function new_class:create()
is part of the class declaration returned and will create new instances of the sub class we are creating. This function creates a newinst
table which uses our new class table to hold it's methods. The new class table in turn looks in the baseClass
if it cannot find a method we require, and thus we inherit it's methods.
Inheritance Example
Building on SimpleClass
we now create a class called SubClass
that inherits from SimpleClass
and overrides className()
:
-
> -- Create a new class that inherits from SimpleClass > SubClass = inheritsFrom( SimpleClass ) > > -- override className() function > function SubClass:className() print( "SubClass" ) end > > -- Create an instance of SimpleClass > simple = SimpleClass:create() > > simple:className() SimpleClass > > simple:doSomething() Doing something > > -- Create an instance of SubClass > sub = SubClass:create() > > sub:className() -- Call overridden method SubClass > > sub:doSomething() -- Call base class method Doing something >
OO Properties
We can now expand on our inheritance structure and add features that are common in other languages, like access to a class's super class and a isa()
method that provides type id functionality:
-
-- A new inheritsFrom() function -- function inheritsFrom( baseClass ) local new_class = {} local class_mt = { __index = new_class } function new_class:create() local newinst = {} setmetatable( newinst, class_mt ) return newinst end if nil ~= baseClass then setmetatable( new_class, { __index = baseClass } ) end -- Implementation of additional OO properties starts here -- -- Return the class object of the instance function new_class:class() return new_class end -- Return the super class object of the instance function new_class:superClass() return baseClass end -- Return true if the caller is an instance of theClass function new_class:isa( theClass ) local b_isa = false local cur_class = new_class while ( nil ~= cur_class ) and ( false == b_isa ) do if cur_class == theClass then b_isa = true else cur_class = cur_class:superClass() end end return b_isa end return new_class end
And, an example of usage:
-
> SimpleClass = inheritsFrom( nil ) -- pass nil because SimpleClass has no super class > > SubClass = inheritsFrom( SimpleClass ) > > FinalClass = inheritsFrom( SubClass ) > > sub = SubClass:create() > fc = FinalClass:create() > > print( fc:isa( SubClass ) ) true > print( fc:isa( FinalClass ) ) true > print( sub:isa( SubClass ) ) true > print( sub:isa( FinalClass ) ) false
Contributors: KevinBaca
See Also
FindPage · RecentChanges · preferences
edit · history
Last edited October 18, 2008 10:34 pm GMT (diff)