I want to write toggle, a typestable version of the q ? a : b syntax. What I am writing now has the disadvantage of evaluating both a and b for all values of q. A macro might save the day, but this is not the point of my question. Here is my code:
module A
export toggle
@generated function toggle(cond::Bool,a::Ta,b::Tb) where{Ta,Tb}
Tc = promote_type(Ta,Tb)
return :(cond ? convert($Tc,a) : convert($Tc,b))
end
end
module B
export T
struct T <: Real
value :: Float64
end
Base.promote_rule(::Type{T},::Type{Int}) = T
end
using Main.A
using Main.B
a = T(3.)
b = 3
@show promote_type(typeof(a),typeof(b))
@show toggle(false,a,b)
So I define my type, and promote rules in module B. I define toggle in module A, that does not (and shall not) import module B. (I want toggle to work for any data type, without having to enumerate the modules defining them in module A).
My code yields
promote_type(typeof(a), typeof(b)) = T
toggle(false, a, b) = 3
So I have the promotion rule right, but toggle is generated in ignorance of it (Tc evaluates to Real)
why is toggle unaware of my promote rule? This function gets generated in a context where type T is defined, what did I miss?
how can I fix that?
Besides, surely Julia has some typestable version of q ? a : b already?
Since functions (including, of course, generated functions) evaluate their arguments, I don’t think this is possible to avoid.
But even if you use a macro, either you have a and b so you can take typeof and promote, or somehow rely on inference, which has its own complications and may not work in general.
I would suggest just calculating a type T and promoting the result. The compiler may be able to deal with the interim type instability of the result just fine when applicable.
Is this a variant of the “world age” problem?
I played around a little bit with the code above (to learn) and found this:
julia> @generated function toggle(cond::Bool,a::Ta,b::Tb) where{Ta,Tb}
Tc = my_promote_type(Ta,Tb)
return :(cond ? convert($Tc,a) : convert($Tc,b))
end
toggle (generic function with 1 method)
julia> struct T <: Real
value :: Float64
end
julia> my_promote_type(::Type{T},::Type{Int}) = T
my_promote_type (generic function with 1 method)
julia> a = T(3.)
T(3.0)
julia> b = 2
2
julia> my_promote_type(typeof(a),typeof(b))
T
julia> toggle(false,a,b)
ERROR: MethodError: no method matching my_promote_type(::Type{T}, ::Type{Int64})
The applicable method may be too new: running in world age 27830, while current world is 27833.
Closest candidates are:
my_promote_type(::Type{T}, ::Type{Int64}) at REPL[3]:1 (method too new to be called from this world context.)
Stacktrace:
[1] #s1#1 at .\REPL[1]:2 [inlined]
[2] #s1#1(::Any, ::Any, ::Any, ::Any, ::Any, ::Any) at .\none:0
[3] (::Core.GeneratedFunctionStub)(::Any, ::Vararg{Any,N} where N) at .\boot.jl:527
[4] top-level scope at REPL[7]:1
I didn’t check with different modules, because the original problem shows that it doesn’t work either if you just ignore modules A and B and put it just into the REPL. Just generic promote_type is called, the promote_rule definition is ignored.
So my guess is, that promote_rule fails over “The applicable method may be too new” too, but no error is shown, because the generic alternative is available.
The generated function docs emphasize the need for purity in the code generating the generated function – including with respect to the method tables.
In your example, there is no reason for toggle to be @generated.
It very much looks like you nailed it. I had heard about “world age” issues before, but never connected it to anything. I’ll follow the links you provide and get wise. Thank you!
I went for generated in order to not introduce runtime work on inferring types. The way I understand you, this is unnecessary because a) a method gets, what was the name, instantiated for each input type combination it gets called with, and b) the promote_type operator is then a no-op. Makes sense.
I reread the doc on generated, and its warnings about “side effects”. I do not understand your remark “including with respect to the method tables”. Would you care to elaborate? Do you maybe mean that the promote_type call may trigger a compilation, which is a side-effect, i.e. impure?
julia> function toggle(cond::Bool, a::Ta, b::Tb) where{Ta,Tb}
Tc = promote_type(Ta,Tb)
cond ? convert(Tc, a) : convert(Tc, b)
end
toggle (generic function with 1 method)
julia> @code_typed toggle(true, 1, 1f0) # sitofp == Signed Integer TO Floating Point
CodeInfo(
1 ─ goto #3 if not cond
2 ─ %2 = Base.sitofp(Float32, a)::Float32
└── return %2
3 ─ return b
) => Float32
julia> @code_typed toggle(true, 1, 1.0) # sitofp == Signed Integer TO Floating Point
CodeInfo(
1 ─ goto #3 if not cond
2 ─ %2 = Base.sitofp(Float64, a)::Float64
└── return %2
3 ─ return b
) => Float64
julia> @code_typed toggle(true, 1f0, 1.0) # fpext == Floating Point EXTend
CodeInfo(
1 ─ goto #3 if not cond
2 ─ %2 = Base.fpext(Base.Float64, a)::Float64
└── return %2
3 ─ return b
) => Float64
julia> @code_typed toggle(true, one(BigInt), one(UInt32))
CodeInfo(
1 ─ goto #3 if not cond
2 ─ return a
3 ─ %3 = Base.GMP.MPZ.set_ui::typeof(Base.GMP.MPZ.set_ui)
│ %4 = invoke %3(_4::UInt32)::BigInt
└── return %4
) => BigInt
The compiler has no problem figuring things like this out.
I reread the doc on generated, and its warnings about “side effects”. I do not understand your remark “including with respect to the method tables”. Would you care to elaborate? Do you maybe mean that the promote_type call may trigger a compilation, which is a side-effect, i.e. impure?
I meant the method table itself is global state, but now I think I may have confused this with something said about @pure statements, so please don’t quote me on that. The documentation however mentions hasmethod, and also says:
Generated functions are only permitted to call functions that were defined before the definition of the generated function. (Failure to follow this may result in getting MethodErrors referring to functions from a future world-age.)
To all of you guys: thank you so much. It’s not the first time I got great help on Discourse, it’s not the first time I got help from you all. It makes a huge difference, both in how fast I learn, and in the fun of doing so. IOU.
THANK YOU TO THE TEAM THAT CREATED JULIA AND PUSHES IT TO MATURITY
THANK YOU TO EVERYONE THAT DEVELOPS GREAT PACKAGES
THANK YOU TO ALL OF YOU WHO TAKE TIME TO ANSWER QUESTIONS, SILLY OR HARD