lua-users home
lua-l archive

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


John Passaniti escribió:

> Here's something that's come up in some code I'm writing.

<edited>

>     Parent = {this=1, that=2}
>     Child = {other=3}
>     setmetatable(Child, {__index = Parent})

> Now let's iterate over the keys in Child:

>     table.foreach(Child, print)

> We get:

>     other       3

> Does anyone have an elegant way to deal with this?

> (Maybe this suggests a new metamethod?  One way this could work is if
> tables had a metamethod related to indexing a table.  Such a metamethod
> would have application beyond this message's example.)

Yes, I agree. I proposed that ages ago (the proposal is still on the Wiki)
but the principle of least metamethods seems to have overridden it.

OK, I have a couple of solutions:

1) Write your own table.foreach which consults the metatable of the target
table for a metamethod of your choice.

2) Instead of using table.foreach, use a for loop. Then redefine pairs to
consult the metatable of the target table for a metamethod of your choice.

3) Or just write your own iterator function and use it in the for loop.

Things to be aware of in Lua 5beta:

1) table.foreach effectively does a rawget on the target table. If that is
not appropriate for your table (perhaps it is a pure proxy table), don't
use table.foreach. Or write your own, as above.

2) pairs(x) returns next, x, nil. "next" is looked up in the current
C-global environment, which will be the current globals table if you have
never done a setglobals but will be the original globals table unless some
C code has changed it. If that sounds complicated, it is because it is, but
basically if you never do setglobals() you don't have to worry about and:

3) that means you can change the behaviour of pairs(x) by redefining "next"
provided you have not yet done a setglobals()

4) "for k, v in t do" is exactly equivalent to "for k, v in next, t, nil
do" providing that t is a table, whether or not it has a metatable. If it
is not a table, it better be a function which doesn't expect any arguments,
because that is what will happen. "next" is looked up in the same way as it
would be done by "pairs" so you get the same result; you can change what it
does by redefining next (provided you haven't done setglobals())

Now, having said all that, I don't think that redefining "next" is a good
idea. I think it makes more sense to redefine "pairs". But if you do that,
it will not apply to the "compatibility for" statement, so it is a good
idea to start writing "for k, v in pairs(t)" instead of "for k, v in t"
now, rather than when you have to.

By the way, the compatibility code (built in to the VM) does not check to
see if there are additional arguments to the for statement; just if the
first one is a table. So you cannot actually use objects with a __call
metamethod in a for statement, even if you are using the new syntax.
Presumably this restriction will be lifted once the compatibility code
disappears, but I am just guessing.

Here is a simple __pairs metamethod which might help you and a pairs
function to go with it: Note that this irrevocably ditches the existing
pairs function, reimplementing in Lua. This will have no impact on code
unless it is doing setglobals(), and I think the change probably makes the
behaviour more predictable.

do
  -- note: if t is not a table, the error will be caught by next,
  -- so we don't worry about error checking here. Perhaps next has
  -- been redefined to handle the case in question.

  local default = {__pairs = function(t) return next, t, nil end}
  function pairs(t)
    return (getmetatable(t) or default).__pairs(t)
  end
end

Now here is __pairs for the simple case where the __index metamethod is a
table:

do
  empty = {}
  local next = next
  function __pairs(parent)
    local child = (getmetatable(t) or empty).__index
    local function nexter(_, key)
      local k, v = next(parent, key)
      while not k and child do
        parent, child = child, (getmetatable(child) or empty).__index
      end
      return k, v end
    end
    return nexter, nil, nil
  end
end