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?