I have the following function, and I’ve written two return statements which are identical in an input-output sense, but they have different syntax and mechanics. I like the format of the former statement because it’s a generator, and there’s less syntax there so it’s clearer. But the former return statement is not type stable, until I run the second return statement! The latter return statement is always type stable.
function lagrangepolynomial(x::AbstractVector, y::AbstractVector, z::Number = Symbolics.variable(:z))
n = length(x)
δⱼ(z,x,j,k) = (z - x[k]) / (x[j] - x[k])
Pⱼ(z,x,j,n) = prod(δⱼ(z,x,j,k) for k in 1:n if k != j)
# NOT type stable...
return sum((y[j] * Pⱼ(z,x,j,n)) for j in 1:n)
# Type stable
return sum(map(j -> y[j] * Pⱼ(z,x,j,n), 1:n))
end
Minimum Working Example
using Symbolics
@variables z
x = -1:0.1:1
y = cos.(x)
n = length(x)
# Both of these are type stable!
δⱼ(z,x,j,k) = (z - x[k]) / (x[j] - x[k])
Pⱼ(z,x,j,n) = prod(δⱼ(z,x,j,k) for k in 1:n if k != j)
L1(z,x,y,n) = sum(y[j] * Pⱼ(z,x,j,n) for j in 1:n)
L2(z,x,y,n) = sum(map(j -> y[j] * Pⱼ(z,x,j,n), 1:n))
The first function is type unstable…
julia> @code_warntype L1(z,x,y,n)
MethodInstance for L1(::Num, ::StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}, ::Vector{Float64}, ::Int64)
from L1(z, x, y, n) in Main at REPL[19]:1
Arguments
#self#::Core.Const(L1)
z::Num
x::StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}
y::Vector{Float64}
n::Int64
Locals
#25::var"#25#26"{Num, StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}, Vector{Float64}, Int64}
Body::Any
1 ─ %1 = Main.:(var"#25#26")::Core.Const(var"#25#26")
│ %2 = Core.typeof(z)::Core.Const(Num)
│ %3 = Core.typeof(x)::Core.Const(StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64})
│ %4 = Core.typeof(y)::Core.Const(Vector{Float64})
│ %5 = Core.typeof(n)::Core.Const(Int64)
│ %6 = Core.apply_type(%1, %2, %3, %4, %5)::Core.Const(var"#25#26"{Num, StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}, Vector{Float64}, Int64})
│ (#25 = %new(%6, z, x, y, n))
│ %8 = #25::var"#25#26"{Num, StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}, Vector{Float64}, Int64}
│ %9 = (1:n)::Core.PartialStruct(UnitRange{Int64}, Any[Core.Const(1), Int64])
│ %10 = Base.Generator(%8, %9)::Core.PartialStruct(Base.Generator{UnitRange{Int64}, var"#25#26"{Num, StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}, Vector{Float64}, Int64}}, Any[var"#25#26"{Num, StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}, Vector{Float64}, Int64}, Core.PartialStruct(UnitRange{Int64}, Any[Core.Const(1), Int64])])
│ %11 = Main.sum(%10)::Any
└── return %11
The second function IS type stable…
julia> @code_warntype L2(z,x,y,n)
MethodInstance for L2(::Num, ::StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}, ::Vector{Float64}, ::Int64)
from L2(z, x, y, n) in Main at REPL[20]:1
Arguments
#self#::Core.Const(L2)
z::Num
x::StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}
y::Vector{Float64}
n::Int64
Locals
#27::var"#27#28"{Num, StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}, Vector{Float64}, Int64}
Body::Num
1 ─ %1 = Main.:(var"#27#28")::Core.Const(var"#27#28")
│ %2 = Core.typeof(z)::Core.Const(Num)
│ %3 = Core.typeof(x)::Core.Const(StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64})
│ %4 = Core.typeof(y)::Core.Const(Vector{Float64})
│ %5 = Core.typeof(n)::Core.Const(Int64)
│ %6 = Core.apply_type(%1, %2, %3, %4, %5)::Core.Const(var"#27#28"{Num, StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}, Vector{Float64}, Int64})
│ (#27 = %new(%6, z, x, y, n))
│ %8 = #27::var"#27#28"{Num, StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}, Vector{Float64}, Int64}
│ %9 = (1:n)::Core.PartialStruct(UnitRange{Int64}, Any[Core.Const(1), Int64])
│ %10 = Main.map(%8, %9)::Vector{Num}
│ %11 = Main.sum(%10)::Num
└── return %11
And now, the first function is type stable? Why?
julia> @code_warntype L1(z,x,y,n)
MethodInstance for L1(::Num, ::StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}, ::Vector{Float64}, ::Int64)
from L1(z, x, y, n) in Main at REPL[8]:1
Arguments
#self#::Core.Const(L1)
z::Num
x::StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}
y::Vector{Float64}
n::Int64
Locals
#5::var"#5#6"{Num, StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}, Vector{Float64}, Int64}
Body::Num
1 ─ %1 = Main.:(var"#5#6")::Core.Const(var"#5#6")
│ %2 = Core.typeof(z)::Core.Const(Num)
│ %3 = Core.typeof(x)::Core.Const(StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64})
│ %4 = Core.typeof(y)::Core.Const(Vector{Float64})
│ %5 = Core.typeof(n)::Core.Const(Int64)
│ %6 = Core.apply_type(%1, %2, %3, %4, %5)::Core.Const(var"#5#6"{Num, StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}, Vector{Float64}, Int64})
│ (#5 = %new(%6, z, x, y, n))
│ %8 = #5::var"#5#6"{Num, StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}, Vector{Float64}, Int64}
│ %9 = (1:n)::Core.PartialStruct(UnitRange{Int64}, Any[Core.Const(1), Int64])
│ %10 = Base.Generator(%8, %9)::Core.PartialStruct(Base.Generator{UnitRange{Int64}, var"#5#6"{Num, StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}, Vector{Float64}, Int64}}, Any[var"#5#6"{Num, StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}, Vector{Float64}, Int64}, Core.PartialStruct(UnitRange{Int64}, Any[Core.Const(1), Int64])])
│ %11 = Main.sum(%10)::Num
└── return %11