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

plx plxswift at icloud.com
Wed Feb 3 08:29:55 CST 2016

After reading these guidelines and seeing the responses I am glad to see some consideration given to argument labeling.

After thinking it over, I think the rules *I* would like to see can be expressed very straightforwardly much more  can be made much A minor point, but I think it’s important to distinguish between single-argument functions and multi-argument functions; doing so seems to simplify the rules (even if there are more of them).

Everything that follows is my preferences, but I generally agree with Erica’s suggestions in the concrete cases. I also think the emphasis on quasi-grammatical rules-and-roles is something of a dead-end for design guidelines and won’t include such considerations in what follows.


### I. Single-Argument Functions:

#### RULES:

- general rule: don’t label the first argument
- exceptions:
  - (a) the argument has a default value (`removeAll(keepCapacity: Bool = default)`)
  - (b) the function acts-like a constructor (covered in your rule 2)
  - (c) the “ecosystem rule” (see section III)
  - (d) the semantics of the argument are non-obvious (see below)


I’m not sure (d) actually exists, though; every concrete example I can think up either falls under rule (b) or rule (c). It may not actually need to be a rule (other than as, perhaps, the underlying motivation for rules (b) and (c)). 

My intent with (d) was to address a similar concern as in Erica’s `init(truncating …)` and `init(saturating …)`: “if a reasonable reader would be unclear which of N plausible implementation choices you are making, you may wish to label the argument, even if you only have a single such function”…but, again, it’s hard to find any examples for (d) that aren’t also some mixture of (b) and/or (c).

### II. Multi-Argument Functions:

#### RULES:

- general rule: label all arguments
- exceptions:
  - (a) omit the first label whenever the first argument is the semantic focus, and the other arguments are some mix of “details, adjustments, or modifiers”
  - (b) omit labels entirely whenever argument-ordering is irrelevant to the output (see below)


For (a), the assumption is that we have a general consensus that “in methods for which one of the arguments is the semantic focus, that argument should be the first argument”; this seems pretty widely followed. 

This rule seems to cover e.g. `addObserver(_:forKeyPath:)` and `addObserver(_:selector:name:object:)` and `encodeObject(_:forKey:)` and `present(_:animated:completion:)` (née `presentViewController(_:animated:completion:)`), and so on.

A point to bring up is that under these rules, the “evolution” of a name would be different: the just-so story for how `addObserver(_:forKeyPath:)` came to be so-called is that it *began* as `add(observer:forKeyPath:)`, but b/c the `observer` argument is the semantic focus it "made sense to move `observer` into the method name”; that is, the assumption is that functions like `addObserver(_:forKeyPath:)` are considered to be exceptions to the "base convention” and need to be justified.

Also note that "counter-examples" to rule (a) are anything for which no one argument is easily-identifiable as the semantic focus.

EG, in a function like: `adjudicate(plaintiff:defendant:circumstances:)` we can colorably claim `circumstances` is a modifier-type parameter, but we don’t—and shouldn’t!—treat either `plaintiff` or `defendant` as the semantic-focus. If you have two focuses then you have no focus, as it were.

For (b), the intuition is that whenever argument-order is irrelevant, arguments should be unlabelled; thus e.g.:

- min/max: don’t label the arguments
- hypot: don’t label the arguments
- copysign: ideally, label the arguments
- atan2: ideally, label the arguments

…and so on. Note that these examples are all "free functions”; there don’t seem to be many natural examples that *aren’t* free functions. Also, please don’t be mislead by your familiarity with e.g. `copysign` and/or `atan2`; they are used here to illustrate a general principle (argument-ordering) only, but in practice such highly-familiar “legacy functions” might be best-off given special-case handling.

### III. Naming Functions/Ecosystem Rule

The previous sections essentially assumed the function names are already-chosen (in line with existing conventions) and voice specific argument-labeling preferences.

This section deals with a few changes to how function names should be chosen.

The over-arching consideration is what I’ve been calling the “Ecosystem rule”: whenever a method a member of a “method family"—or could foreseeably become a member of such—one should aim for consistency in the base name, and use argument-labels as necessary; note that method families need not *require* argument labels: 

`contains(_: Point)`
`contains(_: Line)`
`contains(_: Shape)`

…but they *may* require them, as for example in the `login` function that has already been discussed.

