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:
- 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
- Create a bunch of custom types e.g.
struct ExpectedReturn
and then use typingfunction utility(expected_return::ExpectedReturn,...); utility(ExpectedReturn(0.1)...)
—but this is overbearing! - 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?