Understanding dispatch on types with nested type parameters and covariance

I feel it should be obvious, but I can’t figure it out. Why does this work:

julia> function foo(::Type{<:AbstractArray{<:Integer}})
julia> foo(AbstractArray{<:Signed})

But not this:

julia> function bar(::Type{<:AbstractArray{T}}) where {T<:Integer}
julia> bar(AbstractArray{<:Signed})

ERROR: MethodError: no method matching bar(::Type{AbstractArray{#s47,N} where N where #s47<:Signed})
Closest candidates are:
  bar(::Type{#s47} where #s47<:(AbstractArray{T<:Integer,N} where N)) where T<:Integer at REPL[3]:2

This works, however:

julia> bar(AbstractArray{Signed})

Do you really want to dispatch on the argument being a union type?

Because

foo(::Type{<:AbstractArray{<:Integer}})

means

foo(::Type{<:AbstractArray{T} where T <: Integer})

Do you really want to dispatch on the argument being a union type?

Actually yes, in this case. This was just a toy example, what’s actually provided is the a type to be read from data. So the user is supposed to just specify “I expect an array of integers” without giving the exact type.

I am curious what the purpose of this is. It will not yield fast code when the type is abstract. Also, AbstractArray{<:Integer} will miss perfectly fine arrays of integers, such as

Any[1, 2, 3]

IMO well written Julia code should have results which are invariant to container types, except for result container types (not values!) and of course speed. A function that works on a Int[1, 2, 3] should work on Any[1, 2, 3] or Real[1, 2, 3], even if it is (significantly) slower.

1 Like

I am curious what the purpose of this is. It will not yield fast code when the type is abstract.

Definitely not - but I’m using it as a way for the user to express how some data is to be interpreted, semantically (data stored in HDF5). For example, is a matrix a matrix or a vector of vectors? Is an HDF5 group a table of columns? Semantics like that. So that dispatch is only run once every multi-megabytes, hence speed is not an issue.

I do have a workaround for this, I just wanted to understand what makes
foo(::Type{<:AbstractArray{<:Integer}}) different from bar(::Type{<:AbstractArray{T}}) where {T<:Integer} when it comes to dispatch. The method error does list bar as a candidate, and in color mode, not part of it is colored as doesn’t match. But why doesn’t it match? Mainly, I was curious about what I’m missing about dispatch and type system here (probably something obvious).

Sure, I know it’s not the same as bar, exactly. But why doesn’t dispatch find bar?

The where T outside the signature basically means there needs to be a specific T value, and AbstractArray{<:Signed} does not have any specific value for T in AbstractArray{T}.

4 Likes

The where T outside the signature basically means there needs to be a specific T value

Thanks, @jeff.bezanson ! I never realized that (like @Tamas_Papp suggested I normally try hard to keep my code type stable :wink: ) - good to know!