lua-users home
lua-l archive

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

Here is another approach which uses Lua's amazing
tables-are-functions-are-tables worldview. Here I assume that it is ok to
memoise the result of function calls (which I think works in the case you
used as an example.)

The basic idea is that the object has a number of property fields which may
or may not be present. Each one has an associated function defined in terms
of other field(s). We assume that no function will return "false"; some
other marker value could be used but it would be uglier. The marker is
necessary to avoid endless recursion and to identify whether computations
have the necessary information to proceed or not.

Then we can define everything in terms of an __index metamethod:

function try_to_compute(table, key)
  local func = getmetatable(table)[key]
  if func then
    table.key = false       -- mark goal
    local val = func(table) -- try to get the value
    if val
      then table.key = val       -- got it, so record it
           return val            -- and return it
      else table.key = nil       -- nope, reset for next try
           return nil

Note that we use the metatable also for the functions. To set up an
object, we need to attach the metatable, and we need to include the
above function as the __index method. So here we go:

function Searcher(props, funcs)
  funcs.__index = try_to_compute
  return setmetatable(props, funcs)

If I were doing this in real life, I would put the definition of
try_to_compute in Searcher and use a closure instead of getmetatable.

I haven't tried this, but the basic idea would be (my math is a bit rusty
and I'm a bit sleep-deprived, so apologies in advance for typos and stupid

  local funcs = {}
  -- example of a property with two possible formula
  function funcs.radius(t)
    return (t.diameter and t.diameter / 2)
           or (t.circumference and t.circumference / 2 * math.pi
  function funcs.diameter(t)
    return t.radius and t.radius * 2
  function funcs.circumference(t)
    return t.diameter and t.diameter * math.pi
  function funcs.area(t)
    return t.radius and t.radius * t.radius * math.pi

  function Circle(props)
    return Searcher(props, funcs)

a = Circle {radius = 27}
b = Circle {circumference = 40}