Smart kwargs... dispatch

Let’s say a function f() involves another function with multiple arguments. From what I understood, one can avoid listing them with with the kwargs... argument that “passes” the optional arguments down to the other functions.

function f(x; kwargs...)
   g(x; kwargs...)
end

Is it possible to setup something similar but when two functions are involved, and smartly dispatch appropriate arguments to each function.

function f(x; kwargs...)
   y = g(x; kwargs...)
   z = h(y; kwargs...)
end

Currently, it would throw a MethodError as some of the kwargs of g() do not exist for h().

This is a typical idiom in R, but I would avoid it in Julia. Instead, I would prefer

function f(x, args)
   y = g(x, args)
   z = h(y, args)
end

and make args a NamedTuple.

g and h can then pick the fields they like. Also see Parameters.@unpack.

When the interface stabilizes, you can collect the args in a struct, without changing f, g, or h fundamentally.

1 Like

I can’t say if it is advisable, but it is possible. All you need to do is to slurp the extra kwargs in g and h as well.

function f(x; kwargs...)
   y = g(x; kwargs...)
   z = h(y; kwargs...)
end

g(x; g_kwarg="hello", kwargs...) = <some function>
h(x; y_kwarg=:goodbye, kwargs...) = <some other function>

This way, you don’t get any method errors. On the other hand, it will no longer warn you if you pass a misspelt kwarg.

2 Likes

The problem with this is that you cannot build in defaults into the args field, right? On the other hand @korsbo approach has pretty low overhead

julia> @noinline f1(;x::Int = 1, kwargs...) = x; @noinline f2(;y::Int = 2, kwargs...) = y;

julia> f(; kwargs...) =  f1(;kwargs...) + f2(;kwargs...)
f (generic function with 1 method)

julia> @btime f()
  0.026 ns (0 allocations: 0 bytes)
3

julia> @btime f(x=4)
  17.066 ns (1 allocation: 32 bytes)
6

If you know in advance what are the keywords that f accepts you could also do something like:

function f(x; kwargs...)
   belongstog(s) = first(s) in v # where v are the keywords that f accepts
   kw1 = filter(belongstog, kwargs)
   kw2 = filter(!belongstog, kwargs)
   y = g(x; kw1...)
   z = h(y; kw2...)
end

That’s reminiscent of Mathematica’s approach. But in Mathematica we have Options[function], which returns the kwargs in function. Does anybody know if there a way to obtain the kwargs admitted by a function (the v in @piever’s code) by introspection, somehow?

I was wondering if a syntax like f( args; kwargs1... ;kwargs2... ; ... )
would make sense for this, then one could define

function f(; kwargs1... ; kwargs2... )
  g( ;kwargs1... )
  h( ;kwargs2... )
end

Calling f(; a = 1, b = 2, c = 3) what is kwargs1 and kwargs2?.

Well calling would have to be f(; a = 1, b = 2; c = 3 ) also.

What is the use case?

Now it leaks out to the caller though while it seems like the usecase for this should just be a convenience for the function itself.

1 Like

For example writing a function that calls an optimization routine, as well as an ODE solve and a plotting routine which may share same keyword arguments, then one could pass the keywords to those separately.
But maybe namedtuples is a better approach in this case anyways.

you can have defaults with a namedtuple

julia> function f(nt::NamedTuple{(:a,:b,:c)}=(a=1, b=2.0, c="three"))
          return nt
       end
f (generic function with 2 methods)

julia> f((a=2, b=3.0, c="four"))
(a = 2, b = 3.0, c = "four")

julia> f()
(a = 1, b = 2.0, c = "three")
2 Likes

I’ve been doing it something like this:

julia> function foo(;kwargs...)
           kwargs = Dict(kwargs)
           x = get(kwargs, :x, "default")
           return x
       end
foo (generic function with 1 method)

julia> foo(x="myValue")
"myValue"

julia> foo()
"default"

the following would be faster, and is a general pattern.

function foo(;kwargs...)
       defaults = (;  x="default")
       settings = merge(defaults, kwargs)
       x = settings.x
       return x
end

merge of two namestuples is extremely fast.

11 Likes

Hello,

I upgraded from version 0.63 to version 1.4 and when I try to merge the default and new kwargs it throws the following error message:
MethodError: no method matching merge!(::NamedTuple{(:x,),Tuple{String}}, ::Base.Iterators.Pairs{Symbol,String,Tuple{Symbol},NamedTuple{(:x,),Tuple{String}}})

I suppose, that merge is not defined for the this type of variable. Do you suggest a way to do it ?

Welcome, @alejandrovpm!

We’re happy to help, but please open a new topic for this rather than posting on an old thread.

2 Likes