Retrieve default values of keyword arguments

Sometimes I would find it useful to have access to the default values of keyword arguments of a function.
I wrote this macro to achieve this for functions that I defined myself:

julia> macro kwvalues(func)                                                                    
           @eval $func                                                                         
           ftype = :(typeof($(func.args[1].args[1])))                                          
           if length(func.args[1].args) == 1                                                   
               @eval defaultkwvalues(::$ftype) = NamedTuple()                                  
           elseif typeof(func.args[1].args[2]) == Expr &&                                      
               func.args[1].args[2].head == :parameters                                        
               kw = func.args[1].args[2].args                                                  
               keys = [typeof(k.args[1]) == Symbol ? k.args[1] : k.args[1].args[1] for k in kw]
               vals = [k.args[2] for k in kw]                                                  
               if length(func.args[1].args) > 2                                                
                   @eval defaultkwvalues(::$ftype, $(func.args[1].args[3:end]...)) = NamedTuple
{($keys...,)}(($vals...,))                                                                     
               else                                                                            
                   @eval defaultkwvalues(::$ftype) = NamedTuple{($keys...,)}(($vals...,))      
               end                                                                             
           else                                                                                
               @eval defaultkwvalues(::$ftype, $(func.args[1].args[2:end]...)) = NamedTuple()  
           end                                                                                 
       end
                                                                                     
julia> @kwvalues f3(x; a = 2, b = 3) = 1
defaultkwvalues (generic function with 11 methods)                                            

julia> @kwvalues f3(x::Int; c = 3) = 1
defaultkwvalues (generic function with 11 methods)                                            

julia> defaultkwvalues(f3, 1.)
(a = 2, b = 3)

julia> defaultkwvalues(f3, 1)
(c = 3,)

I’m sure there are nicer ways to write the macro (any suggestions are welcome :smile:) , but is there maybe even a way to achieve the same without a macro?

2 Likes

When I use defaults in multiple places, I usually define them as constants, eg

const DEFAULT_PAGESIZE = 50

process_page(output; pagesize = DEFAULT_PAGESIZE, ...) = ...
2 Likes

Impressive macro, and a cool idea, hadn’t thought of that before!

That said, I tend to be reluctant to use macros for things like this. I feel like it often reduces flexibility and readability (people not familiar with your package will have no idea what @kwvalues is, so now you’ve introduced a barrier to entry).

How would you for example reuse the same value in multiple functions? Perhaps I’m missing it, but it looks like it’d become:

julia> @kwvalues optimize1(x; tolerance = 1e-10) = 3.14;

julia> defaultkwvalues(optimize1, 1)
(tolerance = 1.0e-10,)

julia> @kwvalues optimize2(x; tolerance = defaultkwvalues(optimize1, 1.0)[tolerance]) = 42;

julia> defaultkwvalues(optimize2, 1)
(tolerance = :((defaultkwvalues(optimize1, 1.0))[tolerance]),)

I also just use constants for this, like @Tamas_Papp suggests.

1 Like

Thanks for the feedback @Tamas_Papp & @bennedich!
I would also use constants to use defaults at multiple places.

The use case I had in mind was rather something that could be done with parameter structures (the way I would do in in C), e.g.

using Parameters
@with_kw struct ParamsF
    a::Int = 2
    b::Float64 = 3.
end
@with_kw struct ParamsG
    c::Int = 4
end

f(x, params) = x * params.a + params.b
g(x, y, params) = x + y * params.c
function big_simulation(x0; paramsf = ParamsF(), paramsg = ParamsG())
    y = f(x0, paramsf)
    println("Running simulation with $paramsf and $paramsg")
    g(x0, y, paramsg)
end

big_simulation(1, paramsf = ParamsF(a = 3))

If I want to change or reuse the parameters at other places in the code, I would clearly go with this approach.

But sometimes it feels so natural to me to have the parameters just as keyword arguments or I don’t want to define a parameter structure for each function. The alternative I thought of was

@kwvalues f(x; a = 2, b = 3.) = x * a + b
@kwvalues g(x, y; c = 4) = x + y * c

function big_simulation(x0; paramsf = NamedTuple(), paramsg = NamedTuple())
    paramsf = merge(defaultkwvalues(f, x0), paramsf)
    y = f(x0; paramsf...)
    paramsg = merge(defaultkwvalues(g, x0, y), paramsg)
    println("Running simulation with $paramsf and $paramsg")
    g(x0, y; paramsg...)
end

big_simulation(1, paramsf = (a = 3,))

Or do you think it is always better to use the struct of parameters approach?

You can also define those defaultkwvalues as a const NamedTuple, or just a function that returns them (which you can later modify then).