-
-
Notifications
You must be signed in to change notification settings - Fork 5.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a set-up step before iterate
#46802
Comments
As pointed out in Mike Innes' post Iterate on It, this also allows you to create a destructive iterator, then GC the original object. |
It is unnecessary, since if you have a stateful iterator, you just return that as the |
Hm, that would make my original use-case somewhat unnecessary (although significantly uglier to implement), but it wouldn't fix the use-case from Mike's blog post (thanks @jakobnissen I had forgotten about this!). If I used your trick @vtjnash with the |
there is no reference if |
Does that rely on |
Isn't this sort of going back to the old iteration protocol..? |
Not really, no. It's just an optional hook that lets you choose what object you actually iterate over. |
GC'ing appears to rely on inlining. In the following example: mutable struct Foo
x::Int
function Foo(x::Int)
y = new(x)
println("Creating $(repr(objectid(y)))")
finalizer(i -> println("Destroying $(repr(objectid(i)))"), y)
end
end
Base.length(x::Foo) = x.x
Base.iterate(x::Foo) = (x.x, x.x-1)
@noinline function Base.iterate(x::Foo, state)
iszero(state) && return nothing
GC.gc()
(state, state-1)
end
f(x) = foreach(println, Foo(x)) No GC is performed during the loop IMO, if it was possible to GC in the un-inlined case, then Jameson is correct and there would be no need for a setup function. |
Just wanted to point out that this was discussed at the time, so there's some extensive discussion here: #25261 (comment). In general, I'm not necessarily opposed to it, but I'm not sure the justification is all that strong, since I don't think it would let you implement anything that couldn't be implemented right now, while simultaneously imposing an extra method resolution on every single loop in the system (which isn't terrible, but should have some reason for it). |
Edit: Nvm, I think I was doing something wrong. |
Fixed I guess with SetupIterator.jl mutable struct Foo
x::Int
function Foo(x::Int)
y = new(x)
println("Creating $(repr(objectid(y)))")
finalizer(i -> println("Destroying $(repr(objectid(i)))"), y)
end
end
Base.length(x::Foo) = x.x using SetupIterator
SetupIterator.iterator(f::Foo) = f.x:-1:1
@setup_iterate Foo julia> function f(n)
for x ∈ Foo(n)
GC.gc()
@show x
end
end
f (generic function with 1 method)
julia> f(5)
Creating 0x7dbd75374892a811
Destroying 0x7dbd75374892a811
x = 5
x = 4
x = 3
x = 2
x = 1
|
Currently, when one writes a loop over an iterator, it gets lowered from
to the equivalent of
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 justidentity
, and then we could lower the loop down toThis 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
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, becauseBase.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
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:
which is considerably more work, and is just a bunch of pointless boiler-plate just to forward an iteration specification to an existing iterator.
The text was updated successfully, but these errors were encountered: