Hey, so suppose I have a struct
struct Foo
a::Int
b::Float64
end
and I basically want this thing to behave like a Tuple
under iteration. e.g. I'd like this to 'just work' and carry no runtime performance overhead.
f((a, b)) = a + b
What's a clean way to do this right?
If I do the naive thing, it's very slow:
Base.iterate(f::Foo, s=1) = s <= length(fieldnames(Foo)) ? (getfield(f, s), s+1) : nothing
let foo = Ref(Foo(1,2))
@btime f($foo[])
end
#+RESULTS:
802.830 ns (4 allocations: 112 bytes)
3.0
I feel there must be some clean way to basically say "iterate this thing as if it were a Tuple"
Okay, so if I don't define the above iterate
method and instead define
Base.indexed_iterate(f::Foo, i::Int, state=1) = (Base.@_inline_meta; (getfield(f, i), i+1))
everything is fast. Good to know I suppose.
You should probably still define iterate
as well though, since indexed_iterate
is just an implementation detail and is always assumed to return the same results as regular iteration
Still slower that indexed_iterate
, but better that the OP
@inline Base.iterate(f::Foo, s = 1) = s ≤ fieldcount(Foo) ? (getfield(f, s), s + 1) : nothing
Some LinearAlgebra types does this, I thing QR does, you can perhaps see how they do it?
Hm, it seems that @Pablo Zubieta's method is just as fast as indexed_iterate for me on 1.6.
julia> struct Foo
a::Int
b::Float64
end
julia> f((a, b)) = a + b
f (generic function with 1 method)
julia> @inline Base.iterate(f::Foo, s = 1) = s ≤ fieldcount(Foo) ? (getfield(f, s), s + 1) : nothing
julia> let foo = Ref(Foo(1,2))
@btime f($foo[])
end
1.859 ns (0 allocations: 0 bytes)
3.0
and
julia> struct Foo
a::Int
b::Float64
end
julia> f((a, b)) = a + b
f (generic function with 1 method)
julia> Base.indexed_iterate(f::Foo, i::Int, state=1) = (Base.@_inline_meta; (getfield(f, i), i+1))
julia> let foo = Ref(Foo(1,2))
@btime f($foo[])
end
1.849 ns (0 allocations: 0 bytes)
3.0
Oh. I was trying on 1.5.3, great to see that the compiler is also better here in 1.6.
I think with https://github.com/JuliaLang/julia/pull/39285 you wouldn’t even need an iterate method, just a semicolon: f((;a, b)) = a + b
Wow, that’s very neat. Kinda niche though, since you need to bind the variables to the same name as the property names.
There was some discussion in https://github.com/JuliaLang/julia/issues/28579 about having some more advanced matching as well. Wouldn't be too hard to add, but would need to come to a decission on how exactly that should work.
Last updated: Nov 06 2024 at 04:40 UTC