Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Swift require parameter names if it also requires argument order?

In Swift, when you call a function, you are required to label the arguments, unless the author of the function has explicitly allow you not to. Is there a reason for this in terms of language design? I had always viewed parameter labels as a good way to allow the caller to order the arguments in whatever way makes sense to them, but this way just feels like a lot of meaningless/useless boilerplate.

For example, if we define the function makeDog like this:

makeDog(legs: int, name: String)->Dog{}

Then it must be called like this:

makeDog(legs: 4, name: "fido")

And it can't be called like this (compiler error):

makeDog(name: "fido", legs: 4)

Even the Stack Overflow tag description for named-parameters says:

Named parameters enable you to specify an argument for a particular parameter by associating the argument with the parameter's name rather than with the parameter's position in the parameter list.

like image 441
Alex N. Avatar asked Oct 11 '19 17:10

Alex N.


1 Answers

The first thing to realize is what you are referring to as 'arguments' are not actually arguments. They are the outward-facing labels given to those arguments. It is important to understand the difference as it directly pertains to answering your question.

In most languages, there is no concept of outward-facing labels. The names you see are the argument names themselves, and in most cases, they aren't used externally at all, relying on you to pass arguments by position.

In some of those languages however, like C#, you can specify the arguments ordinally or by name. If you are specifying all arguments by name, it makes sense to be able to specify them in whatever order you want, but there are no requirements to use names. What happens when you specify some ordinally and some by name? Now you're forced to put the named ones at the end because if you didn't, how would it know the position of the unnamed arguments? What happens if you specify something in the third position and you also specify that same argument by name?

In contrast, Swift strives to not only be much more consistent in its calling conventions, but it also strives to be a self-documenting language with a focus on clarity at the point of use (i.e. the call-site). As such, Swift added the concept of external/outward labels which default to being required, but it allows you to make them optional if it makes sense.

You can think of them not as argument/parameter names, but rather aides to help the call-site read more like a sentence, and just as you can't randomly reorder words in a sentence, you shouldn't be able to randomly reorder inputs to your functions.

A simple set or rules describing the above can be summed up as follows...

  • Argument labels (external) are meant make sense only at the call site so it's clear what you are passing. As such they may function more in a 'grammar' fashion than an 'identifier' fashion (e.g. the 'to' in add(4, to: 3) or the absence of one at all for the '4')

  • Argument names (internal) are meant to make sense only in the context of the function/method's internal body. The above function may be written as add(_ valA: Int, to valB: Int) which uses valA and valB internally.

  • If such a name makes sense in both contexts, you only specify it once, and it will be implicitly used in both places (e.g. func(a: Int) is the equivalent of func(a a: Int))

Now as to the why this is a good thing, let's look at some examples. Let's start with a language like C# that does let you reorder named arguments.

var result = multiplyValues(valA: 20, valB: 5)

Since you can reorder it, you can write this as well...

var result = multiplyValues(valB: 5, valA: 20)

Now in Swift, what you use at the call site aren't argument names, they are labels to help in the overall context of the signature itself.

Consider this slightly more complex function definition...

func multiply(value: Int, by factor: Double, thenAdd addlAmount: Int){}

When called, it reads like this...

let result = multiply(value: 20, by: 1.5, thenAdd: 5)

Isn't that much clearer than the C# example? But it's important to note that 'by' is not the argument name, but rather a label that makes sense given its position in the call. The actual, underlying argument is called 'factor' and that's what's used inside the body of the function's implementation.

Following the Swift design guidelines, one may even make the first argument's label optional, which now reads like this...

let result = multiply(20, by: 1.5, thenAdd: 5)

Again, it's incredibly clear what's going on, almost reading like a sentence.

Now imagine if you didn't use those labels and just used the argument names. Now the names matter in the external context. For instance, look at this

let result = multiply(value: 20, addlAmount: 5, factor: 1.5)

Clarity starts to get obscured. Are you multiplying 20 by 1.5 then adding 5, or are you multiplying 25 by 1.5? Is the answer 35 or 37.5?

And what if you then used the external names/labels? Now it's even worse!

let result = multiply(by: 1.5, thenAdd: 5, 20)

What the heck is going on?

When you reorder them in this way, it's not exactly clear what you're multiplying by 1.5, because if this is in an entity (struct, class, etc.) it could be easily mistaken that you're multiplying the implicit self by 1.5 (i.e. self.multiply(by:)), then there's some rando parameter 20 at the end without context. Again, it's not clear.

Of course, naturally you may respond 'But that's with an optional label! The order shouldn't matter if and only if all labels have to be specified.' but now you're cluttering up the rules and breaking consistency for a single, specific use-case.

Perhaps a more pertinent question is what do you actually get with that one-off use-case? With all these up-sides of the existing rules, what's a down-side? What are you not able to do by not allowing a random order?

It's the above aversion to inconsistency that led Swift to introduce a breaking change in earlier versions of Swift where the first argument's label wasn't required even if you specified one. It implicitly added a _ if you will. But why should that first argument get special treatment when compared to the others? The answer... it shouldn't! All that did was confuse people, so they changed it in a future version because again, consistency is more important than cleverness, even over introducing a breaking change.

To Sum Up...

As mentioned above, try thinking of labels as only there to help clarify things at the call site, and names to only matter in the context of a function's implementation. Prioritize the values to aid only in those respective areas.

Follow that simple rule and it will become very clear what should go where and in what order, and why it's a good thing that you can't simply reorder your arguments at the call site.

like image 93
Mark A. Donohoe Avatar answered Sep 28 '22 04:09

Mark A. Donohoe