lua-user Inheritance Tutorial

 

Metamethods Tutorial

lua-users home
wiki

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.
Note that the metamethods are added to the metatable 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
We attach an __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
Here we just call the Lua 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

lua-users home
wiki

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.
We create a table 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
And reversed:
> 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


FindPage · RecentChanges · preferences
edit · history
Last edited October 19, 2008 8:00 am GMT (diff)


Inheritance Tutorial

lua-users home
wiki

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)

        
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值