Is there any smart way to write a lua object so that it doubles as an iterator?

Suppose I have some “object” that I defined elsewhere. Perhaps it is a collection of elements, but more complex than a simple table. Anyway, it would be logical to sort it out.

So it has an iterator method. So I can write this:

 local myObject = AbstractObject:new() for obj in myObject:iterator() do obj:foo() end 

I am wondering if there is any kind of metametal trickery that I can do that will allow me to write this:

 local myObject = AbstractObject:new() for obj in myObject do obj:foo() end 

That's it?

+4
source share
2 answers

One small change in your example will make semantics much less painful:

 local myObject = AbstractObject:new() for obj in myObject() do obj:foo() end 

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

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

Without building an iterator, you will effectively use one iterator for several iterations, which means that you need to save the iterator state in closing the object / creation and reset after completion, so that the next call will restart the iteration again. If you really want to do this, the best solution would be to write something for a specific iteration implementation, but this would do a general iteration:

 local iterator --table.pack is planned for 5.2 local pack = table.pack or function(...) local t = {...} tn = 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 customized according to your use case.

+3
source

The object used after in must be a function that will be called repeatedly by the general for loop.

I'm not sure that you can make the table or user object callable as a function, but even then the problem will be that your object can have only one internal state of the iterator - that is, it does not allow multiple iterations compared to the same object ( neither simultaneously, nor sequentially) if you somehow have not explicitly dropped it.

As Stuart replied, you could use the __call __call to return the iterator, but then you have to write

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

This is not exactly what we want.

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

So my idea would be to use these values ​​to distinguish between individual calls.

If you can create a next(element) function for your collection that returns the following for each element, the implementation will 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 complicated.


I thought about using coroutines here, but this still needs the factory method (which we want to avoid). This would lead to something similar, as Stuart wrote (i.e. saving the iterator state somewhere in the object itself or in some other variable associated with the object), and using the parameter and / or result of the iterators to decide when to create / clean the object and the state of the iterator.

Nothing is won here.

0
source

All Articles