The “ecosystem-rule" can also be applied somewhat more-broadly; consider the following name suggestions:


…where the first three form an obvious family, and the next two are obvious “cousins” of that family due to choice of base names.

A corollary of this policy is that the rule (3) suggestion—of omitting something like `…ForIdentifier...` or `(forIdentifier:…)`—will sometimes be overruled out of ecosystem concerns, but I suspect this will be somewhat rare in practice.

For example, consider the following naming suggestions for the “tracks” example:

// solo method (not part of any family)

// family
asset.allTracksWith(mediaCharacteristic: …)
asset.allTracksWith(mediaType: ...

// the below, instead of `trackWith` or `track(
asset.firstTrackWith(mediaCharacteristic: ...)
asset.firstTrackWith(mediaType: …)

…or the same again, but perhaps dropping the `With` if that’s the overall preference.

In any case, the overall goal behind the "ecosystem rule” is that similar things should be named similarly, and when semantic differences are small-enough it makes sense to use argument labels to make distinctions; different base names should be for functions that are at least a little different from each other.


Note that with the way I’ve tried to formulate these rules the Swift standard library should largely stay as-is. In particular:

- methods without an identifiable “semantic focus” seem rare in a standard-library context; IMHO they occur naturally, but only really within UI/application-level code, not “basic building blocks” code
- "method families” seem somewhat unnatural in “Swift-y” code outside of a small number of special-case scenarios (`contains`, various `init` families, etc.); they seem more common in UI/application-level code (e.g. for Objective-C interoperation), as default arguments cover most of the motivating use-cases

…and most of the intent in these rules is to free up some room in the guidelines so that application-level code can be written to the guidelines without going through bizarre contortions (e.g. no one would ever have *chosen* `func dismissViewControllerAnimated(_:completion:)`, and we shouldn’t have to chose between either (a) using equally-awkward constructs in our own code or (b) being “non-guideline-compliant”). 


Separately, I think rule 3 is a hair too coarse to be a good guideline as-stated.

I would split the “asking for X by name/identifier/etc.” into two cases:

- (a) asking for X by some well-known/canonical $ID (e.g., such that it is a *major* error if no X is found for $ID)
- (b) asking for X by some identifier (without a strong expectation as to whether or not such an X will or won’t be found)

…and at least as a code-reader:

- I have no objection to the proposed rule (3) in scenario (a)
- I find rule (3) very odd in scenario (b)
- I think very differently about scenario (a) and scenario (b), and would thus prefer that they look different

…and that’s my thoughts, here.

> On Feb 2, 2016, at 6:32 PM, Dave Abrahams via swift-evolution <swift-evolution at swift.org> wrote:
> This thread is related to the review of new API guidelines, but it's not
> a review thread; it's exploratory.  The goal is to come up with
> guidelines that:
> * describe when and where to use argument labels
> * require labels in many of the cases people have asked for them
> * are understandable by humans
> * preserve important semantics communicated by existing APIs.
> Here's what I'm thinking
> 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.
> 2. Words that describe attributes of an *already-existing* instance
>   should go in the base name rather than in a label:
>      a.tracksHavingMediaType("Wax Cylinder")      // yes
>      a.removeFirstTrackHavingMediaType("BetaMax") // yes
>      a.tracks(mediaType: "Wax Cylinder")          // no
>      a.removeFirstTrack(havingMediaType: "BetaMax") // no
>   [yes, we could use "With" instead of "Having", but it's more
>   ambiguous]
>   Words that describe attributes of an instance *to be created* should
>   go in argument labels, rather than the base name (for parity with
>   initializers):
>      AudioTrack(mediaType: "BetaMax")                   // initializer
>      trackFactory.newTrack(mediaType: "Wax Cylinder")   // yes
>      trackFactory.newTrackWithMediaType("Wax Cylinder") // no
> 3. (this one is separable) When the first argument is the *name* or
>   *identifier* of the subject in the base name, do not label it or
>   describe it in the base name.
>      a.transitionToScene(.GreatHall)               // yes
>      a.transitionToSceneWithIdentifier(.GreatHall) // no
>      let p = someFont.glyph("propellor")           // yes
>      let p = someFont.glyphWithName("propellor")   // no
>      let p = someFont.glyph(name: "propellor")     // no
> Thoughts?
> -- 
> -Dave
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

More information about the swift-evolution mailing list