-
-
Couldn't load subscription status.
- Fork 5.7k
Description
Currently, when one writes a loop over an iterator, it gets lowered from
for i in v
...
endto the equivalent of
res = iterate(v)
while !isnothing(res)
i, state = res
...
res = iterate(v, state)
endI wonder if there'd be any potential problems with generalizing this a bit by adding a function Base.to_iterator (or whatever) with a default method that is just identity, and then we could lower the loop down to
itr = Base.to_iterator(v) # <------ Setup step, default method just returns v
res = iterate(itr)
while !isnothing(res)
i, state = res
...
res = iterate(itr, state)
endThis should not change anything about existing iterators, but I believe that for certain cases, this would afford us some interesting optimization opportunities, and make the implementation of some iterators easier.
Some iteration protocols are just easier (or more efficient) to express in terms of a mutable object, but it can be wasteful to have to carry around a mutable container in your type to hold state just in case someone wants to iterate over it. In this way, if it's set up and torn down automatically, there are opportunities for the compiler to stack allocate all the mutable containers.
An example usecase:
Suppose I have the type
struct VectorOfVectors{T} <:AbstractVector{T}
data::Vector{Vector{T}}
endwhich I want to basically treat as a flat vector semantically.
Implementing iterate on this type is pretty painful, it requires juggling an inner state and an outer state for the outer vectors and the inner vectors, and trying to do this has a large chance of being wrong, or suboptimal. It's especially frustrating to write out this iterator, because Base.Iterators has already provided the wanted iteration protocol for this type: Iterators.flatten.
If we had a setup step like the one proposed, someone could just write
Base.to_iterator(vov::VectorOfVectors) = Iterators.flatten(vov)and be done with it. Currently, it seems (thanks @vtjnash for pointing out this is possible) that the current best way to re-use the work already done in base would be to write something like this:
function Base.iterate(vov::VectorOfVectors)
itr = Iterators.flatten(vov.data)
res = iterate(itr)
isnothing(res) && return nothing
x, state = res
return x, (itr, state)
end
function Base.iterate(vov::VectorOfVectors, (itr, state))
res = iterate(itr, state)
isnothing(res) && return nothing
x, state = res
return x, (itr, state)
endwhich is considerably more work, and is just a bunch of pointless boiler-plate just to forward an iteration specification to an existing iterator.