让我说我有一些我在别处定义的“对象”。也许它代表一组项目,但比简单的表格更复杂。无论它是什么,迭代它都是合乎逻辑的。
因此,它定义了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
那是吗?
答案 0 :(得分: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
再次:这应该真的进行调整,以适应您的使用案例。
答案 1 :(得分:0)
in
之后使用的对象必须是一个函数,通用for
循环将重复调用该函数。
我不确定你是否可以使一个表或用户对象像函数一样可调用,但即使这样,问题就是你的对象只能有一个内部迭代器状态 - 即它不会允许多次迭代对象(既不是同时也不是顺序),除非你以某种方式明确地重置它。
正如Stuart所说,你可以适当地使用__call
metamethod来返回迭代器,但是你必须写
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所写的东西(即在对象本身或与对象相关的其他变量中保存迭代器状态),并使用参数和/或迭代器结果来决定何时创建/清除迭代器对象/状态。
这里没有赢得。