Mutable Functions

lua-users home
wiki

Functions and closures implemented as first class objects can accomplish a wide variety of semantics, as we know from functional programming. Here is shown how to use closures to implement the semantics of Lua tables and objects, without using any Lua tables.

We first create a decorator function [1] called mutable, i.e. a function that returns a function that is a variant (wrapper) of the function passed in:

function mutable(func)

  local currentfunc = func

  local function mutate(func, newfunc)

    local lastfunc = currentfunc

    currentfunc = function(...) return newfunc(lastfunc, ...) end

  end

  local wrapper = function(...) return currentfunc(...) end

  return wrapper, mutate

end

Here, the function wrapper provides a level of indirection to the original function (currentfunc) and allows the identity of currentfunc, which is an up-value, to be mutated. During mutation, we allow the function replacing another function to know the identity of the function it is replacing, thereby allowing cascading effects where one function overrides or filters the behavior of the previous functions.

Example usage:

local sqrt, mutate = mutable(math.sqrt)

assert(sqrt(4) == 2)

assert(sqrt(-4) ~= sqrt(-4)) -- NaN

mutate(sqrt, function(old, x) return x < 0 and old(-x) .. "i" or old(x) end)

assert(sqrt(4) == 2)

assert(sqrt(-4) == "2i")

There's probably not much use doing the above rather than

local function sqrt(x) return x < 0 and math.sqrt(-x) .. "i" or math.sqrt(x) end

However, here's how we can simulate table semantics with functions:

local t, mutate = mutable(function() end)

mutate(t, function(old, x) if x == 1 then return "first" else return old(x) end end)

mutate(t, function(old, x) if x == 2 then return "second" else return old(x) end end)

mutate(t, function(old, x) if x == 3 then return "third" else return old(x) end end)

assert(t(1) == "first" and t(2) == "second" and t(3) == "third")

The syntax for setting and the efficiency are lacking of course, but we gain in a more general semantics:

local t, mutate = mutable(function() end)

mutate(t, function(old, x,y) if x == 1 then return "first"

                                       else return old(x,y) end end)

mutate(t, function(old, x,y) if x == 2 then return "second"

                                       else return old(x,y) end end)

mutate(t, function(old, x,y) if x > 2  then return "large number"

                                       else return old(x,y) end end)

mutate(t, function(old, x,y) if y ~= 0 then return "off axis", math.sqrt(x^2+y^2)

                                       else return old(x,y) end end)

assert(t(1,0) == "first" and t(2,0) == "second" and t(5,0) == "large number" and

       t(3,4) == "off axis")

assert(select(2, t(3,4)) == 5)

We now have the fallback semantics of metamethods (e.g. __index) as well as the ability to index and return multiple values, the latter of which we didn't have before with Lua tables.

Lets clean up the syntax with a few helper functions that wrap mutate:

local SET = function() end    -- unique key

local MUTATE = function() end -- unique key



-- decorator function for adding methods.

function mutable_helpers(func, mutate)

  mutate(func, function(old_func, ...)

    if select(1, ...) == SET then

      local k = select(2, ...)

      local v = select(3, ...)

      mutate(func, function(old_func, ...)

        if select(1, ...) == k then return v

        else return old_func(...) end

      end)

    else

      return old_func(...)

    end

  end)

  mutate(func, function(old_func, ...)

    if select(1, ...) == MUTATE then

      local new_func = select(2, ...)

      mutate(func, function(old_func, ...)

        return new_func(old_func, ...)

      end)

    else

      return old_func(...)

    end  

  end)

  return func

end

The mutable_helpers is a decorator function that adds support for what are semantically method calls on a mutable function representing an object. These methods are SET (for setting a table value) and MUTATE (alternate syntax for the mutate function). The SET and MUTATE are unique keys identifying the methods. These take advantage of the fact that functions are objects, which have unique identities (in special cases, strings might have been used instead for keys--e.g. "set" and "mutate").

So, we can now use method-like calls in the form of message passing to access the simulated table:

local t = mutable_helpers(mutable(function() end))

t(MUTATE, function(old, ...)

  local x = select(1, ...)

  if type(x) == "number" and x > 2 then return "large" else return old(...) end

end)

t(SET, 1, "first")

t(SET, 2, "second")

assert(t(1) == "first", t(2) == "second", t(5) == "large")

Optionally, we can modify the default metatable on functions to use regular Lua table syntax. (This is the only time a real Lua table is used, but it is only an artifact of the Lua meta mechanism for supporting the table syntax, and it could be avoided by some patch to Lua.)

-- Enable table get/set syntax.

-- Warning: uses debug interface

function enable_table_access()

  local mt = {

    __index    = function(t,k)   return t(k) end,

    __newindex = function(t,k,v) return t(SET, k, v) end,

  }

  debug.setmetatable(function() end, mt)

end

A table constructor helper function will also be defined:

function T() return mutable_helpers(mutable(function() end)) end

Example usage:

local t = T()

t[1] = "first"

t[2] = "second"

t[3] = "third"

assert(t[1] == "first" and t[2] == "second" and t[3] == "third" and t[4] == nil)

So, in terms of expression, this suggests that tables are not a necessary feature of Lua, and it may well be possible to remove them entirely from the language, though we likely wouldn't want to do that for efficiency concerns.

More practically, maybe this suggests that the concepts of tables and functions could be further unified in the language, though, for efficiency, preserving the distinction in the underlying implementation.

-- setting properties on an object

obj.color = "blue"

obj["color"] = "blue"

obj:size(10,20,30)        -- traditional syntax, method call style

obj("size") = (10,20,30)  -- multivalued setter syntax, function style

obj["size"] = (10,20,30)  -- multivalued setter syntax, table style

obj.size    = (10,20,30)  -- multivalued setter syntex, table property style

x,y = obj("position")     -- multivalued getter syntax, function style

x,y = obj["position"]     -- multivalued getter syntax, table style

x,y = obj.position        -- multivalued getter syntex, table property style 

obj[10,20] = 2            -- multivalued keys, table style

obj(10,20) = 2            -- multivalued keys, function style

Related discussion: LuaList:2007-07/msg00177.html - "Multiple return value in __index metamethod"

--DavidManura

See Also


RecentChanges · preferences
edit · history
Last edited May 2, 2009 1:19 am GMT (diff)