# Why is Julia not inferring the output type of f(x,y)=x+y?

I’m playing around with type inference in Julia.
I have the following simple function:

``````f(x,y) = x+y
function q2()
x = Number[1.0,3]
a = 4
b = 2
c = f(x[1],a)
d = f(b,c)
f(d,x[2])
end
@code_warntype q2()
``````

Here’s the output:

``````MethodInstance for q2()
from q2() in Main at In[114]:2
Arguments
#self#::Core.Const(q2)
Locals
d::Any
c::Any
b::Int64
a::Int64
x::Vector{Number}
Body::Any
1 ─      (x = Base.getindex(Main.Number, 1.0, 3))
│        (a = 4)
│        (b = 2)
│   %4 = Base.getindex(x, 1)::Number
│        (c = Main.f(%4, a::Core.Const(4)))
│        (d = Main.f(b::Core.Const(2), c))
│   %7 = d::Any
│   %8 = Base.getindex(x, 2)::Number
│   %9 = Main.f(%7, %8)::Any
└──      return %9
``````

I’m surprised that the type of `c` is `Any`. Given that `f` is adding a `Float64` and a `Int64`, I would expect `c` to be inferred as a `Float64`.

I can use `c::Float64 = f(x[1],a)` to get type inference for `c` and `d`, which is also surprising b/c if Julia can determine that `d` is a `Float64` by knowing that `c` is a `Float64`, then it can track the types that flow through `f`, but that would imply it could predict the types for `c` w/o ever having an explicit type annotation.

Even after doing `c::Float64`, the final line (`f(d,x[2])`) seems to return `Any`:

``````%11 = Main.f(%9, %10)::Any
return %11
``````

This is surprising, b/c Julia knows the type of `%9` is `Float64` and Julia knows the type of `%10` is `Number`. And presumably `Float64` + `Number` is at the very least `Number` instead of `Any`.

Any reason why Julia’s type inference is having a hard time here?

Furthermore, `code_warntype` displays `Number` in red. Is it bad if your code has types like `Number` (instead of more concrete types like `Float64`)?

Given that `f` is adding a `Float64` and a `Int64`

``````%4 = Base.getindex(x, 1)::Number
``````

This is the problem. It doesn’t know that `getindex` on your `Number` array returns a `Float64`.

Replacing the array with a `Float64` array, or writing
`c = f((x[1])::Float64,a)` will also make it type stable.

1 Like

Julia’s type inference algorithm needs to keep compilation time “reasonably small” while also computing precise type information where it “seems likely” that this would result in fast code at runtime.

Fast code machine code can generally be produced when type inference can compute concrete types (or small `Union`s of concrete types). But when an abstract type like `Number` is encountered with many subtypes, it’s unlikely to be better to infer `Number` than to widen the type to `Any`. So type inference has just chosen to widen the type here, computing a less precise result in favor of lowering compilation time.

By the way, here’s the subtypes of `Number` defined in Base. There’s also many others in external libraries:

``````Number
├─ Complex
└─ Real
├─ AbstractFloat
│  ├─ BigFloat
│  ├─ Float16
│  ├─ Float32
│  └─ Float64
├─ AbstractIrrational
│  └─ Irrational
├─ Integer
│  ├─ Bool
│  ├─ Signed
│  │  ├─ BigInt
│  │  ├─ Int128
│  │  ├─ Int16
│  │  ├─ Int32
│  │  ├─ Int64
│  │  └─ Int8
│  └─ Unsigned
│     ├─ UInt128
│     ├─ UInt16
│     ├─ UInt32
│     ├─ UInt64
│     └─ UInt8
└─ Rational
``````

In short, yes.

2 Likes

Const prop also doesn’t work through arrays (again, they’re a bit of a black box), but IIRC Shuhei was implementing it for even unryped fields of `mutable struct`s, so it’s conceivable that this will work in the future.

If I explicitly cast the return value to a `Float64`, like this:

``````f(x,y) = x+y
function q2()
x = Number[1.0,3]
a = 4
b = 2
c = f(x[1],a)
d = f(b,c)
Float64(f(d,x[2]))
end
@code_warntype q2()
``````

The last 2 lines of the output are:

``````│   %9  = Main.f(%7, %8)::Any
│   %10 = Main.Float64(%9)::Any
└──       return %10
``````

I’m not sure if I’m reading the notation incorrectly, but it looks like this is saying `Main.Float64(%9)` returns `Any`, which seems incorrect since presumably the entire point of `Main.Float64` is to either return a `Float64` or throw an exception?

``````struct Foo end
(::Type{Float64})(::Foo) = "not a Float64"
``````

If the `Any` is a Foo, we’d get a `String`.

This is legal (but bad), therefore inference is not allowed to inner the return type is `Float64`.
Use `::Float64` for type asserts on variable declarations or function returns.
These will allow inference to assume the result `Float64` (or throw).

It’s generally best to try and avoid the type instability in the first place, thereby avoiding the need to recover from one, if possible.

1 Like

Thanks for the example here.

I’m a little confused by the syntax:

``````(::Type{Float64})(::Foo) = "not a Float64"
``````

I read: Types · The Julia Language

but I can’t find any mention of this syntax

https://docs.julialang.org/en/v1/manual/types/#man-typet-type

basically `x::T` means `x` is a variable and `typeof(x) <: T`; but what if the argument `x` you want to pass is a type to begin with? you need `x::Type{T}`, which would be matched when `x === T`

Thanks for the explanation!
I think what I’m confused about, is that there is nothing to the left of the `::` in `::Type{Float64}`.

Or I guess, it looks like we are assigning a string to a variable, but I don’t see any variable name on the left hand side of the assignment.

we’re not using that variable, so we don’t need to give it a name, this is same idea to:

``````f(x::Vector{<:Any}) = ...
#v.s.
f(x::Vector{T}) where {T<:Any} = ...
``````

if I’m not using `T` in the method definition’s body, I don’t really care to give it a name

1 Like

Hm, what would the version of this look like where we were assigning to a variable?

• Seems like `::Type{Float64}` is a type
• I’m not sure how `::Foo` (which is also a type?) is interacting with a `Float64` given that `Foo` is a struct. Also not sure how the end result here becomes a string of all things.
``````a = Foo(...)
here `a::Foo` and `Float64::Type{Float64}`