The death of optional arguments?

With many keyword arguments just 1 keyword many times is not enough to tell you what it actually does. Most of the time it does, but there are times where this is not true.

You can just use the documentation string feature using ? if you forget the argument orders. I think it’s clear from your concerns that this is more in the interactive part of coding, which is perfectly fine to use ?.

Yes, I agree with you that your example should be keyword arguments. However, why would that mean optional arguments should be removed from the language and break the examples I am showing? That makes no sense. I’m just showing there’s a use case for both. I also agree with you that optional arguments were abused too much in the past for dispatch, but that doesn’t mean they don’t have a purpose.

I am not sure that’s a particularly good example. Once you know what rstrip does, a regex or a number just don’t make sense in that context.

Well, I guess I don’t feel super strongly about this. Unless I’m being dense, max isn’t defined using optional arguments in the way I’m thinking about them at all. Maybe a better word for what I’m thinking of is default arguments?

Is it possible that you are confusing optional and variadic arguments?

I guess I am, but I always see vaiadic arguments as just optional arguments which have a default of nothing and are just ignored. In that sense, APIs with f(x,y=nothing,z=nothing) are a pretty easy and powerful way to turn off various proceeding steps.

Also, the DiffEq API sol = solve(prob,alg;kwargs...) depends on dispatching to specific solvers in different packages via alg. I think it would be very difficult to follow if alg was turned into some “special keyword argument” that chose which package solve would actually call! Instead, it’s quite clear from that prob and alg do the main dispatch, and the other kwargs are just tweaks.

I prefer to have only keyword arguments in my coding style. For example, in R I always keyword any and all arguments. One crucial difference though, is that currently keyword arguments have a default value while positional arguments do not.

It is actually impossible to remove optional arguments from Julia, because they are just syntactic sugar for a particular application of multiple dispatch. For example, f(x,y=0) = 2x+y is equivalent to:

f(x,y) = 2x+y
f(x) = f(x,0)

You can get rid of the f(x,y=0) sugar, but you can’t get rid of the underlying functionality.

Since the underlying functionality will always be there (and is often useful, as others have pointed out), I don’t see any benefit to getting rid of the sugar.

7 Likes

Maybe the original suggestion of the OP was a little strict. But in general it would be interesting to derive some kind of coding guidelines in context of the new performance behavior. Until now APIs where partially chosen due to the performance bottleneck of kw args. Maybe now one would rethink the API?

3 Likes

But in this example I now need to remember both what the second argument is supposed to be, and what it’s called. Is it chars or characters, or maybe char?

Also, verbosity is bad.

1 Like

Could somebody please explain why it would be so desirable to remove a feature like optional arguments from Julia? What is the advantage gained from removing it over keeping it?

If I understand correctly, it is to promote a clearer, more explicit, programming style, that is easier to read.

I happen to disagree that it’s easier to read (except in cases with many input parameters — more than four, perhaps?), and it’s certainly harder to write. Also, as mentioned by @stevengj, it really cannot be done, without sacrificing multiple dispatch itself.

Good point! In fact one can argue that Julia has optional arguments in any position, not just trailing. Eg consider

rand([rng], [S], [dims...])

It is just that it has syntactic sugar for trailing optional arguments. Perhaps syntax could be introduced for the other kinds, eg

foo(a = 1, b, c = 3)

Of course foo(4, 5) is ambiguous, but we already have mechanisms for dealing with that.

See allow default arguments at any place in a function by rfourquet · Pull Request #22460 · JuliaLang/julia · GitHub ! But it’s unlikely to get merged, so I’d rather make a package of it.

It is similar, except for the square brackets. Why did you introduce them?

You are right, it’s not exactly equivalent. I think we could imagine built-in default arguments not only at a trailing position, with the rule that if m arguments are missing, the m rightmost defaults are used (so in your example, foo(4, 5) would be a=4, b=5, c=3). Notice that n default values leads to n+1 methods (once de-sugared).
I used the square brackets for the case where one wants 2^n methods for n default values in square brackets. For the example you give of rand([rng], [S], [dims]), there are indeed 8=2^3 methods available, rather than 4=3+1, so both ways have their usefulness.

While I was initially enthusiastic about having optional arguments on the left side, the more I think about it, the more apparent the disadvantages become clear.

AFAICT not enforcing that methods have the same number of (non-optional) positional arguments is unique to Julia (Common Lip enforces this, and so do multiple dispatch systems descended from CLOS). It allows signatures like we currently have for Base.show and Base.rand, among other things, but it seems that in practice only a carefully chosen subset of those 2^n combinations is used (except when n is small of course).

Manually writing those methods is not much of a hassle anyway, and is perhaps more clear.

1 Like

Since there are APIs that make the most sense with or without this language feature, it seems to be a style question. Perhaps off topic, but this could be applied more consistency within a single codebase at a users request by a linter that accepts user-defined rules

1 Like