假设我有一些在其他地方定义的"对象"。也许它代表一组项目,但比一个简单的表更复杂。无论它是什么,迭代它都是合乎逻辑的
因此,它定义了一个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
那是吗?
对您的示例进行一个微小的更改将使语义不那么痛苦:
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
再次:这应该真正进行调整,以适应您的用例。
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所写的内容(即将迭代器状态保存在对象本身或与对象相关的其他变量中的某个位置),并使用参数和/或迭代器结果来决定何时创建/清理迭代器对象/状态。
这里什么都没赢。