Allow use of named-argument syntax for positional arguments?

This feature already exists. Here’s the syntax:

function f(; person::String, from::String)
    hometown = from
    return "Hello, $(person)! Glad you could visit from $hometown."
end

julia> f(person = "Bill", from = "Cupertino")
"Hello, Bill! Glad you could visit from Cupertino."
3 Likes

The trouble with this is that you need to know which arguments passed with key = val syntax are positional and which are keyword arguments before you can choose a method. But you don’t know that until you’ve selected a method. For example, if I see f(x = 1, y = 2, z = 3) how do I know which subset of x, y and z are positional and should be used to dispatch f and which are keywords and should be ignored during dispatch? There are 8 = 2^3 possibilities to consider and that number grows exponentially with the number of arguments.

As a higher level observation, Swift was designed to appeal to and make sense to a large existing base of Objective-C programmers and Objective-C does some very unusual stuff with keyword names and dispatch. When someone writes [obj name:argument] in Objective-C, the name is the name of the method that’s sent to obj. So the “keyword names” are actually the name of the method to invoke—any dispatch that’s done happens after looking at the set of keyword names. That’s kind of the opposite of what Julia does, where the dispatch is done ignoring keyword arguments and then keyword values are used to pass additional data to the selected method as though they’d just been assigned at the top of the method body.

More generally when it comes to APIs, if the arguments to a function need labels in order for the call’s meaning to be clear, then the API should probably be reconsidered. Either the arguments should be keywords so that the name is required, or as has been suggested, you could use a structure to hold the options.

4 Likes

I’m going to second what Stefan just said. It seems like the proposal mostly centers around wanting functions to be called with explicitly named arguments. You can already do this with keyword arguments.

1 Like

I understand the reasons given against named arguments syntax and don’t have a solution for them. However, the current situation definitely has some rough edges and inconsistencies that would not arise at all if naming any arguments was allowed. Several examples from widely used mature libraries:

# inconsistency when it would make lots of sense to allow both variants:
mean([...], dims=1)  # OK
mean([...], 1)  # error
# vs
splitdims([...], dims=1)  # error
splitdims([...], 1)  # OK
# does it read from a and write to b, or the other way?
map!(x -> x * 2, a, b)
# wouldn't it be better?
map!(x -> x * 2, src=a, dest=b)

# which function is mapping, which is reduction?
mapreduce(f, g, [...])

There are several ways that the API in Base could be made more consistent — this was done prior to 0.7/1.0 and it can be done again for 2.0. You may want to check if there is an existing issue for things you notice, and if not, open one and ask for a 2.0 milestone.

That said, in

the convention for ! is to modify the first argument, while

the API is consistent with the function name (f is for mapping, g is reducing).

2 Likes

This makes perfect sense, thanks for the detailed analysis. I guess you could use a different syntax for naming positional arguments but that would be pretty ugly.

This is really the crux of the issue - what is “clear” or not is both hugely subjective, and dependent on context. The current situation means we have a code-style decision relating to consumer code, but it is needing to be decided by API authors, not API consumers.

There’s also the issue that even if an API author decides she wants to force all her consumers to write akdor1154-style code with kwargs everywhere, and is OK with irritating the rest of the community in doing so, she cannot use dispatch on kwargs.

Both @CameronBieganek’s fondness of concise mathematical-style functions and @aplavin’s dislike of perceived ambiguity are perfectly valid, and I’d love to work towards a solution where both of these desires can be satisfied.

1 Like

@StefanKarpinski I’ve had this in the back of my mind for a while - a solution to the problem you raise would be as simple as requiring callers who want to give kws to positional args put a semi in, wouldn’t it? So I as a caller would have to do f(x=4, y=5; z=6) to pass x,y as named positionals. Seems reasonable to me, given everyone really has to be aware about the diff between pos and kwargs anyway.
You’d probably also need to require a trailing semi for the case of named posargs and no kwargs.

I’ve had a shot at a macro implementation of this as per Tamas’ suggestion above: https://github.com/akdor1154/NamedPositionals.jl

@np train_model(myModel, max_iters=100, k_weight=0.4, j_weight=0.2;)

I’ve not yet published it to the registry but will do so as soon as I iron out a couple more rough edges. Please have a play! There will definitely be situations where you can break it :slight_smile: Hopefully some of them are fixable…

2 Likes

Looks great! I also agree with this:

Allowing the caller to re-order arguments: nope. Argument order is hugely important in Julia, it shouldn’t be hidden or abstracted. Hiding this is potentially a huge footgun, and would yield major WTFs from anybody who ever reads your code.

(from your readme)

Basically, this should work as just an extra check to confirm that argument names match?

Exactly right.