Type inference broken?

Motivation:
Imagine I want to use the return type of a function to define an output array of that type.

julia> makearray(f::Function, ::Type{T}) where T = 
                                         Core.Inference.return_type(f, (T,T))[]
makearray (generic function with 3 methods)

julia> makearray(cmp, Int)
0-element Array{Int64,1}

julia> makearray(cmp, Integer)
0-element Array{Any,1}

julia> which(cmp, (Integer, Integer))
cmp(x::Integer, y::Integer) in Base at operators.jl:348

julia> which(cmp, (Int, Int))
cmp(x::Integer, y::Integer) in Base at operators.jl:348

The result is as I expected with Int, but fails with Integer. That is due to Core..return_type.
The used method is the same in both cases:

operators.jl:348
# cmp returns -1, 0, +1 indicating ordering
cmp(x::Integer, y::Integer) = ifelse(isless(x, y), -1, ifelse(isless(y, x), 1, 0))

No, inference is not broken as it correctly infers that the return type of cmp(<:Integer,<:Integer) must be a subtype of Any. But sure, there is room for improvement.

Also, note that it is discouraged to make your program dependent on inference; one of the reasons being because inference might get better in the future and your program may then fail.

2 Likes

Is there?

I view the current situation as a conservative approach:
Also if all current implementations of cmp clearly return Int64, and also if I call invoke to get the implementation for the abstract Integer, there is no guarantee, that in the future, somebody implements for a new subtype of Integer a methods with a different return type.
But being conservative is not a typical approach for Julia, I suppose.

No that’s not the reason. There are just too many matching methods.

That does not sound like a big obstacle! I my example there are 6: method(cmp, (Integer, Integer). But in general, there is no limit, except given of the size of the code base.
For the moment, I will stick to my work-around. I force a concrete type to be used, by changing the signature to:

julia> makearray(f::Function, ::T) where T = Core.Inference.return_type(f, (T,T))[]
makearray (generic function with 1 method)
julia> makearray(cmp, BigFloat(0))
0-element Array{Int64,1}

Only need a handy object at times.

Imagine I want to use the return type of a function to define an output array of that type.

The following describes how this is handled generally in Base to have inference-independent behavior (except for empty arrays), avoiding what @mauro3 pointed above. Granted, it won’t be generally inferable but it will give you a tight element-typed-array when not inferable.

1 Like