H. Conrad Cunningham
17 October 2016
Note: I corrected a few formatting errors on 29 January 2020.
Acknowledgements: These slides are adapted from slide set 11 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 January 2020 is a recent version of Firefox from Mozilla.
Lua tables are objects
Lua tables can have own operations (closures associated with keys)
Account = { balance = 0.0 }
function Account.withdraw(v)
Account.balance = Account.balance - v
end
Account.withdraw(100.00)
Account.withdraw
almost method as in OOP languages
Problem is hard-coding of receiver as global name
Better to pass receiver as explicit parameter
function Account.withdraw(self,v)
self.balance = self.balance - v
end
a1 = Account
a2 = { balance = 0, withdraw = Account.withdraw }
a1.withdraw(a1,100.00) -- operates on a1
a2.withdraw(a2,200.00) -- operates on a2
In most OOP languages, method has implicit receiver object called self
or this
Lua method is function that takes receiver as 1st parameter, call parameter whatever want
Indexing Lua “receiver” object with method name returns its closure, then closure can be applied to arguments
> obj.method(obj, <other arguments>)
Lua has colon operator :
as syntactic sugar for above – obj
any expression, evaluated once, get method, pass as 1st parameter
> obj:method(<other arguments>)
Can use colon to declare a method
function obj:method(<other arguments>)
<code of method>
end
Effect same as following
function obj.method(self,<other arguments>)
<code of method>
end
Square
Object local square = { x = 10, y = 20, side = 25 }
function square:move(dx, dy)
self.x = self.x + dx
self.y = self.y + dy
end
function square:area()
return self.side * self.side
end
return square
> print(square:area())
625
> square:move(10, -5)
> print(square.x, square.y)
20 15
Methods in square
work with any table with x
, y
, and side
> square2 = { x = 30, y = 5, side = 10 }
> print(square.area(square2))
100
> square.move(square2, 10, 10)
> print(square2.x, square2.y)
40 15
Can put methods in Square
class, a prototype for objects like square
and square2
Also add method to create new instances
Have metatable with metamethod __index
pointing to Square
Square
Class as Module local Square = {}
Square.__index = Square
function Square:new(x, y, side)
return setmetatable({ x = x, y = y, side = side }, self)
end
function Square:move(dx, dy)
self.x = self.x + dx
self.y = self.y + dy
end > s1 = Square:new(10, 5, 10)
> s2 = Square:new(20, 10, 25)
function Square:area() > print(s1:area(), s2:area())
return self.side * self.side 100 625
end > s1:move(5, 10)
> print(s1.x, s1.y)
return Square 15 15
Other fields in Square
become default values for instances
local Square = { color = "blue" }
Reading field gives default value from class
> s1 = Square:new(10, 5, 10)
> print(s1.color)
blue
But setting field just sets field in instance
> s1.color = "red"
> print(s1.color)
red
> s2 = Square:new(20, 10, 25)
> print(s2.color)
blue
Circle
Class local Circle = {}
Circle.__index = Circle
function Circle:new(x, y, radius)
return setmetatable({ x = x, y = y, radius = radius }, self)
end
function Circle:move(dx, dy) -- same as for Square
self.x = self.x + dx
self.y = self.y + dy
end
function Circle:area()
return math.pi * self.radius * self.radius
end
return Circle
Shape
SuperclassFactor common parts into Shape
class:
local Shape = {}
Shape.__index = Shape
function Shape:new(x, y)
return setmetatable({ x = x, y = y }, self)
end
function Shape:move(dx, dy)
self.x = self.x + dx
self.y = self.y + dy
end
return Shape
Metatable of instance is class; metatable of class is superclass
Point
extends Shape
Points are simple shapes with just coordinates, area 0
local Shape = require "shape"
> p = Point:new(10, 20)
local Point = setmetatable({}, Shape) > print(p:area())
Point.__index = Point 0
> p:move(-5, 10)
function Point:area() > print(p.x, p.y)
return 0 5 30
end
return Point
setmetatable
makes new class inherit methods of Shape
including constructor
Circle
extends Shape
Override constructor new
in class Circle
, but call Shape
constructor to do part of work
local Shape = require "shape"
local Circle = setmetatable({}, Shape)
Circle.__index = Circle
> c = Circle:new(10, 20, 5)
function Circle:new(x, y, radius) > c:move(5, -5)
local shape = Shape.new(self, x, y) > print(c.x, c.y)
shape.radius = radius 15 15
return shape > print(c:area())
end 78.539816339745
function Circle:area()
return math.pi * self.radius * self.radius
end
return Circle
Can use explicit reference like Shape.new
to call the super
method
This just one way to implement objects in Lua
Advantage: simple!
Disadvantage: putting “class methods” (new
) and “instance methods” (move
, area
) in same namespace
Other metamethods not inherited
To connect __tostring
with a tostring
method that can be overriden, explicitly set Class.__tostring =
Class.tostring
for each Class
Can define more sophisticated object models in libraries, make work with the :
operator
With our object model, how could we check whether an object is an instance of a class?
What about checking whether an object is an instance of a class or one of its subclasses?
instanceof
FunctionAssuming the object/class model from this set of slides:
function instanceof(obj, class)
local mt = getmetatable(obj) -- get class prototype
if mt == class then -- is instance of class?
return true
elseif mt == nil then -- top has no metatable
return false
else -- check superclass
return instanceof(mt, class)
end
end