What's with the invariant parametric types gotcha?


julia> foo(x::Vector{Integer}) = "bar"
foo (generic function with 1 method)

julia> foo([1, 2, 3])
ERROR: MethodError: no method matching foo(::Array{Int64,1})
Closest candidates are:
  foo(::Array{Integer,1}) at REPL[1]:1
 [1] top-level scope at REPL[2]:1

julia> foo(x::Vector{<:Integer}) = "bar"
foo (generic function with 2 methods)

julia> foo([1, 2, 3])

Of course, this is documented: Types · The Julia Language

Still, in my mind I could almost audibly hear Julia saying “ha ha, gotcha!” when I first learned this. What is the reason for this syntax?

Is it even possible to call foo(x::Vector{Integer}) = "bar"? If not, why not just implicitly add the <: syntax so it “just works”?

Is it just a syntax decision? Maybe the designers of the language wanted to be explicit by requiring the <: syntax? If so, that’s understandable, even though I may or may not agree*.

* I don’t agree or disagree with any syntax decisions. I like Julia, and I like its syntax. And in general I consider syntax issues to be very low on the list of things that matter, so I don’t even bother forming an opinion on most syntax debates.

Yes. Just call:

foo(Integer[1, 2, 3])

No, the syntax really isn’t the core of the issue here. A Vector{Integer} is a real type, and one you might even use at some point, it’s just that [1, 2, 3] doesn’t give you one.

A Vector{Integer} is a vector in which every element can be a (potentially different) subtype of Integer:

julia> x = Vector{Integer}()

julia> push!(x, 1)
1-element Array{Integer,1}:

julia> push!(x, big(2)^big(100))
2-element Array{Integer,1}:

This is different from Vector{Int64} in which every element is exactly the concrete type Int64. It’s also different from Vector{<:Integer} which is a set of types describing any Vector whose element type is any subtype of Integer. Vector{Int64} and Vector{Integer} are both members of that set:

julia> Vector{Int64} <: Vector{<:Integer}

julia> Vector{Integer} <: Vector{<:Integer}

but they have no relationship to one another otherwise:

julia> Vector{Int64} <: Vector{Integer}

I see. So there is at least a reason for the syntax. Because you need some way to distinguish between, well, a Vector{Integer} and a Vector{<:Integer}. I’m satisfied with that explanation.

1 Like