Skip to content

Add a set-up step before iterate #46802

@MasonProtter

Description

@MasonProtter

Currently, when one writes a loop over an iterator, it gets lowered from

for i in v
    ...
end

to the equivalent of

res = iterate(v)
while !isnothing(res)
    i, state = res
    ...
    res = iterate(v, state)
end

I 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)
end

This 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}}
end

which 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)
end

which is considerably more work, and is just a bunch of pointless boiler-plate just to forward an iteration specification to an existing iterator.

Metadata

Metadata

Assignees

No one assigned

    Labels

    iterationInvolves iteration or the iteration protocol

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions