Isn’t mod like the exact case of a function where this is just unhelpful line noise? And that’s at the crux here: the names that package/language developers use just aren’t necessarily going to be informative to the callers. Just use units_sold! That’s what you care about.
Yes, sometimes the callers and the devs use the same language, and it’s hugely beneficial! But not always. And that separation of concerns is sensible, in my view.
At least for me it’s helpful, as I can’t ever remember what the order of operations are in mod functions:
mod( arg1, arg2 ) … is that dividing arg1 into arg2 or arg2 into arg1?
But when I see mod( x, y ) it triggers my memory of x divided by y. And so in every language, I always put comments around mod or rem functions to remind myself.
I think there are people that would really like to know if the function mod(x, y) was changed to mod(x, z). So ideally a prompt in the ide, or package diagnostic, or (less desirable) a runtime error when running the unit test suite.
Writing leftover_units = mod( x = units_sold, y = units_per_package )
But having the compiler use leftover_units = mod( units_sold, units_per_package )if and only if the names and positions of x and y are still consistent with when I wrote the code, could be helpful.
Some folks here want to be able to write functions (that is, literally write — as they develop the code) without worrying about the exact order of the positional args. Just the names are enough, in any order. You’d still need to look up/know the names, but you wouldn’t need to know the order.
Some folks here want to be able to read function calls as being self-documented, even if the args already match the defined positional order. Comments, variable names, etc, can all do the trick, but, yeah, none are enforced to match the defined argument names.
But either way, I fear this is a PSA: Julia is not at that stage of development anymore thing. Making a change like this would drastically expand the surface area of every language and package API that’s ever existed in ways that weren’t necessarily intended.
Advanced LSP servers solve these problems. They can show the definition of functions/methods when you type them and show the name of parameters with inline hints.
I think it’s fine to have position-only arguments as an option. Sometimes, position-or-keyword arguments don’t make sense, if the keyword argument is poorly-named. However, the default should be position-or-keyword (like Python, Swift, R, and most other languages have adopted). The problem is that right now, people just default to positional arguments (because keywords are slower).
“most” is doing a whole lot of heavy lifting there. there’s definitely a decent chunk that have made that choice (e.g. the ones you listed + Kotlin, Fortran, Scala, F#, probably a few more), but I wouldn’t be so bold as to present this as a universal/“obvious” design choice
In any case, the omission of this type of parameter certainly doesn’t make Julia an outlier
No, it’s definitely the multimethod aspect that makes position-only arguments much more useful, as I explained earlier about KeywordCalls.jl.
I’m not sure if function overloading counts as multimethods but it’s similar to compile-time method dispatch so I checked if any language had both function overloading and position-or-keyword arguments. I ran into Kotlin, and when I adapted the ambiguous method dispatch example I wrote earlier, it just caused a Overload resolution ambiguity. The positional arguments calls worked just fine.
It’s perhaps not more natural, the pedantic approach would have been, perhaps, to name the arguments to mod to be dividend and divisor, but the mental picture I’m reminded of when I see mod( x, y ) are the various lists of elementary operators:
Irrational, I know, but it’s an example of why I like to write my code in such a manner. I find that “future-Greg” curses “past-Greg” a lot less when I do.
Public methods should have no more than three positional arguments, with three discouraged unless it really makes sense, e.g. mapreduce(f, op, x) or fit(model, X, y).
…Granted, not all package developers have heard of this rule-of-thumb. I don’t have a reference, but I have seen something like it mentioned on Discourse.
And, of course, it is just a rule-of-thumb. It is subjective, and there are exceptions.
A similar rule should apply for keyword arguments. Imho, Python/R tend to overuse keyword arguments tending towards one-stop kitchen-sink functions with many, many keyword arguments due to the perceived readability of them. Yet, eventually the number of concepts you have to hold in your head trumps the perceived simplicity of surface syntax.
Especially with keyword arguments acting like configuration switches this leads to excessive branching inside a single function. Further, different parts of keyword options are often meaningful together, but independent of other groups of arguments without any visual cues about that. Personally, I would prefer smaller functions with clear separation of concerns, i.e.,
put options that belong together into dedicated structs and pass these – naturally giving you named arguments for free
separate different aspects of the functionality into designated functions, e.g., instead of an iterations argument return an iterator and let the caller choose the desired number of iterations or pass in a function/functor for handling termination conditions etc.
use a fluent interface building up the final configuration in small logical steps, e.g., compare reading CSV in Spark and Pandas.
I don’t really care about variables named x and z. On the other hand, I’d certainly prefer it if these names were exposed, so that developers could give them better names than x and y, like mod(dividend, modulus).
The main point is that with keywords you don’t have to hold those concepts in your head, unlike with positional arguments (where you have to remember the concept associated with each argument).
Even assuming the developer makes the name public API and won’t change it, that syntax right now specifies x as a keyword argument (keyword-only, there’s no such thing as positional-or-keyword).
julia> function venus(;x::Real) # continuing from your example
println("Key x")
end
venus (generic function with 3 methods)
julia> venus(x = 1.100)
Key x
So there’s no way we can make that syntax use positional arguments without breaking Julia v1. A macro can transform that syntax to something that does, or transform the function like KeywordCalls.jl does.