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 a2In 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>
endEffect same as following
function obj.method(self,<other arguments>)
<code of method>
endSquare 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)
blueBut 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 ShapePoints 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 ShapeOverride 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 CircleCan 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