11
votes

The question is about 'best practice' in Julia. I have read this and this. I have a function

function discount_rate(n, fv, pmt, pv; pmt_type = 0)
...
end

The problem right now is I have to call the method like so

discount_rate( 10, 10, 10, -10 )

It's not clear what these arguments mean -- even I forget. What I'd love to do is write

discount_rate( n = 10, fv = 10, pmt = 10, pv = -10 )

That's clearer: easier to read and understand. But I can't define my method by making these arguments keywords arguments or optional arguments because they don't have natural defaults. From the point of view of design, is there a recommended way around this?

4

4 Answers

7
votes

Could do the following:

function discount_rate(;n=nothing,fv=nothing,pmt=nothing,pv=nothing,pmt_type=0)
    if n == nothing || fv == nothing || pmt == nothing || pv == nothing
        error("Must provide all arguments")
    end
    discount_rate(n,fv,pmt,pv,pmt_type=pmt_type)
end

function discount_rate(n, fv, pmt, pv; pmt_type = 0)
    #...
end
0
votes

As a follow up, it got a little tedious to have to (re)write the keywords-only counterparts for the functions I already had. Inspired by Iain's answer above, I wrote a macro that essentially does the same thing...

macro make_kwargs_only( func, args... )
 quote
  function $( esc( func ) )( ; args... )
   func_args = [ arg[2] for arg in args ]
   return $( esc( func ) )( func_args... )
  end
 end
end

And so, for example

f( a, b ) = a/b
@show f( 1, 2 )
f(1,2) => 0.5

Creating its keywords-only counterpart gives

@make_kwargs_only f a b
@show f( a = 1, b = 2 )
f(a=1,b=2) => 0.5

Note however this is not the general case. Here the order of the argument is crucial. Ideally I would've liked the macro to work the same way for both f( a = 1, b = 2 ) and f( b = 2, a = 1 ). That is not the case.

@show f( b = 2, a = 1 )
f(b=2,a=1) => 2.0

So for now, as a hack, I use methods( f ) if I can't remember the order of the arguments. Any suggestion on how to rewrite the macro to handle both cases is welcome...maybe a way to sort func_args within the macro's function definition based on the function signature of func?

0
votes

It is worth noting that Julia introduced mandatory keyword arguments in v0.7:

julia> foo(; a) = a
foo (generic function with 1 method)

julia> foo()
ERROR: UndefKeywordError: keyword argument a not assigned
Stacktrace:
 [1] foo() at ./REPL[1]:1
 [2] top-level scope at none:0
0
votes

Is this answer any different in 1.5? In Python, whenever I write a function with several arguments, I usually call it with keyword arguments. It doesn't seem like there's a clean way to do that in Julia without either (1) giving them default values (could be sentinal values, as suggested by Iain) or (2) making them mandatory keyword arguments (as suggested by gTcV).