blog.poucet.org Rotating Header Image

September, 2007:

A simple OO-system in Lua

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

A simple OO-system in Lua

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: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
  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")                    -- 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

Back from Vacations

Well it has been a while since I last blogged. There are several reasons for this (well two main ones, really):

  • I was away on vacations to Colombia
  • I was (and still am) busy with Journal papers

That being said, I do believe it’s time to leave the now probably dissapeared reader a small note.

Usually I do not post personal messages, or at least try to reduce them to a minimum as they have little information for whatever casual reader that may pass by. In this specific instance, however, I will deviate from that self-imposed guide-line. If you’re interested, keep on reading :) (There are some semi-technical questions at the end, for those loving web-stuff)

Colombia was a blast! Well that is certainly the best way to describe it. I don’t think I’ve ever had such a fun trip. The reason I travelled there was for the wedding of my best friend, whom I’ve known since 10 years now. Having met a couple of friends of his future wife in the past, we decided to travel together to explore Colombia.

Thus in the 2 weeks we explored Carthagena, Bogota, Villa de Lleva and Leticia. It seems we caught the down-season as Carthagena was rather empty, though I did not mind it, not really looking for big crowds. Being the sort of person with a very small memory (I think I automatically compile all my experiences into background memory which is usable from an algorithmic perspective, but is quite hard to introspect).

Carthagena

There’s a stark contrast between Bocca Grande, the new part of Carthagena, and the old center. Both are interesting to take a look at. The old center is obviously a beautiful to place to walk around. On the other hand, Bocca Grande gives you a better look at the real life of Carthagena. Especially in the evening and at night, the place comes alive with many people going out to drink or eat.

Bogota

I think this city doesn’t end. When you drive with a taxi (Which feels more like a rally-race through urban surroundings a la GTA) the city just keeps on going and going. The old part is quite lovely to walk through and there are quite a few things to see within walking distance such as the Gold Museum and various other buildings whose name I forgot. You can then walk uphill a bit to see smaller streets with more typical buildings.

If instead you’re more interested in shopping and going out, then I can definitely recommend Zona Rosa, or Zona T (at least I think that’s how it’s spelled.) This is a little block with streets crossing it in the shape of a ‘T’ and filled with bars to go out. Surrounding the block are various malls and fashion stores, definitely worth a look.

Villa de Lleva

Due to certain reasons, I was only able to stay here for a very short time. That being said, I do think I caught the gist of the place. It’s a very tranquil pre-colombian town (which seems to be quite known amongst Colombians as they all praised it when mentioned in answer to the question where we were going). Cobble-stone streets (Which were quite crazy to driver over with the nearly rusted to pieces taxi that brought us there the first evening), low-rise white buildings, very green surrounding hills. It’s a shame I could not stay there another day for the hotel we were staying had a natural pool and was very quaint overall.

Leticia

What Bogota is to taxis, Leticia is to motor-cycles. Wherever you look, there are motorcycles. But if one figures that there is no actual road going to Leticia, and that the longest tract of road from Leticia is only 25km, it starts to make more sense. The town is rather small but definitely worth a look for the style of buildings as well as the way of life. It is indeed far more primitive than the other mentioned towns. From the way that the houses are built on poles, and the catwalks that connects some of them, it is clear that this town has to deal with the different seasons and heights of the amazon river.

One note to the reader, be careful with people that await you at the airport to give you an
amazon tour, even if the hotel says they’re trustworthy. They might not run with the money you have to pay upfront, but they could seriously overcharge you. And as always, make sure to bargain for the final price. These are the two rules that my travelmates and myself sinned against, and we ended up paying a lot more than the fourth person that joined us on the trip.

Aside from the price, however, the trip into the amazon forest was rather enjoyable. We traveled by small boat, just us 4 and three guides, into Brazil and Peru, taking a side-river to get to see a more authentic amazon forest.

Conclusions

As usual, when coming back from a vacation, I had a big todo list of things I’d like to do. We’ll see what comes of them. One of them was to start writing stories, though who knows how that will pan out. If I do, however, I thought I’d make a blog for them, but then the question arises: Do I use this blog? Do I start a fresh blog? An anonymous one or under a pseudonym?

Now onto the technical issue. I’d like to start some sort of site where everyone that attended the wedding can drop their pictures. I’ve thought about different solutions, though perhaps someone has a better idea. Ideally the solution should be easy to use for people not very familiar with computers. One option would be that everyone sends me their pictures and I then place them on some site (or even picasa/flickr). This is most likely not going to be possible due to the sheer quantity and size of the pictures. Another option would be to have one shared flickr/picasa account where I would share the login/password to all the people that attended, but a shared password is certain way to security leaks. Another option would be to start a blog through blogger, and share this blogger. Now this would require that everyone has a google account, but who doesn’t nowadays? Then everyone could write a little entry and link to their pictures. While attractive from a security point of view, and also giving the ability for people who do not have a login to place comments, this has one downside. It would make it harder to actually browse the pictures. Does anyone have a better idea?

