lua-users home
lua-l archive

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

On Sun, Nov 15, 2009 at 11:59:20AM +0200, steve donovan wrote:
> Basically, there is a need to separate method lookup from field
> lookup, but in such a way that the old method is always available.
> Here is the problem case: say you wish to wrap a set as an object. Say
> also that you want s[val] to _reliably and always_ indicate the
> presence of val in the set s.  Currently, this cannot be be done.  If
> our set has an intersection() method, then s['intersection'] will
> always be a false positive, because we look up 'intersection' in the
> set's metatable if lookup fails.  So we have to write this operation
> as a method call, say s:get(val) or s:exists(val) that uses rawget
> internally, which isn't so pretty or intuitive.

A different pattern, which I like better and is achievable within Lua as
it is today, is just don't bind methods to objects you want to permit
arbitrary data keys on.

Set = {}
setmetatable(Set, {__call = function(cls,...)
    local obj = {}
    for i=1,select('#',...) do
        obj[select(i,...)] = true
    return setmetatable(obj, Set)
function Set:intersection(other)
    -- blah blah --

-- make some sets --
s1,s2 = Set(...), Set(...)
-- get their intersection --
Set.intersection(s1, s2)

That is, we don't ever make getmetatable(s1).__index == Set. You call
Set.intersection(s1,...) not s1:intersection(...). In the same way you call
table.remove(tbl,...) not tbl:remove(...).

If you want inheritance, you can wrap that in too. You're just giving up
the object-oriented syntax; you don't have to give up its power.

-- One way to get inheritance

local class_sentinel = {}

setmetatable(Set, {__call = function(cls, ...)
    local obj = {}
    for i=1,select('#',...) do
        obj[select(i,...)] = true
    obj[class_sentinel] = cls
    return setmetatable(obj, Set)
local function _intersection(self, other)
    local cls = self[class_sentinel]
    -- we assume that subclasses will have their bases as __index, so
    -- cls.intersection is always defined, even though it may just
    -- be == Set.intersection
    local meth = cls.intersection
    if meth ~= _intersection then
        return meth(self, other)
        -- default Set.intersection implementation here --
Set.intersection = _intersection