A table in Lua is an object in more than one sense. Like objects, tables have a state. Like objects, tables have an identity (a self) that is independent of their values; specifically, two objects (tables) with the same value are different objects, whereas an object can have different values at different times. Like objects, tables have a life cycle that is independent of who created them or where they were created.
Objects have their own operations. Tables also can have operations, as in the following fragment:
Account = {balance = 0} function Account.withdraw (v) Account.balance = Account.balance - v end
This definition creates a new function and stores it in
field withdraw
of the object Account
.
Then, we can call it like here:
Account.withdraw(100.00)
This kind of function is almost
what we call a method.
However, the use of the global name Account
inside the function
is a horrible programming practice.
First, this function will work only for this particular object.
Second, even for this particular object,
the function will work only
as long as the object is stored in that particular global variable.
If we change the object’s name,
withdraw
does not work any more:
a, Account = Account, nil a.withdraw(100.00) -- ERROR!
Such behavior violates the principle that objects have independent life cycles.
A more principled approach is to operate on the receiver of the operation. For that, our method would need an extra parameter with the value of the receiver. This parameter usually has the name self or this:
function Account.withdraw (self, v) self.balance = self.balance - v end
Now, when we call the method we have to specify the object that it has to operate on:
a1 = Account; Account = nil ... a1.withdraw(a1, 100.00) -- OK
With the use of a self parameter, we can use the same method for many objects:
a2 = {balance=0, withdraw = Account.withdraw} ... a2.withdraw(a2, 260.00)
This use of a self parameter is a central point in any
object-oriented language.
Most OO languages have this mechanism hidden
from the programmer,
so that she does not have to declare this parameter
(although she still can use the name self or
this inside a method).
Lua also can hide this parameter,
with the colon operator.
Using it,
we can rewrite the previous method call as
a2:withdraw(260.00)
and the previous
definition as here:
function Account:withdraw (v) self.balance = self.balance - v end
The effect of the colon is to add an extra argument in a method call and to add an extra hidden parameter in a method definition. The colon is only a syntactic facility, although a convenient one; there is nothing really new here. We can define a function with the dot syntax and call it with the colon syntax, or vice-versa, as long as we handle the extra parameter correctly:
Account = { balance=0, withdraw = function (self, v) self.balance = self.balance - v end } function Account:deposit (v) self.balance = self.balance + v end Account.deposit(Account, 200.00) Account:withdraw(100.00)
So far, our objects have an identity, state, and operations on this state. They still lack a class system, inheritance, and privacy. Let us tackle the first problem: how can we create several objects with similar behavior? Specifically, how can we create several accounts?
Most object-oriented languages offer the concept of class, which works as a mold for the creation of objects. In such languages, each object is an instance of a specific class. Lua does not have the concept of class; the concept of metatables is somewhat similar, but using it as a class would not lead us too far. Instead, we can emulate classes in Lua following the lead from prototype-based languages like Self. (Javascript also followed that path.) In these languages, objects have no classes. Instead, each object may have a prototype, which is a regular object where the first object looks up any operation that it does not know about. To represent a class in such languages, we simply create an object to be used exclusively as a prototype for other objects (its instances). Both classes and prototypes work as a place to put behavior to be shared by several objects.
In Lua, we can implement prototypes
using the idea of inheritance that
we saw in the section called “The __index
metamethod”.
More specifically,
if we have two objects A
and B
,
all we have to do to make B
a prototype for A
is this:
setmetatable(A, {__index = B})
After that, A
looks up in B
for any operation
that it does not have.
To see B
as the class of the object A
is not much
more than a change in terminology.
Let us go back to our example of a bank account.
To create other accounts with behavior similar to Account
,
we arrange for these new objects to inherit their operations
from Account
, using the __index
metamethod.
local mt = {__index = Account} function Account.new (o) o = o or {} -- create table if user does not provide one setmetatable(o, mt) return o end
After this code, what happens when we create a new account and call a method on it, like this?
a = Account.new{balance = 0} a:deposit(100.00)
When we create the new account, a
,
it will have mt
as its metatable.
When we call a:deposit(100.00)
,
we are actually calling a.deposit(a, 100.00)
;
the colon is only syntactic sugar.
However, Lua cannot find a "deposit"
entry in the table a
;
hence, Lua looks into the __index
entry of the metatable.
The situation now is more or less like this:
getmetatable(a).__index.deposit(a, 100.00)
The metatable of a
is mt
,
and mt.__index
is Account
.
Therefore, the previous expression evaluates
to this one:
Account.deposit(a, 100.00)
That is, Lua calls the original deposit
function,
but passing a
as the self parameter.
So, the new account a
inherited the function deposit
from Account
.
By the same mechanism,
it inherits all fields from Account
.
We can make two small improvements on this scheme.
The first one is that we do not need
to create a new table for the metatable role;
instead, we can use the Account
table itself for that purpose.
The second one is that we can use the colon syntax for
the new
method, too.
With these two changes,
method new
becomes like this:
function Account:new (o) o = o or {} self.__index = self setmetatable(o, self) return o end
Now, when we call Account:new()
,
the hidden parameter self
gets Account
as its value,
we make Account.__index
also equal to Account
,
and set Account
as the metatable for the new object.
It may seem that we do not gained much with
the second change (the colon syntax);
the advantage of using self
will become apparent when we introduce
class inheritance, in the next section.
The inheritance works not only for methods,
but also for other fields that are absent in the new account.
Therefore, a class can provide not only methods,
but also constants and default values for its instance fields.
Remember that, in our first definition of Account
,
we provided a field balance
with value 0.
So, if we create a new account without an initial balance,
it will inherit this default value:
b = Account:new() print(b.balance) --> 0
When we call the deposit
method on b
,
it runs the equivalent of the following code,
because self
is b
:
b.balance = b.balance + v
The expression b.balance
evaluates to zero
and the method assigns an initial deposit to b.balance
.
Subsequent accesses to b.balance
will not invoke
the index metamethod,
because now b
has its own balance
field.
Because classes are objects, they can get methods from other classes, too. This behavior makes inheritance (in the usual object-oriented meaning) quite easy to implement in Lua.
Let us assume we have a base class like Account
,
in Figure 21.1, “the Account
class”.
Figure 21.1. the Account
class
Account = {balance = 0} function Account:new (o) o = o or {} self.__index = self setmetatable(o, self) return o end function Account:deposit (v) self.balance = self.balance + v end function Account:withdraw (v) if v > self.balance then error"insufficient funds" end self.balance = self.balance - v end
From this class,
we want to derive a subclass SpecialAccount
that allows the customer to withdraw more than his balance.
We start with an empty class
that simply inherits all its operations from its base class:
SpecialAccount = Account:new()
Up to now, SpecialAccount
is just an instance of Account
.
The magic happens now:
s = SpecialAccount:new{limit=1000.00}
SpecialAccount
inherits new
from Account
,
like any other method.
This time, however, when new
executes,
its self
parameter will refer to SpecialAccount
.
Therefore, the metatable of s
will be SpecialAccount
,
whose value at field __index
is also SpecialAccount
.
So, s
inherits from SpecialAccount
,
which inherits from Account
.
Later,
when we evaluate s:deposit(100.00)
,
Lua cannot find a deposit
field in s
,
so it looks into SpecialAccount
;
it cannot find a deposit
field there, too,
so it looks into Account
;
there it finds the original implementation for a deposit.
What makes a SpecialAccount
special is that we can redefine
any method inherited from its superclass.
All we have to do is to write the new method:
function SpecialAccount:withdraw (v) if v - self.balance >= self:getLimit() then error"insufficient funds" end self.balance = self.balance - v end function SpecialAccount:getLimit () return self.limit or 0 end
Now, when we call s:withdraw(200.00)
,
Lua does not go to Account
,
because it finds the new withdraw
method in SpecialAccount
first.
As s.limit
is 1000.00
(remember that we set this field when we created s
),
the program does the withdrawal,
leaving s
with a negative balance.
An interesting aspect of objects in Lua is that we do not
need to create a new class to specify a new behavior.
If only a single object needs a specific behavior,
we can implement that behavior directly in the object.
For instance, if the account s
represents some
special client whose limit is always 10% of her balance,
we can modify only this single account:
function s:getLimit () return self.balance * 0.10 end
After this declaration, the call s:withdraw(200.00)
runs the withdraw
method from SpecialAccount
,
but when withdraw
calls self:getLimit
,
it is this last definition that it invokes.
Because objects are not primitive in Lua, there are several ways to do object-oriented programming in Lua. The approach that we have seen, using the index metamethod, is probably the best combination of simplicity, performance, and flexibility. Nevertheless, there are other implementations, which may be more appropriate for some particular cases. Here we will see an alternative implementation that allows multiple inheritance in Lua.
The key to this implementation is the use
of a function for the metafield __index
.
Remember that, when a table’s metatable has a function in
the __index
field,
Lua will call this function whenever it
cannot find a key in the original table.
Then, __index
can look up for the missing
key in how many parents it wants.
Multiple inheritance means that
a class does not have a unique superclass.
Therefore, we should not use a
(super)class method to create subclasses.
Instead, we will define an independent function for this purpose,
createClass
,
which has as arguments all superclasses of the new class;
see Figure 21.2, “An implementation of multiple inheritance”.
This function creates a table to represent the new class
and sets its metatable with an __index
metamethod that does
the multiple inheritance.
Despite the multiple inheritance,
each object instance still belongs to one single class,
where it looks for all its methods.
Therefore, the relationship between classes and superclasses
is different from the relationship between instances and classes.
Particularly,
a class cannot be the metatable for its instances and for
its subclasses at the same time.
In Figure 21.2, “An implementation of multiple inheritance”,
we keep the class as the metatable for its instances,
and create another table to be the metatable of the class.
Figure 21.2. An implementation of multiple inheritance
-- look up for 'k' in list of tables 'plist' local function search (k, plist) for i = 1, #plist do local v = plist[i][k] -- try 'i'-th superclass if v then return v end end end function createClass (...) local c = {} -- new class local parents = {...} -- list of parents -- class searches for absent methods in its list of parents setmetatable(c, {__index = function (t, k) return search(k, parents) end}) -- prepare 'c' to be the metatable of its instances c.__index = c -- define a new constructor for this new class function c:new (o) o = o or {} setmetatable(o, c) return o end return c -- return new class end
Let us illustrate the use of createClass
with a small example.
Assume our previous class Account
and another class,
Named
, with only two methods: setname
and getname
.
Named = {} function Named:getname () return self.name end function Named:setname (n) self.name = n end
To create a new class NamedAccount
that is a subclass of
both Account
and Named
,
we simply call createClass
:
NamedAccount = createClass(Account, Named)
To create and to use instances, we do as usual:
account = NamedAccount:new{name = "Paul"} print(account:getname()) --> Paul
Now let us follow how Lua evaluates the expression account:getname()
;
more specifically,
let us follow the evaluation of account["getname"]
.
Lua cannot find the field "getname"
in account
;
so, it looks for the field __index
on the account
’s metatable,
which is NamedAccount
in our example.
But NamedAccount
also cannot provide a "getname"
field,
so Lua looks for the field __index
of NamedAccount
’s metatable.
Because this field contains a function, Lua calls it.
This function then looks for "getname"
first in Account
,
without success,
and then in Named
, where it finds a non-nil value,
which is the final result of the search.
Of course, due to the underlying complexity of this search, the performance of multiple inheritance is not the same as single inheritance. A simple way to improve this performance is to copy inherited methods into the subclasses. Using this technique, the index metamethod for classes would be like this:
setmetatable(c, {__index = function (t, k) local v = search(k, parents) t[k] = v -- save for next access return v end})
With this trick, accesses to inherited methods are as fast as to local methods, except for the first access. The drawback is that it is difficult to change method definitions after the program has started, because these changes do not propagate down the hierarchy chain.
Many people consider privacy (also called information hiding) to be an integral part of an object-oriented language: the state of each object should be its own internal affair. In some object-oriented languages, such as C++ and Java, we can control whether a field (also called an instance variable) or a method is visible outside the object. Smalltalk, which popularized object-oriented languages, makes all variables private and all methods public. Simula, the first ever object-oriented language, did not offer any kind of protection.
The standard implementation of objects in Lua, which we have shown previously, does not offer privacy mechanisms. Partly, this is a consequence of our use of a general structure (tables) to represent objects. Moreover, Lua avoids redundancy and artificial restrictions. If you do not want to access something that lives inside an object, just do not do it. A common practice is to mark all private names with an underscore at the end. You immediately feel the smell when you see a marked name being used in public.
Nevertheless, another aim of Lua is to be flexible, offering the programmer meta-mechanisms that enable her to emulate many different mechanisms. Although the basic design for objects in Lua does not offer privacy mechanisms, we can implement objects in a different way, to have access control. Although programmers do not use this implementation frequently, it is instructive to know about it, both because it explores some interesting aspects of Lua and because it is a good solution for more specific problems.
The basic idea of this alternative design is to represent each object through two tables: one for its state and another for its operations, or its interface. We access the object itself through the second table, that is, through the operations that compose its interface. To avoid unauthorized access, the table representing the state of an object is not kept in a field of the other table; instead, it is kept only in the closure of the methods. For instance, to represent our bank account with this design, we could create new objects running the following factory function:
function newAccount (initialBalance) local self = {balance = initialBalance} local withdraw = function (v) self.balance = self.balance - v end local deposit = function (v) self.balance = self.balance + v end local getBalance = function () return self.balance end return { withdraw = withdraw, deposit = deposit, getBalance = getBalance } end
First,
the function creates a table to keep the internal object state
and stores it in the local variable self
.
Then, the function creates the methods of the object.
Finally, the function creates and returns the external object,
which maps method names to the actual method implementations.
The key point here is that these methods do not get self
as an extra parameter;
instead, they access self
directly.
Because there is no extra argument,
we do not use the colon syntax to manipulate such objects.
We call their methods just like regular functions:
acc1 = newAccount(100.00) acc1.withdraw(40.00) print(acc1.getBalance()) --> 60
This design gives full privacy to
anything stored in the self
table.
After the call to newAccount
returns,
there is no way to gain direct access to this table.
We can access it only through
the functions created inside newAccount
.
Although our example puts only one instance variable into
the private table,
we can store all private parts of an object in this table.
We can also define private methods:
they are like public methods,
but we do not put them in the interface.
For instance, our accounts may give an extra credit of 10%
for users with balances above a certain limit,
but we do not want the users to have access to the details of
that computation.
We can implement this functionality as follows:
function newAccount (initialBalance)
local self = {
balance = initialBalance,
LIM = 10000.00,
}
local extra = function ()
if self.balance > self.LIM then
return self.balance*0.10
else
return 0
end
end
local getBalance = function ()
return self.balance + extra()
end
as before
Again, there is no way for any user to access
the function extra
directly.
A particular case of the previous approach
for object-oriented programming
occurs when an object has a single method.
In such cases, we do not need to create an interface table;
instead,
we can return this single method as the object representation.
If this sounds a little weird,
it is worth remembering iterators like
io.lines
or string.gmatch
.
An iterator that keeps state internally
is nothing more than a single-method object.
Another interesting case of single-method objects occurs when this single-method is actually a dispatch method that performs different tasks based on a distinguished argument. A prototypical implementation for such an object is as follows:
function newObject (value) return function (action, v) if action == "get" then return value elseif action == "set" then value = v else error("invalid action") end end end
Its use is straightforward:
d = newObject(0) print(d("get")) --> 0 d("set", 10) print(d("get")) --> 10
This unconventional implementation for objects is quite effective.
The syntax d("set", 10)
,
although peculiar,
is only two characters longer than
the more conventional d:set(10)
.
Each object uses one single closure,
which is usually cheaper than one table.
There is no inheritance, but we have full privacy:
the only way to access an object state is through its sole method.
Tcl/Tk uses a similar approach for its widgets. The name of a widget in Tk denotes a function (a widget command) that can perform all kinds of operations over the widget, according to its first argument.
Another interesting approach for privacy uses a dual representation. Let us start seeing what a dual representation is.
Usually, we associate attributes to tables using keys, like this:
table[key] = value
However, we can use a dual representation: we can use a table to represent a key, and use the object itself as a key in that table:
key = {} ... key[table] = value
A key ingredient here is the fact that we can index tables in Lua not only with numbers and strings, but with any value —in particular with other tables.
As an example,
in our Account implementation,
we could keep the balances of all accounts
in a table balance
,
instead of keeping them in the accounts themselves.
Our withdraw
method would become like this:
function Account.withdraw (self, v) balance[self] = balance[self] - v end
What we gain here?
Privacy.
Even if a function has access to an account,
it cannot directly access its balance unless it also
has access to the table balance
.
If the table balance
is kept in a local
inside the module Account
,
only functions inside the module can access it and,
therefore,
only those functions can manipulate account balances.
Before we go on,
I must discuss a big naivety of this implementation.
Once we use an account as a key in the balance
table,
that account will never become garbage for the garbage collector.
It will be anchored there until some code explicitly
removes it from that table.
That may not be a problem for bank accounts
(as an account usually has to be formally closed
before going away),
but for other scenarios that could be a big drawback.
In the section called “Object Attributes”,
we will see how to solve this problem.
For now, we will ignore it.
Figure 21.3, “Accounts using a dual representation” shows again an implementation for accounts, this time using a dual representation.
Figure 21.3. Accounts using a dual representation
local balance = {} Account = {} function Account:withdraw (v) balance[self] = balance[self] - v end function Account:deposit (v) balance[self] = balance[self] + v end function Account:balance () return balance[self] end function Account:new (o) o = o or {} -- create table if user does not provide one setmetatable(o, self) self.__index = self balance[o] = 0 -- initial balance return o end
We use this class just like any other one:
a = Account:new{} a:deposit(100.00) print(a:balance())
However, we cannot tamper with an account balance.
By keeping the table balance
private to the module,
this implementation ensures its safety.
Inheritance works without modifications.
This approach has a cost quite similar to the standard one,
both in terms of time and of memory.
New objects need one new table and
one new entry in each private table being used.
The access balance[self]
can be
slightly slower than self.balance
,
because the latter uses a local variable while the first
uses an external variable.
Usually this difference is negligible.
As we will see later,
it also demands some extra work from the garbage collector.
Exercise 21.1:
Implement a class Stack
,
with methods push
, pop
, top
,
and isempty
.
Exercise 21.2:
Implement a class StackQueue
as a subclass of Stack
.
Besides the inherited methods,
add to this class a method insertbottom
,
which inserts an element at the bottom of the stack.
(This method allows us to use objects of this class
as queues.)
Exercise 21.3:
Reimplement your Stack
class using a dual representation.
Exercise 21.4:
A variation of the dual representation
is to implement objects using proxies (the section called “Tracking table accesses”).
Each object is represented by an empty proxy table.
An internal table maps proxies to tables that carry the object state.
This internal table is not accessible from the outside,
but methods use it to translate their self
parameters
to the real tables where they operate.
Implement the Account example using this approach
and discuss its pros and cons.
Personal copy of Eric Taylor <jdslkgjf.iapgjflksfg@yandex.com>