(My original question seemed a bit convoluted, so I changed it significantly to make it succinct, but the basic questions are the same.)
I’m experiencing a difficulty in extending a Base
function type-stably. Not sure if the same difficulty arises for any Base
functions, but the one I’m having a difficulty with is Base.in
.
Consider the following code where I extend Base.in
for four concrete types of the same abstract type MyAbs
:
abstract type MyAbs end
struct MyType1 <: MyAbs end
struct MyType2 <: MyAbs end
struct MyType3 <: MyAbs end
struct MyType4 <: MyAbs end
Base.in(x::Int, ::MyType1) = x==1
Base.in(x::Int, ::MyType2) = x==2
Base.in(x::Int, ::MyType3) = x==3
Base.in(x::Int, ::MyType4) = x==4
Now, I would like to call in
with Int
and MyAbs
using the function below, which takes a vector of MyAbs
, iterates over the vector, and executes in
for each entry of the vector.
function zero_in(mvec::Vector{MyAbs})
# Return true if any element of mvec contains 0
t = false
for m = mvec
t = in(0, m)
t && break
end
return t
end
The output of this function turns out to be type-unstable:
julia> VERSION
v"0.6.1-pre.0"
julia> mvec = [MyType1(), MyType2(), MyType3(), MyType4()];
julia> @code_warntype zero_in(mvec)
Variables:
#self#::#zero_in
mvec::Array{MyAbs,1}
m::MyAbs
#temp#::Int64
t::Any
Body:
begin
t::Any = false # line 3:
#temp#::Int64 = 1
4:
unless (Base.not_int)((#temp#::Int64 === (Base.add_int)((Base.arraylen)(mvec::Array{MyAbs,1})::Int64, 1)::Int64)::Bool)::Bool goto 17
SSAValue(2) = (Base.arrayref)(mvec::Array{MyAbs,1}, #temp#::Int64)::MyAbs
SSAValue(3) = (Base.add_int)(#temp#::Int64, 1)::Int64
m::MyAbs = SSAValue(2)
#temp#::Int64 = SSAValue(3) # line 4:
t::Any = (0 in m::MyAbs)::Any # line 5:
unless t::Any goto 15
goto 17
15:
goto 4
17: # line 7:
return t::Any
end::Any
On the other hand, let’s repeat the same experiment with a non-Base function. Here I define myfun
that does exactly the same thing as Base.in
above, and also zero_myfun
that does exactly the same thing as zero_in
above:
myfun(x::Int, ::MyType1) = x==1
myfun(x::Int, ::MyType2) = x==2
myfun(x::Int, ::MyType3) = x==3
myfun(x::Int, ::MyType4) = x==4
function zero_myfun(mvec::Vector{MyAbs})
t = false
for m = mvec
t = myfun(0, m)
t && break
end
return t
end
Now, unlike the previous experiment, zero_myfun
produces a type-stable output:
julia> @code_warntype zero_myfun(mvec)
Variables:
#self#::#zero_myfun
mvec::Array{MyAbs,1}
m::MyAbs
#temp#::Int64
t::Bool
Body:
begin
t::Bool = false # line 3:
#temp#::Int64 = 1
4:
unless (Base.not_int)((#temp#::Int64 === (Base.add_int)((Base.arraylen)(mvec::Array{MyAbs,1})::Int64, 1)::Int64)::Bool)::Bool goto 17
SSAValue(2) = (Base.arrayref)(mvec::Array{MyAbs,1}, #temp#::Int64)::MyAbs
SSAValue(3) = (Base.add_int)(#temp#::Int64, 1)::Int64
m::MyAbs = SSAValue(2)
#temp#::Int64 = SSAValue(3) # line 4:
t::Bool = (Main.myfun)(0, m::MyAbs)::Bool # line 5:
unless t::Bool goto 15
goto 17
15:
goto 4
17: # line 7:
return t::Bool
end::Bool
Why do these two experiments give different behavior? More importantly, how can I extend Base.in
to produce a stable output in the first experiment?