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 Tuple
s, 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.