What is a good way to write methods that help the user not mix up input order?

philip
Fri, 17 Feb 2023 18:29:05 +0000
General Usage

I’ve often encountered issues similar to Keyword argument types - what's going on? - #11 by sijo . I like to write methods with keyword arguments because I find them harder to silently screw up calling than positional arguments. Making up an example, let’s consider a function which gives utility from mean and variance of returns U = E[r] - \frac{1}{2} A \sigma^2:

function utility(expected_return, sd_of_returns, risk_aversion)
    expected_return - 1/2 * risk_aversion * sd_of_returns^2
end

Suppose a stock has E[r_s] = 10\% and \sigma_s = 20\% and an individual’s risk aversion A = 2, then this could be called:

julia> utility(0.1, 0.2, 2.0)
0.06

Since these are all Float64, nothing stops someone from making a mistake such as the following:

julia> utility(0.2, 0.1, 2.0)
0.19

Of course, the potential mistakes increase in the number of arguments, the complexity of their meaning, etc.

Now. If the function is rewritten to use keyword arguments, there is visually no ambiguity:

julia> function utility(;expected_return, sd_of_returns, risk_aversion)
                         expected_return - 1/2 * risk_aversion * sd_of_returns^2
                         end
utility (generic function with 1 method)

julia> utility(0.1, 0.2, 2.0)
ERROR: MethodError: no method matching utility(::Float64, ::Float64, ::Float64)
Stacktrace:
 [1] top-level scope
   @ REPL[2]:1

julia> utility(risk_aversion=2.0, sd_of_returns = 0.2, expected_return = 0.1)
0.06

Lovely. But, keyword-argumenting everything seems to be discouraged by the design of the method dispatch system. Furthermore, unlike in this MWE, often I want to write multiple methods that do dispatch differently on types of arguments. But I’d still like to reduce argument order ambiguity.

There are clearly some possible solutions, including:

1. GitHub - simonbyrne/KeywordDispatch.jl: Dispatch on keyword arguments as mentioned in the previously linked post here—but this is currently failing CI and again does not seem to be an encouraged approach

2. Create a bunch of custom types e.g. struct ExpectedReturn and then use typing function utility(expected_return::ExpectedReturn,...); utility(ExpectedReturn(0.1)...) —but this is overbearing!

3. Similar approach with typing the input itself in a new type (perhaps à la Parameters.jl, @with_kw struct UtilityInput ... end; utility(UtilityInput(expected_return = 0.1, ...))…but once again this is quite overbearing.

Is strikes me as strange that simply essentially offering the user a “tip” of being able to tell the function/method “here’s the name of the argument I’m trying to pass this value to” needs to break dispatch. What’s so special about argument order? Couldn’t an equivalent ordered-argument method always be present “under-the-hood” for any given named-argument function? (I assume, without being able to understand its source code, that this is what KeywordDispatch.jl tries to achieve.)

TL;DR, is there not a way that combines the elegance, simplicity, and error-preventiveness of keyword arguments with the elegance, power, and stability of positional argument method dispatch?

8 posts - 6 participants