Syntax for dispatching on subtypes

If I have a type with multiple subtypes, and I want to dispatch on one of the types I usually type:

struct Foo{A,B,...}
   ...
end

function somefunc(f::F{A, SomeSubtype,C,...}) where {A,C,...}
   ...
end

This becomes a bit cumbersome. Is there some better syntax?

Something like this would already be nice

function somefunc(f::F{_,SomeSubtype,_,...})
   ...
end

but this doesn’t work.

You can at least leave out all ones behind (C,...) and just do

function somefunc(f::F{A, SomeSubtype}) where {A}
   ...
end

which makes ordering of the parameters a design decision: put those you dispatch on often far upfront.

4 Likes

You don´t need to repeat all types, just up to the one where dispatch is needed. Thus, the idea is to put first the types that are needed for dispatch. For example:

julia> struct A{T1,T2}
           x::T1
           y::T2
       end

julia> f(a::A{T1}) where {T1<:Int} = "Int"
f (generic function with 2 methods)

julia> f(a::A{T1}) where {T1<:Float64} = "Float64"
f (generic function with 2 methods)

julia> f(A(1,2))
"Int"

julia> f(A(1.0, 2))
"Float64"
3 Likes

Great, I wasn’t aware of this. Thanks a lot!

1 Like

I don’t think we have anything like this with _, but if there are parameters you want to “ignore” you can write them as <:Any. Like
F{<:Any, <:TargetSupertype, <:Any, <:AnotherType}. Note that an inline <:X is a shorthand for something like _X with a where {_X <: X} at the end (although _X is something I made up and what the compiler uses will be sure to be unique), so those substitutions are equivalent to F{A, B, C, D} where {A,B<:TargetSupertype,C,D<:AnotherType}. Note also that where A<:Any is equivalent to where A, which is why I didn’t expand those fully.

1 Like

Ah, of course. This syntax I knew but somehow didn’t think to use. Thanks for the suggestion. Both suggestions together make everything a lot easier to write and maintain in my code. Thanks!

1 Like

I wrote a macro for this once but never really used it myself (hence not registered; it also relies on Julia internals): GitHub - fatteneder/NamedTypeParameters.jl: Macro for naming type parameters in signatures

With this you could ‘shorten’ it to

function somefunc(f::@parameterize(F{B=SomeSubtype}))
   ...
end

The macro name should be shortened too, otherwise its not much shorter :laughing:

Interestingly, both of the suggested solutions don’t seem to work for Type{T} type selectors.

If I want to dispatch on the subtype of a passed type, I do have to write out everything. Any suggestions for this?

Can you be a bit more specific? For example you might have ran into the warning from
Types · The Julia Language ? If so see the two code blocks afterwards for the fix.

Is there any difference between the form

f(::AbstractType{A,B}) where {A,B} = B

and this form?

f(::AbstractType{<:Any,B}) where {B} = B

I’ve got the impression that the first one is considered more “specialized” by the compiler but don’t remember reading anything explaining the difference in the Julia docs.

I don’t think the two methods differ:

julia> (AbstractArray{<:Any, B} where {B}) == (AbstractArray{A, B} where {A, B})
true

Also, when I define both in a fresh REPL session (using AbstractArray as an example type) I only get one method at the end. Seems like they are exactly the same:

julia> f(::AbstractArray{A,B}) where {A,B} = B
f (generic function with 1 method)

julia> f(::AbstractArray{<:Any,B}) where {B} = B
f (generic function with 1 method)

julia> methods(f)
# 1 method for generic function "f" from Main:
 [1] f(::AbstractArray{<:Any, B}) where B
     @ REPL[4]:1

But funnily enough, if I define the first method again in the same session, it changes the printout of methods to match the way it was defined. Perhaps because === is not true for both types written above.

julia> f(::AbstractArray{A,B}) where {A,B} = B
f (generic function with 1 method)

julia> methods(f)
# 1 method for generic function "f" from Main:
 [1] f(::AbstractArray{A, B}) where {A, B}
     @ REPL[6]:1

Unless I am missing something super subtle here, the only difference is that you cannot use the type parameter A directly in the function body :man_shrugging: