lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


I have just started OO programming in Lua, and, having had a look at some
of the object implementations, spotted the obvious oddity: although Lua is
a dynamic language, they were all class-based. I wondered if this was
merely a hangup owing to the fact that most OO languages are class based,
and after struggling to find a nice simple syntax for all the things I
wanted to do, concluded that it was!

So I present my simple prototype-based system. Very little code to
implement[*], utterly dynamic, and uses Lua's existing features to the
full to provide nice syntax and all the required functionality...hmm,
that's Spirit of Lua!

[*] Very little OO-specific code. The rest are all routines that any
self-respecting Lua programmer will want in their armoury whether or not
they use objects. Oh, yes!

First, a useful tag method for tables:

-- merge: Merge two tables
-- If there are duplicate fields, u's will be used. The tag of the
-- returned table is that of t
--   t, u: tables
-- returns
--   r: the merged table
function merge(t, u)
  local r = {}
  for i, v in t do
    r[i] = v
  end
  for i, v in u do
    r[i] = v
  end
  if tag(t) ~= tag({}) then
    settag(r, tag(t))
  end
  return r
end

-- Tag methods for tables
-- table + table = merge
settagmethod(tag({}), "add",
             function (t, u)
               if type(u) ~= "table" then
                 error("table expected")
               end
               return merge(t, u)
             end)

and a very useful function for messing around with tables:

-- permute: Permute some keys of a table
--   p: list of oldkey=newkey
--   t: table to permute
-- returns
--   u: permuted table
function permute(p, t)
  local u = {}
  for i, v in t do
    if p[i] then
      u[p[i]] = t[i]
    else
      u[i] = t[i]
    end
  end
  return u
end

Also, a non-obvious implementation of shallow copy:

-- curry: Partially apply a function
--   f: function to apply partially
--   a1 .. an: arguments to fix
-- returns
--   f_: function with ai fixed
function curry(f, ...)
  local fix = arg
  return function (...)
           return call(%f, %fix .. arg)
         end
end

-- clone: Make a shallow copy of a table, including any tag
--   t: table
-- returns
--   u: copy of table
clone = curry(merge, {})

Then the object bit is almost trivial:

-- Prototype-based objects

-- Root object
Object = {_init = {}}
settag(Object, newtag())
copytagmethods(tag(Object), tag({})) -- copy table tag methods

-- Object constructor
--   init: table for constructor
-- returns
--   o: new object
function Object:_clone(init)
  return self + permute(self._init, init)
end

-- Sugar instance creation
settagmethod(tag(Object), "function",
             function (o, init)
               return o:_clone(init)
             end)

Usage:

Point = Object + {_proto = {"x", "y"}} -- add x and y fields to the
                                       -- constructor
p = Point{3,4}
ColorPoint = Point + {color = "white"} -- color is not in the constructor
c = ColorPoint{5,6}
c.color = "red"

One wrinkle I just came across: if you want to simply copy a class without
adding new fields (e.g. if you just want to add methods; I prefer to write
these using : notation at the top level), you should use:

NewPoint = clone(Point)

not

NewPoint = Point{}

because although the default constructor function doesn't mind you
omitting fields, some classes might do more in the constructor, and hence
might require the fields to be supplied.

[An aside that struck me as I was desigining this stuff: wouldn't it be
nice if function calls used tables instead of tuples? More regular, and
you could give a mixture of positional and non-positional arguments. You
could also do away with multiple assignment; instead, functions and
assignment could use table assignment:

function f{a,b=1;c=10} ... end

can be called as

f{1,3} -- a = 1, b = 3, c = 10

or

f{1}  -- a = 1, b = 1, c = 10

or

f{} -- a = nil, b = 1, c = 10

or

f{1;c=5} -- a = 1, b = 1, c = 5

{a,b} = f(3)

where f says "return {x,y}"

would set a = x, b = y

This may set efficiency alarm bells ringing, as all the RHS tables need to
be constructed (the LHS tables are just patterns which I make look like
tables for consistency; perhaps it would be better to make them look like
tuples instead), but personally I don't care. I want everything to be
tables! (Recall my suggestion about replacing code blocks with tables of
statements.)
]

-- 
http://sc3d.org/rrt/ | egrep, n.  a bird that debugs bison