有没有一些聪明的方法来编写 lua 对象,使其兼作迭代器?

发布于 2024-11-09 03:23:13 字数 373 浏览 0 评论 0原文

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

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

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

那么有吗?

Lets say I have some "object" that I've defined elsewhere. Maybe it represents a set of items, but is more complex than a simple table. Whatever it may be, it would be logical to iterate over it.

As such, it has a iterator method defined. So I can write this:

local myObject = AbstractObject:new()

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

What I'm wondering is if there is some metamethod trickery that I can do, which will allow me to write this:

local myObject = AbstractObject:new()

for obj in myObject do
    obj:foo()
end

So is there?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

表情可笑 2024-11-16 03:23:13

对示例进行一个细微的更改将使语义变得不那么痛苦:

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

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

One slight change to your example would make the semantics a lot less painful:

local myObject = AbstractObject:new()

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

That way, you can use a metatable to define the __call metamethod to return myObject:interator(), with code that looks something like this in AbstractObject:new():

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

Without the iterator construction, you'll be effectively reusing a single iterator for multiple iterations, which means you'll need to keep the iterator state in the object/creation closure, and reset it after it finishes so the next call will restart the iteration again. If you really want to do this, the best solution would really be to write something for the specific iteration implementation, but this would perform the generic iteration:

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

Again: This should really be adjusted to fit your use case.

空‖城人不在 2024-11-16 03:23:13

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

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

正如斯图尔特所回答的,您可以适当地使用 __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 所写的内容(即将迭代器状态保存在对象本身的某个位置或与对象相关的其他变量中),并使用参数和/或迭代器结果来决定何时创建/清理迭代器迭代器对象/状态。

这里没有任何胜利。

The object used after in must be a function, which will be called repeatedly by the generic for loop.

I'm not sure if you can make a table or user object callable like a function, but even then the problem would be that your object can only have one internal iterator state - i.e. it would not allow multiple iterations over the same object (neither concurrently nor sequentially), unless you are somehow explicitly resetting it.

As answered by Stuart, you could use the __call metamethod suitably to return the iterator, but then you would have to write

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

This is not quite what we want.

Reading a bit more in PiL, I see that there are more components used in the for loop: the invariant loop state, and the current value of the control variable, which are passed to the iterator function in each call. If we don't provide them in the in expression, they are initialized to nil.

Thus, my idea would be to use these values to distinguish the individual calls.

If you can create a next(element) function for your collection which returns for each element the next one, the implementation would be simple:

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

But often we would not have something like this, then it gets more complicated.


I thought about using coroutines here, but these still need a factory method (which we want to avoid).
It would result in something similar like what Stuart wrote (i.e. saving the iterator state somewhere in the object itself or in some other variable related to the object), and using the parameter and/or the iterators result to decide when to create/clean the iterator object/state.

Nothing won here.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文