Where on abstract type and parameters

Is there a way to use a type signature that imposes that two arguments are the same subtype of an abstract type and also have the same parameter for some but not necessarily all parameters?

For example, the hypothetical method of bob below should dispatch whenever the two arguments (1) are both subtypes of AbstractArray (2) have the same concrete type (e.g. both are SparseArray or both are Array) (3) have the same number of dimensions. But they may have different eltypes.

""" method 1"""
bob(x::A{T1, D} , y::A{T2, D} ) where {A <: AbstractArray, T1, T2, D} = 
"same array type, same number of dimensions, possibly different eltypes"

The above throws an error (“TypeError: in Type{…} expression, expected UnionAll, got a value of type TypeVar”).

Methods that do not accomplish what I want, but are syntactically valid:

""" method 2"""
bob(x::AbstractArray{<:Any, D}, y::AbstractArray{<:Any, D}) where D = 
"abstract arrays with same number of dimensions"

""" method 3"""
bob(x::T, y::T) where T <: AbstractArray = 
"same subtype of abstract array"

""" method 4 """ 
bob(x::A, y::A) where A <: AbstractArray{T, D} where {T, D} = 
"same parametric type"

A = SentinelVector{Float64}(undef, 10)
bob(A, [1,2]) # dispatches to method 2.  I do not want it to dispatch to method 1

bob([1 2; 3 4], [1,2])  # dispatches to method 3.  I do not want it to dispatch to method 1

bob([1.], [2.]) # would dispatch to method 4.  

bob([1. 2.; 3. 4.], [1 2 3; 4 5 6]) # would want to dispatch to method 1.  
# does not match type signature for method 4.  
# matches type signature for method 2 and 3.  

Is there a syntax for method 1 that actually works, or is there no way to directly accomplish my intent with a single method signature? (note, I am not actually doing this for AbstractArray but for a user-defined type. The reason I am doing this is because if method 1 matches, then I can skip to directly doing a thing, but if method 1 does not match, then I want to do a bunch of checks before doing the thing, accomplished by bob(x, y) = "do checks".

There are obvious workarounds – I could, for example, check in the function body for the conditions, but it seems like I should be able to directly and more parsimoniously accomplish this with a type signature.

1 Like

I think this is what you want for method 1:

bob(x::AbstractArray{T1, D} , y::AbstractArray{T2, D} ) where {T1, T2, D} = 
"same array type, same number of dimensions, possibly different eltypes"

Edit: Nope, that will not guarantee that they are the same array type, nevermind.

The thing that I would expect to work is:

bob(x::A, y::A) where {D, A <: (AbstractArray{T, D} where T)}

But it seems to be treated as your method 4

Thanks for trying! I suspect that the syntax might not exist. It’s not a crippling problem (because there are workarounds), but it feels wrong that this is not possible.

1 Like

This should be a way to do that: Dispatching on Types with the Same UnionAll (but You Don’t Know the Type Beforehand)

But yes, it would be great to have an easy way to do it implemented in the language itself

Nits aside (there are terminological issues, but I get what you mean), this condition is not expressible in Julia’s type system. What you really want when you say “concrete type” (which means something else) is the “base type”. However, an issue is that “base type” does not mean anything generically.

Relevant section in the docs:

Don’t do that, it’s a horrible hack.

3 Likes

One more thing to keep in mind is that the type system has no notion of the concept you call “same subtype” here. Basically what I think you mean by “subtype of an abstract type” is “declared with struct or mutable struct”, however that concept doesn’t exist in the type system.

Some clarification:

  • The definition of “concrete type” for Julia is, I guess, the isconcretetype doc string:

    A characterization, I believe, is “type whose only subtypes are the type itself and the bottom type”. Slightly more formally: “if a type t is concrete, s <: t implies (t <: s) || (s <: Union{})”.

  • Regarding the meaning of “base type”, for example:

    • the “base type” of A{B, C} could be A

    • the “base type” of A{B, C} could be A{B}

  • Some doc strings relevant for Julia’s subtyping in general:

1 Like

Thank you Neven!

Very helpful, and I appreciate you taking the time to clarify concepts and terminology!

1 Like

Why dispatching using Holy trait mechanism is not a good solution?:

julia> struct SameArray{d} end

julia> issamearray(a::A,b::B) where {A,B} = SameArray{A.name== B.name}()
issamearray (generic function with 1 method)

julia> bob(x::A, y::B ) where {D,T1,T2,A <: AbstractArray{T1,D}, B<:AbstractArray{T2,D}} = bob(issamearray(x,y),x,y)
bob (generic function with 3 methods)

julia> bob(::SameArray{false},x::A, y::B ) where {D,T1,T2,A <: AbstractArray{T1,D}, B<:AbstractArray{T2,D}} = 
       "different array type, same number of dimensions, possibly different eltypes"
bob (generic function with 3 methods)

julia> bob(::SameArray{true},x::A, y::B ) where {D,T1,T2,A <: AbstractArray{T1,D}, B<:AbstractArray{T2,D}} = 
       "same array type, same number of dimensions, possibly different eltypes"
bob (generic function with 3 methods)

julia> bob([1. 2.; 3. 4.], [1 2 3; 4 5 6])
"same array type, same number of dimensions, possibly different eltypes"

julia> bob([1. 2.; 3. 4.], sparse([1 2 3; 4 5 6]))
"different array type, same number of dimensions, possibly different eltypes"