I’m trying understand type stability of functions like
f(t::Tuple) = tuple(t...)
g(t::Tuple) = tuple((identity(l) for l in t)...)
identity here is meant to be a place-holder for any type stable function. Of the two, f is stable, g is not:
julia> t = (:a, 1, 0.1);
julia> @code_warntype f(t)
Variables
#self#::Core.Compiler.Const(f, false)
t::Tuple{Symbol,Int64,Float64}
Body::Tuple{Symbol,Int64,Float64}
1 ─ %1 = Core._apply_iterate(Base.iterate, Main.tuple, t)::Tuple{Symbol,Int64,Float64}
└── return %1
julia> @code_warntype g(t)
Variables
#self#::Core.Compiler.Const(g, false)
t::Tuple{Symbol,Int64,Float64}
Body::Tuple{Vararg{Union{Float64, Int64, Symbol},N} where N}
1 ─ %1 = Base.Generator(Main.identity, t)::Base.Generator{Tuple{Symbol,Int64,Float64},typeof(identity)}
│ %2 = Core._apply_iterate(Base.iterate, Main.tuple, %1)::Tuple{Vararg{Union{Float64, Int64, Symbol},N} where N}
└── return %2
There are two, possibly related, things I don’t understand here. First, what exactly does tuple(t...) do? I would have expected this to iterate through t, and pass the results onto tuple. But I guess that’s not what’s happening, since iterate doesn’t seem to be type stable:
julia> @code_warntype iterate(t)
Variables
#self#::Core.Compiler.Const(iterate, false)
t::Tuple{Symbol,Int64,Float64}
Body::Union{Nothing, Tuple{Union{Float64, Int64, Symbol},Int64}}
1 ─ nothing
│ nothing
│ %3 = (#self#)(t, 1)::Union{Nothing, Tuple{Union{Float64, Int64, Symbol},Int64}}
└── return %3
So what exactly does splatting do then, and is this explained somewhere in the manual?
Second, for g, I’ve come to the conclusion that its type instability is probably caused by the compiler not being able to infer the length of a Base.Generator{NTuple{N,T}} from its type. Is there a reason why this is so? Could there be something like a value for IteratorSize called TypeHasLength() that would be a guarantee that length(::T) = length(T) for this type T, i.e. the length is inferable from the type? This seems to me like it would make a lot of sense for Tuples, and probably also for a lot of user-defined composite types that have a number of fields, and iterate just goes through them. This is actually the original use case that got me thinking about all this: A user-defined, iterable type for which I would like a function like g to be type stable.