Best Practice: Default Values/Optional Arguments/Readable Code

Some time you want to have default values.
Sometimes you want to be relaxed about the order parameters are handed in.
Sometimes you just want to write readable code:

discount_rate(;risk_category = 'AA+', years = 3, amount=$3,000,000, starting_date=20190312, )

The issue of default values in type constructors in Julia v1.x is still wet paint, with the Parameters.jl alternative being available as a 3rd party package.

Of the two approaches available in Julia v1.x, each has drawbacks:

  1. Positional Arguments with default values:
    • Extra function methods get defined automagically.
    • They can get overwritten out of sight/silently, just as automagically.
    • Result in a code base that is not understood (has behavior not covered by test cases while cov metrics might be green).
  2. Keyword arguments:
    • Function methods don’t dispatch on keyword arguments.
    • Result in dispatch behavior changing (issue #9489).
    • Can also overwrite an existing default method silently.

Others have come this way before:

Using only Julia Base v1.x (i.e. not Parameters.jl):

What is the best practice or ‘habit’ to develop for using default values or writing readable code that will:

  • preserve Julia’s performance
  • avoid the other issues mentioned above
  • likely cause the least pain between 1.0 and 2.0?

It might help to use an example(s) from the posts listed above.


I also wrote FieldDefaults.jl, mostly so you can easily view what the defaults are and use them outside of the constructor by defining methods that dispatch on type and fieldname.

Whatever the accepted method ends up being I think they need to be easily introspected, like default(::Type{T}, ::Type{Val{:fieldname}}), or at least defaults(T) and return a tuple.


This is more verbose, but it forces the default variable definition logic checks into the type inner constructor.

mutable struct RateVariables
    function RateVariables(p)
        # Enforce the default value if a parameter is missing
        category = get!(p, :risk_category, "AAA+") 
        horizon = get!(p,:years, 30)
        value = get!(p,:amount, 1000000)
        start = get!(p,:starting_date, 20190101)
        # Do anything else with the given & default values

# The all default values case

# Some parameters given:
# Note without {symbol, Any} passed to Dict you'll get a int -> string type conversion error when the
# string parameter is inserted
rv = RateVariables(Dict{Symbol,Any}( :years => 3, :amount=>1, :starting_date=>20000101))

Is it really so straightforward to say to a Julia newcomer:

If you want to use optional/default parameters, the simplest robust approach in Julia 1.x is to use a Dict, a mutable struct (aka type) and its inner constructor?

Appreciate any suggestions that are simpler.

1 Like

I think I have avoided these problems by not allowing empty defaults, and adding only one method in the macro:

T(; kwargs...)

I’m not sure what that could overwrite silently, or how it would be overwritten by other methods accidentally, unless I am missing something.

Thanks for pointing out FieldDefaults.jl. I’ll look at to learn more Julia-fu.

Right now my costraint is to use std/base julia Packages

I was referring to the existing way Julia handles default/optional values using positional arguments and Keyword arguments - they stand on each other’s toes.

Sorry I wasn’t clear. I agree with you that those methods with optional fields are risky and complicated, thats why I avoided them - but it wasn’t really because I understood the problems you have layed out!

I also nearly always wantSomeParams() to provide a completely functional default, so having fields without defaults is kind of irrelevent to me.

Edit : And no FieldDefaults.jl is definately not standard! the FieldMetadata.jl trick with field level traits for metadata storage is in the “weird things julia can do, but should it?” category, although I use it all day for basically everything…

1 Like