4

假设我有一些我在别处定义的“对象”。也许它代表一组项目,但比简单的表格更复杂。无论它是什么,迭代它都是合乎逻辑的。

因此,它iterator定义了一个方法。所以我可以这样写:

local myObject = AbstractObject:new()

for obj in myObject:iterator() do
    obj:foo()
end

我想知道是否有一些我可以做的元方法技巧,这将允许我写这个:

local myObject = AbstractObject:new()

for obj in myObject do
    obj:foo()
end

那么有吗?

4

2 回答 2

3

对您的示例稍作改动将使语义不那么痛苦:

local myObject = AbstractObject:new()

for obj in myObject() do
    obj:foo()
end

这样,您可以使用元表来定义__call要返回的元方法myObject:interator(),代码如下所示AbstractObject:new()

setmetatable(newobject, {__call = function() return newobject:iterator() end})

如果没有迭代器构造,您将有效地重用单个迭代器进行多次迭代,这意味着您需要将迭代器状态保留在对象/创建闭包中,并在完成后将其重置,以便下一次调用将重新开始迭代再次。如果您真的想这样做,最好的解决方案实际上是为特定的迭代实现编写一些东西,但这将执行通用迭代:

local iterator

--table.pack is planned for 5.2
local pack = table.pack or function(...)
  local t = {...}
  t.n = select('#',...)
  return t
end

--in 5.1 unpack isn't in table
local unpack = table.unpack or unpack

function metamethods.__call(...)
  if not iterator then
    iterator = newobject:iterator()
  end

  local returns = pack(iterator(...))

  if returns[1] == nil then
    --iteration is finished: next call will restart iteration
    iterator = nil
  end
  return unpack(returns, 1, returns.n)
end

再说一遍:这确实应该进行调整以适合您的用例。

于 2011-05-24T16:25:29.023 回答
0

之后使用的对象in必须是一个函数,该函数将被泛型for循环重复调用。

我不确定您是否可以使表或用户对象像函数一样可调用,但即便如此,问题仍然是您的对象只能具有一个内部迭代器状态 - 即它不允许对同一个对象进行多次迭代(两者都不同时或顺序),除非您以某种方式明确地重置它。

正如 Stuart 所回答的那样,您可以__call适当地使用元方法来返回迭代器,但是您必须编写

for obj in myObject() do
    obj:foo()
end

这不是我们想要的。

在PiL中阅读更多内容,我发现 for 循环中使用了更多组件:不变的循环状态和控制变量的当前值,它们在每次调用中传递给迭代器函数。如果我们不在in表达式中提供它们,它们将被初始化为nil.

因此,我的想法是使用这些值来区分各个调用。

如果您可以next(element)为您的集合创建一个函数,该函数为每个元素返回下一个元素,那么实现将很简单:

metatable.__call = function(_state, _last)
    if(_last == nil) then
       return obj:first()
    else
       return obj:next(_last)
   end
end

但通常我们不会有这样的东西,然后它变得更加复杂。


我考虑过在这里使用协程,但是这些仍然需要工厂方法(我们要避免)。这将导致类似于 Stuart 所写的内容(即将迭代器状态保存在对象本身的某处或与对象相关的其他变量中),并使用参数和/或迭代器结果来决定何时创建/清理迭代器对象/状态。

这里什么都没赢。

于 2011-05-24T16:24:32.673 回答