[swift-evolution] When to use argument labels (a new approach)

Brent Royal-Gordon brent at architechies.com
Wed Feb 3 00:08:22 CST 2016

> 1. If and only if the first argument could complete a sentence*
>   beginning in the base name and describing the primary semantics of
>   the call, it gets no argument label:
>     a.contains(b)  // b completes the phrase "a contains b"
>     a.mergeWith(b) // b completes the phrase "merge with b"
>     a.dismiss(animated: b) // "a, dismiss b" is a sentence but 
>                            // doesn't describe the semantics at all, 
>                            // thus we add a label for b.
>     a.moveTo(x: 300, y: 400) // "a, move to 300" is a sentence 
>                              // but doesn't describe the primary 
>                              // semantics, which are to move in both
>                              // x and y.  Thus, x gets a label.
>     a.readFrom(u, ofType: b) // "a, read from u" describes
>                              // the primary semantics, so u gets no
>                              // label. b is an
>                              // option that tunes the primary
>                              // semantics
>   [Note that this covers all the direct object cases and, I believe,
>   all the default argument cases too, so maybe that exception can be
>   dropped.  We still need the exceptions for full-width type
>   conversions and indistinguishable peers]
>   Note: when there is a noun in the base name describing the role of the
>   first argument, we skip it in considering this criterion:
>      a.addObserver(b) // "a, add b" completes a sentence describing 
>                       // the semantics.  "Observer" is omitted in 
>                       // making this determination.
> * We could say "clause" here but I think making it an *independent*
>  clause doesn't rule out any important use-cases (see
>  https://web.cn.edu/kwheeler/gram_clauses_n_phrases.html) and at that
>  point, you might as well say "sentence," which is a more
>  universally-understood term.

I've been considering this for a bit, and I'd like to suggest a different way of thinking about things. This is kind of high-level and may need to be translated into concrete guidelines, but it's guided my thinking about good Swift method names recently.

You can broadly separate parameters into operands and options. Operands slot into the "sentence" formed by the method name; options generally do not.

Operands should generally *not* be labeled explicitly with the name of what's being passed in that slot; the name should fall out by grammatical implication. For instance, in:


The only sensible way of reading that is that `bar` will be removed from `foo`. If it were the other way around, it would have to be:


A second operand should usually be labeled, but not explicitly—only by grammatical implication. Again, this is because the structure of the "sentence" should imply the role of each operand. So when we want to insert into an array, which has two operands, we say:

	foo.insert(bar, at: 0)

The grammar used to label the first parameter, however, should be included in the method name itself. In a sense, the method's name should be thought of as "insert at", but the constraints of Swift syntax demands we use a parameter label for the "at".

(There are, of course, the twin exceptions of omitting completely vacuous labels and labeling the meaning of operands whose type is not specific enough to imply their meaning. Both of these are at work in `reduce(_:combine:)`; it should theoretically be something like `reduce(_:with:)`, but `with` is vacuous and the type of the parameter is not strong enough to imply its meaning and it needs to be labeled `combine`.)

Options, on the other hand, *are* explicitly labeled with the meaning of the parameter, because they aren't as directly connected to the sentence and it would be difficult to imply their role. Hence, in something like:

	foo.lexicographicalCompare(foo2, isOrderedBefore: <)

The second parameter is an option and has to be explicitly labeled with its meaning. Options *often* have default values, but not always; for instance, the `animated` parameter in many UIKit methods is an option, but Swift does not give it a default value. 

Because options should be explicitly labeled with their meaning but operands should have their meaning implied, options usually have the same label and variable name, while operands usually have different labels and variable names. This is not *always* the case, but it's a pretty strong hint.

*Usually* the first parameter is an operand and the other parameters are options, and so by default, Swift gives the first parameter an empty label and the remaining parameters a label matching their variable name. But you may have to override that in either direction if your method doesn't quite fit that norm.

Incidentally, I would like to suggest one small change based on this conception: the `min` and `max` free functions should be called `minOf` and `maxOf`. This both reads slightly better and gives them distinct names from the `Collection` methods.

* * *

As long as I'm here, a word about method names.

I've seen some suggestions that method names should be only verbs, and so `-addSubview:` should be `add(subview:)`. I don't think this is useful or appropriate.

What you need to understand about this is that the `Subview` in `-addSubview:` is not really labeling the parameter. It's connecting the method to the `subviews` collection. Similarly, in `-addObserver:forKeyPath:options:context:`, the `Observer` is connecting the method to the (private, side-table-stored) observer list. This also reflects the fact that, even though these two operations both "add" something, they are massively different—`addObserver` and `addSubview` have almost nothing in common.

Importing `-addSubview:` as `add(subview:)` would be like importing `-setNeedsDisplayInRect:` as `set(needsDisplayInRect:)`. The method name quite simply no longer describes the operation being performed.

Brent Royal-Gordon

More information about the swift-evolution mailing list