lua-users home
lua-l archive

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



> -  Lua functions are _mutable_ objects (their environment can be changed)
> but I can't think of a way to make a copy of one.  So that's a real
problem.

Some time ago I wrote a serialisation system for functions (that was for
Lua 4). The way I did it was to keep a copy of the source code for the
function, so I could recompile it when necessary. I imagine that would work
for this problem, too. It meant modifying the source Lua code, though: the
transformation was fairly simple for Lua 4, and will be slightly more
complex for Lua 5, which has a richer "function" syntax. Basically I
converted, for example:

  function foo (a,b,c) <chunk> end

into

  foo = Function (upvalues) {a, b, c} [[ chunk ]]

where Function (aside from some functional games to allow the above syntax)
compiled the function, stored the source in a table with the closure as a
key, and returned the closure. (It was more complicated than that because
it had to handle upvalues, and for my application it was necessary in order
to relate closures to versions of the closures with bound arguments) In Lua
5, this could be improved upon by making the internal table weak, although
that is not guaranteed to succeed on circular references to, for example,
upvalues.

In Lua 4, it was relatively easy to handle upvalues because they could be
identified in a straightforward manner, and only their value was important.
However, in Lua 5, upvalues present a more intricate parsing issue and
would make it somewhat difficult to deep copy contained functions.

>  Copying a table should at least take its metatable into account.
Whether
> or not you should deep-copy the metatable will depend on the application
you
> have in mind I guess.  If the metatable is protected then this is another
> no-go...

Yes, that is an issue. Lua's metatable protection mechanism is slightly
blunt; I wonder if a more nuanced mechanism wouldn't perhaps be better. I
was thinking of, for example, __getmetatable and __setmetatable
metamethods, which would be used to implement the getmetatable and
setmetatable operations. These would take one additional parameter, a
"secret", which they could use to decide whether to produce/set the
metatable.

Guaraná (another interesting Brasilian software project) secures its
primitive for setting objects' MOPs (metaobject protocol): it requires that
you provide the current MOP if you want to change MOPs, and then lets the
MOP decide. (It doesn't have a getMOP primitive, I don't think, so the MOP
can be used as its own secret.) This got me thinking about possibilities
for Lua; the existence of getmetatable makes the use of the metatable as
its own key impossible, but really unique object could be used as a key
with a little ingenuity (the association of keys with metatables could be
through an internal weak-table, so that having a metatable wouldn't give
you the key to change it). It might be better to provide
getmetamethod(object, key[, secret]) instead of/as well as getmetatable.

Under many circumstances, you will not want to deep-copy metatables (they
might be surrogate type definitions) and I don't see any a priori way of
knowing whether it is desirable or not. In some cases, the object will be
unusable if you copy the metatable; in others, it will be unusable if you
do not. Probably you should implement a __deepcopy metamethod.

Instead of deepcopy, maybe what you want is lazycopy [note 1]. These have
some advantages [note 3] and some disadvantages [notes 2 and 3]; they might
be faster if the objects are large.

Here is a naive implementation which doesn't worry about circular
references or preserving object identity, which might be desirable
additions; however it will at least not blow up on circular references.

local function lazytablecopy(old)
  return setmetatable({}, {__index =
    function (t, k)
      local newvalue = lazycopy(old[k])
      t[k] = newvalue
      return newvalue
    end
  }
end

function lazycopy(t)
  if type(t) ~= "table" then return t
  else return lazytablecopy(t)
end


[note 1] You could implement lazytablecopy(t) as (memoise (compose
'lazycopy (curry '__get t))) if you like to think lispishly.

[note 2] Lazy copies cannot be iterated without some additional work. See
previous mailing list discussion.

[note 3] Lazy copies respect the __get pseudometamethod but do not respect
other metamethods. Adding support for arithmetic metamethods is not
complicated, but metamethod identity rules make proxying of comparison
metamethods difficult. The above implementation assumes that you do not
want to duplicate the __newindex metamethod; it is not easy to see how to
translate it to a new table, though.