Add method with different arguments to existing function?

Hi!

Suppose I have a function f(a::Number, b::Number; c=1, d=2.0, e="hello") and I want to add a method to it with different kwargs, but with the same positional arguments, say for example f(a::Number, b::Number; f="bye", g=pi, h=nothing).

Is this possible without overwriting the first method for f()?

No. Keyword arguments aren’t used for dispatch in Julia.

1 Like

I see, thanks for taking the time to answer!

Welcome!

Here are a couple workarounds you could use. Let us know if you need more help.

  1. Dump all the keyword arguments into one method and branch with if. (Not the best, but it works.)
julia> function f(a::Number, b::Number; c=nothing, d=nothing, e=nothing, f=nothing, g=nothing, h=nothing)
           if c !== nothing && d !== nothing && e !== nothing
               println.((c, d, e))
           elseif f !== nothing && g !== nothing && h !== nothing
               println.((f, g, h))
           else
               throw(ArgumentError("Missing required keyword argument."))
           end
           return a, b
       end
f (generic function with 1 method)

julia> f(1, 2; c=1, d=2.0, e="hello")
1
2.0
hello
(1, 2)

julia> f(1, 2; f="bye", g=pi, h=12)
bye
π
12
(1, 2)
  1. Create custom types and dispatch on those.
julia> struct Type1
           c::Int
           d::Float64
           e::String
       end

julia> struct Type2
           f::String
           g::Irrational{:π}
           h::Int
       end

julia> function f(a::Number, b::Number, c::Type1)
           println(c.e)
           return c
       end
f (generic function with 2 methods)

julia> function f(a::Number, b::Number, c::Type2)
           println(c.f)
           return c
       end
f (generic function with 3 methods)

julia> c1 = Type1(1, 2.0, "hello")
Type1(1, 2.0, "hello")

julia> c2 = Type2("bye", pi, 12)
Type2("bye", π, 12)

julia> f(1, 2, c1)
hello
Type1(1, 2.0, "hello")

julia> f(1, 2, c2)
bye
Type2("bye", π, 12)
  1. Make a function <good descriptive name here> for the second input set instead of another method of f.
1 Like

Another option is to create your own singleton type to indicate keyword arguments that haven’t been filled in, and then call a function which dispatches on that. This approach is used e.g. here as part of the implementation for mapreduce.

julia> struct _EmptyArg end
       begin local z,Z = _EmptyArg(),_EmptyArg
           _f(a, b, (c, d, e), ::NTuple{3,Z}) = let x=(;c,d,e), ks=filter(k->x[k]!==z,keys(x)), vs=map(k->x[k],ks); f1(a, b; NamedTuple{ks}(vs)...) end
           _f(a, b, ::NTuple{3,Z}, (f, g, h)) = let x=(;f,g,h), ks=filter(k->x[k]!==z,keys(x)), vs=map(k->x[k],ks); f2(a, b; NamedTuple{ks}(vs)...) end
           f(a, b; c=z, d=z, e=z, f=z, g=z, h=z) = _f(a, b, (c, d, e), (f, g, h))
       end
       f1(a, b; c=1, d=2.0, e="hello") = "$a $b $c $d $e"
       f2(a, b; f="bye", g=π, h=nothing) = "$a $b $f $g $h";

julia> f(false,true; c=1//1)
"false true 1//1 2.0 hello"

julia> f(true,false; f="hi")
"true false hi π nothing"
1 Like

Thanks @uniment and @Nathan_Boyer for your answers!

While in principle dispatching over variable keyword arguments can be useful in some cases, I found that it can be avoided by simply relegating each combination of kwargs to its own function and then dispatching on those.

For my specific example I found that the code is more legible if one refrains from dispatching (or even splatting) kwargs.Many legible (and testable!!!) functions are better than a single function with a ton of kwargs that can vary.

if you can act on the original function, you can set it this way and then be able to add new methods with different kwargs


nt1=(; c=1, d=2.0, e="hello")

function f(a::Number, b::Number,nt :: typeof(nt1)) 
    println(nt.e, '\n', a+b, '\n', nt.c*nt.d)
end
f(3, 2 ,nt1)


nt2=(; f="bye", g=pi, h=nothing)

function f(a::Number, b::Number, nt::typeof(nt2))
    println(nt.f, '\n', a+b, '\n', isnothing(nt.h) ? 2*nt.g : nt.g/2)
end
f(3, 2, nt2)

PS
I’m not very confident that what has been proposed above makes much sense, in general.

Edit: Can someone of good will and good knowledge explain why it doesn’t work


julia> nt2=NamedTuple{(:f, :g, :h), Tuple{String, Irrational{:π}, Any}}((f="bye", g=pi, h=nothing))
NamedTuple{(:f, :g, :h), Tuple{String, Irrational{:π}, Any}}(("bye", π, nothing))

julia> function f(a::Number, b::Number, nt::typeof(nt2))
           println(nt.f, '\n', a+b, '\n', isnothing(nt.h) ? 2*nt.g : nt.g/2)
       end
f (generic function with 1 method)

julia> f(3, 2, nt2)
bye
5
6.283185307179586

julia> Tuple{String, Irrational{:π}, Nothing} <: Tuple{String, Irrational{:π}, Any}
true

julia> Tuple{String, Irrational{:π}, Number} <: Tuple{String, Irrational{:π}, Any}
true

julia> nt=merge(nt2, (f="addio",h=9))
(f = "addio", g = π, h = 9)

julia> f(5,4,nt)
ERROR: MethodError: no method matching f(::Int64, ::Int64, ::NamedTuple{(:f, :g, :h), Tuple{String, Irrational{:π}, Int64}})
Closest candidates are:
  f(::Number, ::Number, ::NamedTuple{(:f, :g, :h), Tuple{String, Irrational{:π}, Any}}) at c:\Users\sprmn\.julia\environments\v1.8.3\functions2.jl:88
Stacktrace:
 [1] top-level scope
   @ c:\Users\sprmn\.julia\environments\v1.8.3\functions2.jl:100

Is it possible to have a NamedTuple as a subtype of a more generic namedtuple?