Back from Vacations

Well it has been a while since I last blogged. There are several reasons for this (well two main ones, really):

  • I was away on vacations to Colombia
  • I was (and still am) busy with Journal papers

That being said, I do believe it’s time to leave the now probably dissapeared reader a small note.

Usually I do not post personal messages, or at least try to reduce them to a minimum as they have little information for whatever casual reader that may pass by. In this specific instance, however, I will deviate from that self-imposed guide-line. If you’re interested, keep on reading :) (There are some semi-technical questions at the end, for those loving web-stuff)

Colombia was a blast! Well that is certainly the best way to describe it. I don’t think I’ve ever had such a fun trip. The reason I travelled there was for the wedding of my best friend, whom I’ve known since 10 years now. Having met a couple of friends of his future wife in the past, we decided to travel together to explore Colombia.

Thus in the 2 weeks we explored Carthagena, Bogota, Villa de Lleva and Leticia. It seems we caught the down-season as Carthagena was rather empty, though I did not mind it, not really looking for big crowds. Being the sort of person with a very small memory (I think I automatically compile all my experiences into background memory which is usable from an algorithmic perspective, but is quite hard to introspect).

Carthagena

There’s a stark contrast between Bocca Grande, the new part of Carthagena, and the old center. Both are interesting to take a look at. The old center is obviously a beautiful to place to walk around. On the other hand, Bocca Grande gives you a better look at the real life of Carthagena. Especially in the evening and at night, the place comes alive with many people going out to drink or eat.

Bogota

I think this city doesn’t end. When you drive with a taxi (Which feels more like a rally-race through urban surroundings a la GTA) the city just keeps on going and going. The old part is quite lovely to walk through and there are quite a few things to see within walking distance such as the Gold Museum and various other buildings whose name I forgot. You can then walk uphill a bit to see smaller streets with more typical buildings.

If instead you’re more interested in shopping and going out, then I can definitely recommend Zona Rosa, or Zona T (at least I think that’s how it’s spelled.) This is a little block with streets crossing it in the shape of a ‘T’ and filled with bars to go out. Surrounding the block are various malls and fashion stores, definitely worth a look.

Villa de Lleva

Due to certain reasons, I was only able to stay here for a very short time. That being said, I do think I caught the gist of the place. It’s a very tranquil pre-colombian town (which seems to be quite known amongst Colombians as they all praised it when mentioned in answer to the question where we were going). Cobble-stone streets (Which were quite crazy to driver over with the nearly rusted to pieces taxi that brought us there the first evening), low-rise white buildings, very green surrounding hills. It’s a shame I could not stay there another day for the hotel we were staying had a natural pool and was very quaint overall.

Leticia

What Bogota is to taxis, Leticia is to motor-cycles. Wherever you look, there are motorcycles. But if one figures that there is no actual road going to Leticia, and that the longest tract of road from Leticia is only 25km, it starts to make more sense. The town is rather small but definitely worth a look for the style of buildings as well as the way of life. It is indeed far more primitive than the other mentioned towns. From the way that the houses are built on poles, and the catwalks that connects some of them, it is clear that this town has to deal with the different seasons and heights of the amazon river.

One note to the reader, be careful with people that await you at the airport to give you an
amazon tour, even if the hotel says they’re trustworthy. They might not run with the money you have to pay upfront, but they could seriously overcharge you. And as always, make sure to bargain for the final price. These are the two rules that my travelmates and myself sinned against, and we ended up paying a lot more than the fourth person that joined us on the trip.

Aside from the price, however, the trip into the amazon forest was rather enjoyable. We traveled by small boat, just us 4 and three guides, into Brazil and Peru, taking a side-river to get to see a more authentic amazon forest.

Conclusions

As usual, when coming back from a vacation, I had a big todo list of things I’d like to do. We’ll see what comes of them. One of them was to start writing stories, though who knows how that will pan out. If I do, however, I thought I’d make a blog for them, but then the question arises: Do I use this blog? Do I start a fresh blog? An anonymous one or under a pseudonym?

Now onto the technical issue. I’d like to start some sort of site where everyone that attended the wedding can drop their pictures. I’ve thought about different solutions, though perhaps someone has a better idea. Ideally the solution should be easy to use for people not very familiar with computers. One option would be that everyone sends me their pictures and I then place them on some site (or even picasa/flickr). This is most likely not going to be possible due to the sheer quantity and size of the pictures. Another option would be to have one shared flickr/picasa account where I would share the login/password to all the people that attended, but a shared password is certain way to security leaks. Another option would be to start a blog through blogger, and share this blogger. Now this would require that everyone has a google account, but who doesn’t nowadays? Then everyone could write a little entry and link to their pictures. While attractive from a security point of view, and also giving the ability for people who do not have a login to place comments, this has one downside. It would make it harder to actually browse the pictures. Does anyone have a better idea?