I am writing a code in which the input file is a Julia script, and the user may provide some input in the form of functions (of one or several variables).
The code may use these functions by calling them with Float64, or with other datatypes. I have automatic differentiation in mind, but for this MWE, let us say it’s Int64 vs. Float64.
Now, most input functions would use all their inputs
f = (x,y) -> x^2+y
but the user may say “Oh the input parameters do not really matter”
g = (x,y) -> 1
This causes me two types of problems
a) If I call an input function with two Float64, two Int64, or with an Int64 and a Float64. I need to rely on the output being promote_type(typeof(x),typeof(y)), that is, Float64.
Without countermeasures, calling g would wreak havoc in my dispatching logic further down the line. That issue, I can solve:
promote_eltype(v...) = promote_type(eltype.(v)...)
convert_eltype(a,v...) = convert(promote_eltype(a,v...),a)
typestable(f,x...) = convert_eltype(f(x...),x...)::promote_eltype(x)
F(x...) = typestable(g,x...)
G(x...) = typestable(f,x...)
So if I use F and G in place of f and g, my software does the computations I want.
b) But it does it slowly. Behold:
> @code_warntype typestable(f,3,2.)
Variables:
#self#::#typestable
f::##49#50
x::Tuple{Int64,Float64}
Body:
begin
SSAValue(0) = (Core.getfield)(x::Tuple{Int64,Float64}, 1)::Int64
return (Base.add_float)((Base.sitofp)(Float64, (Base.mul_int)(SSAValue(0), SSAValue(0))::Int64)::Float64, (Core.getfield)(x::Tuple{Int64,Float64}, 2)::Float64)::Float64
end::Float64
> @code_warntype F(3,2.)
Variables:
#self#::#F
x::Tuple{Int64,Float64}
Body:
begin
SSAValue(1) = Main.g
SSAValue(3) = (Core.getfield)(x::Tuple{Int64,Float64}, 1)::Int64
SSAValue(4) = (Core.getfield)(x::Tuple{Int64,Float64}, 2)::Float64
$(Expr(:inbounds, false))
# meta: location untitled-33e486cf08c224e1e95c30ab1d254fb4 typestable 8
SSAValue(2) = (SSAValue(1))(SSAValue(3), SSAValue(4))::ANY
# meta: pop location
$(Expr(:inbounds, :pop))
return (Core.typeassert)((Main.convert_eltype)(SSAValue(2), SSAValue(3), SSAValue(4))::ANY, $(QuoteNode(Real)))::REAL
end::REAL
So while calling “typestable” gives me, well, typestable code, any attempt at a syntactic sugar wrap makes me lose type stability.
I can live without the syntactic sugar, but if the implication is that typestability is not “exported by a function” then I am in CPU-trouble, and worse, I do not understand the logic of it all.
What is going on here, why are F and G not typestable, when they return a value that is stamped for type?