Consider the following function that, given a tuple t
, computes partial products of the elements specified by a second tuple parts
as follows
function partialprods(t::NTuple{N,Int}, parts::NTuple{M,Int}) where {N,M}
all(0 ≤ i ≤ length(t) for i ∈ parts) || error("Dimension out of range.")
issorted(parts) || error("`parts` must be sorted in ascending order")
ntuple(i -> prod(t[parts[i]+1 : parts[i+1]]), Val(M-1))
end
julia> partialprods((1,2,3,4), (0,2,4))
(2, 12)
That is, adjacent elements of parts
specify ranges over which to compute the product, and partialprods
outputs a tuple containing them.
I observed the following interesting behavior where partialprods
is only sometimes type stable (in Julia 1.6.0)
julia> wrapper1() = partialprods((1,2,3,4), (0,2,4))
wrapper1 (generic function with 1 method)
julia> wrapper1()
(2, 12)
julia> @code_warntype wrapper1()
Variables
#self#::Core.Const(wrapper1)
Body::Tuple{Int64, Int64}
1 ─ %1 = Core.tuple(1, 2, 3, 4)::Core.Const((1, 2, 3, 4))
│ %2 = Core.tuple(0, 2, 4)::Core.Const((0, 2, 4))
│ %3 = Main.partialprods(%1, %2)::Core.Const((2, 12))
└── return %3
julia> wrapper2() = partialprods((1,2,3,4), (1,3,4))
wrapper2 (generic function with 1 method)
julia> wrapper2()
(6, 4)
julia> @code_warntype wrapper2()
Variables
#self#::Core.Const(wrapper2)
Body::Tuple{Any, Any}
1 ─ %1 = Core.tuple(1, 2, 3, 4)::Core.Const((1, 2, 3, 4))
│ %2 = Core.tuple(1, 3, 4)::Core.Const((1, 3, 4))
│ %3 = Main.partialprods(%1, %2)::Tuple{Any, Any}
└── return %3
I would be grateful to anyone who could explain to me what I’ve done that introduces the type instability here. It seems the output type of partialprods
should be inferrable from the length of parts
, but it is not (always). Note that the first case seems special in that I could not identify another that gave type stable behavior.