Well, I recently (very recently) started to play around with Lua. I ordered the book before leaving to Colombia and found it when I was back. It took me just a day to read it, as the book is a light read and rather well-written, covering all the important topics.
One reason I’ve started to look at Lua is because it seems like a rather powerful language. Read more if you’re interested in why, and a first OO-system that I’ve rolled in it.
Powerful is a rather abstract word, and perhaps I should first explain why I consider Lua, powerful, especially since I come from a rather different programming language sphere (Haskell).
The reason that lua appeals to me is that it seems to enable a way of meta-programming that cuts cleanly through your code. It takes a scheme-approach of being built-up in an orthogonal manner with a small core. That being said, I find it more appealing than scheme as, at least to me, it is easier to code things in. The meta-programming seems to be more powerful than in scheme.
Anyways, I started coding in it yesterday, wanting to roll an OO-system that would be easy to use. Additionally, I wanted a way to nicely dump my state and then be able to reload it after potentially modifying some of the classes I use.
For the serialization aspect, I took the serialization scheme from the Lua Book and extended it to deal with custom serialization meta-methods as well as the possibilities of keys being tables. Note that it’s not yet finished, it need a small piece of code to generate unique names, but that’s rather trivial.
As for the OO system, this is a second version. My original version was rather primitive. One thing that I wanted was the ability to call the static-super method of a method. Now a simple way would be:
function Class:method(...)
SuperClass.method(self, ....)
end
I did not like this idea, however, as it requires explicitly referring to the super-class. Certainly, for simple class-trees this is a viable option. However, since I would like to introduce the concept of mixins, and in general don’t like the verbositoy of this, I introduced a ‘static’ way of defining __super (The super-method). Basically, whenever a method is defined, it’s function-environment is modified to have a link to __super (as well as __class). The reason for having a link to __class is that this is the static-class that the method belongs to, not the class of the object.
The (unfinished) code can be seen below. It is based on ideas from: ClassesAndMethodsExample and InheritanceTutorial. As mentioned before, this is only my second day of hacking Lua, so I’m certain there are things that could be done much better. Suggestions, as always, welcome
local setmetatable = setmetatable
--------------------------------------------------------------------------------
-- Helper functions:
--------------------------------------------------------------------------------
function memoize(f)
local self = setmetatable({}, {__mode = "k"})
self.__index = function(self, k) local v = f(k); self[k] = v return v end
self.__call = function(self, k) return self[k] end
return self
end
--------------------------------------------------------------------------------
-- Class System
--------------------------------------------------------------------------------
-- Methods should be called on the klass object with as parameter the object itself!
--
Root = {
super = nil;
name = "Root";
new =
function(class, ...)
local obj = {class = class}
local meta = {
__index = class.methods,
__serialize = class.__serialize or Root.__serialize
}
setmetatable(obj, meta)
if (class.methods.init) then
class.methods.init(obj, ...)
end
return obj
end;
methods = {
classname = function(self)
return (self.class.name)
end;
isa = function(self, aClass)
local cur_class = self.class
while (nil ~= cur_class) do
if cur_class == aClass then
return true
else
cur_class = cur_class.super
end
end
return false
end
};
data = {};
__serialize = function(self, serializer, name)
serializer:write("setmetatable({class = " .. self:classname() .. "}, {\n")
serializer:write(" __index = " .. self:classname() .. ".methods,\n")
serializer:write(" __serialize = " .. self:classname() .. ".__serialize or Root.__serialize\n")
serializer:write("})\n")
for k,v in pairs(self) do -- save its fields
if k ~= "class" then
local kname
if serializer:isBasic(k) then
kname = serializer:b asicSerialize(k)
elseif type(k) == "table" then
if saved[k] then
kname = saved[k]
else
kname = serializer:generateName()
serializer:serialize(kname, k)
end
end
local fname = string.format("%s[%s]", name, kname)
serializer:serialize(fname, v)
end
end
end
}
function Class(name, super)
super = super or Root
class = {
super = super,
name = name,
methods = {}
}
-- if class slot unavailable, check super class
-- if applied to argument, pass it to the class method new
setmetatable(class, {
__index = super,
__call = function(self,...) return self:new(...) end
})
setmetatable(class.methods, {
-- if instance method unavailable, check method slot in super class
__index = class.super.methods,
-- when defining a new method, set the environment for proper, static, class access and super-method access
__newindex = function (c, m, f)
if (class.super.methods[m]) then
setfenv(f, setmetatable({__class = class, __super = class.super.methods[m]}, {__index = getfenv(f) }))
else
setfenv(f, setmetatable({__class = class}, {__index = getfenv(f) }))
end
rawset(c,m,f)
end
})
return class
end
function Mixin(name, mixin, super)
-- return (name, nil, super)
end
function Singleton(name, super)
end
--------------------------------------------------------------------------------
-- Serializer
-- TODO: Make a way to easily define what attributes -not- to save
--------------------------------------------------------------------------------
Serializer = Class("Serializer")
function Serializer.methods:init(stream, temp, saved)
self.stream = stream or io.stdout
self.temp = temp or 0
self.saved = saved or {}
end
function Serializer.methods:basicSerialize(value)
if type(value) == "boolean" then
return tostring(value)
elseif type(value) == "number" then
return tostring(value)
elseif type(value) == "string" then
return string.format("%q", value)
end
end
function Serializer.methods:isBasic(value)
return type(value) == "boolean" or type(value) == "number" or type(value) == "string"
end
function Serializer.methods:write(...)
self.stream:write(...)
end
function Serializer.methods:serialize(name, value)
self:write(name, " = ")
if self:isBasic(value) then
self:write(self:basicSerialize(value), "\n")
elseif type(value) == "table" then
if self.saved[value] then -- value already saved?
self:write(self.saved[value], "\n") -- use its previous value
else
self.saved[value] = name -- save name for next time
serializer = (getmetatable(value) or self).__serialize or self.__serialize
serializer(value, self, name)
end
else
error("cannot save a " .. type(value))
end
end
function Serializer.methods:__serialize(serializer, name)
serializer:write("{}\n< /font>") -- create a new table
for k,v in pairs(self) do -- save its fields
local kname
if serializer:isBasic(k) then
kname = serializer:basicSerialize(k)
elseif type(k) == "table" then
if saved[k] then
kname = saved[k]
else
kname = serializer:generateName()
serializer:serialize(kname, k)
end
end
local fname = string.format("%s[%s]", name, kname)
serializer:serialize(fname, v)
end
end
function deserialize(o, stream)
stream = stream or io.stdin
end
--------------------------------------------------------------------------------
-- Test Cases
--------------------------------------------------------------------------------
Foo = Class("Foo")
function Foo.methods:init(...)
self.foo = 1
end
function Foo.methods:__serialize(serializer, name)
serializer:write("{foo = " .. self.foo .. "}\n")
end
Bar = Class("Bar", Foo)
function Bar.methods:init(...)
__super(self, ...)
self.bar = 2
end
function Bar.methods:boom()
if __super then __super(self) end
print "Bar:boom"
end
Bum = Class("Bum", Bar)
function Bum.methods:init(...)
if __super then __super(self, ...) end
self.bum = 3
end
function Bum.methods:boom()
if __super then __super(self) end
print "Bum:boom"
end
With the help of debug library it is possible to achieve even shorter syntax:
function Child:method(…)
super(…)
end
still such approach is rather slow (it requires one closure creation per call).
Also take a look at metalua. It enables rather powerful techniques: e.g. one can make an extension that `saves` function body AST somewhere and this allows to get pretty OO-syntax at virtually no cost.