Different dispatch for positional/keyword arguments

I am trying to implement multiple dispatch to give users a default option but also rise an error if the type chosen doesn’t implement the specific method.

It works fine if the method implement positional argument, but not if these are keywork argument.
Why is that? Any workaround ?

# My type structure :

abstract type MyAbstractType end

mutable struct Concrete1 <: MyAbstractType
    a
    b
    function Concrete1(;a=1,b=2)
        new(a,b)
    end
end

mutable struct Concrete2 <: MyAbstractType
    c
    d
    function Concrete2(;c=10,d=20)
        new(c,d)
    end
end
mutable struct Concrete3 <: MyAbstractType
    e
    f
    function Concrete3(;e=100,f=200)
        new(e,f)
    end
end


# Positional arguments: works as expected

function f2(x,obj::MyAbstractType)
    println("Not implemented")
end
function f2(x,obj::Concrete1=Concrete1())
    println(obj.a)
end
function f2(x,obj::Concrete2)
    println(obj.c)
end
f2(1) # 1
f2(1,Concrete1()) # 1
f2(1,Concrete1(a=2,b=3)) # 2
f2(1,Concrete2()) # 10
f2(1,Concrete2(c=20,d=30)) # 20
f2(1,Concrete3()) # "Not implemented"

# Keyword arguments:generate an error on f(1) ("keywork argument obj not assigned")

function f(x;obj::MyAbstractType)
    println("Not implemented")
end
function f(x;obj::Concrete1=Concrete1())
    println(obj.a)
end
function f(x;obj::Concrete2)
    println(obj.c)
end
f(1) # UndefKeywordError: keyword argument obj not assigned
f(1,obj=Concrete1())
f(1,obj=Concrete1(a=2,b=3))
f(1,obj=Concrete2())
f(1,obj=Concrete2(c=20,d=30))
f(1,obj=Concrete3())

I answer myself, I see that only positional arguments support multiple dispatch (see e.g. this discussion.

Any plan to add multiple dispatch also to keyword arguments? To a profane seems odd (yes, I know that for sure there is some reason for that… just it is not something expected)

In some cases you can workaround this limitation by passing your keyword arguments to a method with the desired positional arguments.

Here is a simple example:

function f(a, b::Int)
    println("b is an Int")
end

function f(a, b::Float64)
    println("b is a Float64")
end

function f(a; b)
    f(a,b)
end

f(1, b = 1)
2 Likes

I think that it would be a breaking change, so not for 1.x.

I don’t find this to be a limitation in practice — eg see the workaround suggested by @Christopher_Fisher. This becomes cumbersome if you have a lot of keyword arguments, but that is a code smell per se.

Thank you. I got what I wanted:

abstract type MyAbstractType end
mutable struct Concrete1 <: MyAbstractType
    a
    function Concrete1(;a=1)
        new(a)
    end
end
mutable struct Concrete2 <: MyAbstractType
    b
    function Concrete2(;b=10)
        new(b)
    end
end
mutable struct Concrete3 <: MyAbstractType
    c
    function Concrete3(;c=100)
        new(c)
    end
end

function f(x;obj::MyAbstractType=Concrete1())
    f(x,obj)
end
function f(x,obj::MyAbstractType)
    println("Not implemented")
end
function f(x,obj::Concrete1)
    println(obj.a)
end
function f(x,obj::Concrete2)
    println(obj.b)
end

f(1) # 1
f(1,obj=Concrete1()) # 1
f(1,obj=Concrete1(a=2)) # 2
f(1,obj=Concrete2()) # 10
f(1,obj=Concrete2(b=20)) # 20
f(1,obj=Concrete3()) # "Not implemented"