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

Dave Abrahams dabrahams at apple.com
Wed Feb 3 23:40:27 CST 2016


on Tue Feb 02 2016, Erica Sadun <swift-evolution at swift.org> wrote:

> _______________________________________________ swift-evolution mailing list
> swift-evolution at swift.org https://lists.swift.org/mailman/listinfo/swift-evolution
>
> Thoughts? Thoughts:
>
> Swift prizes clarity. Its parameter labeling system emphasizes
> self-documentation and guides code production. In nearly every case,
> labels follow three simple rules:
>
>   * Skip argument labels for a method or function's first parameter
>   * Use argument labels for a method or function's subsequent parameters
>   * Require argument labels for initializers
>
> These base rules enhance Swift legibility. Unlike other languages
> whose positional argument names have meaning only within the
> implementation context, Swift's labels convey use and meaning at the
> calling site. This creates better communication, enhances
> maintainability, and adheres to the principle that code is written
> rarely and read and reviewed often.

This is probably too expository for the body of the API guidelines, at
least the way they're currently written; they're supposed to be usable
as a reference where you can quickly get the gist of most of the
guidelines without diving in.  We could talk about changing that, but
we'd want to do it holistically, not just for this section.  (Also, we
have tried hard to make the guidelines an example of both clarity and
“omit needless words.”)

Therefore, I'm planning to focus on the examples and the substance of
the guidelines you are suggesting and—for now at least—look past
elements of style or tone.

> At times, special circumstances may apply to your code as explored in
> the following rules:
>
>   * Skip first argument labels when the first argument completes a
>   sentence established in the base name. If the argument describes a
>   call's primary semantics, it does not require a label:

That phrasing is less clear than what I posted, because it implies that
a parameter that describes a call's primary semantics, but does not also
complete a sentence describing the primary semantics, does not require a
label.  It also implies that an argument can somehow, by itself,
describe the call's semantics, without considering the base name.  I
don't see how this phrasing offers any advantages over what I wrote.
Am I missing something?

>     a.contains(b)  // b completes the phrase "a contains b"
>     a.mergeWith(b) // b completes the phrase "merge with b"
>     a.readFrom(u, ofType: b) // "a, read from u" describes
>                              // primary semantics so u gets no
>                              // label. 
>                              // b is an option that tunes the 
>                              // primary semantics


>   * Skip the first argument label when a noun in the base name describes the first
>     argument's role.
>
>    a.addObserver(b) // "add b" completes a meaningful sentence that
>                     // defines the intentended semantics.  The first
>                     // argument is the "Observer".

To start out by saying the general rule is “skip the first argument
label” and then go on to give a bunch of specific cases where you should
skip the first argument label seems redundant.  Couldn't you just
eliminate both of these examples?

>   * Move the first argument label to the base name when it describes a
>     name or identifier that acts as the subject of the base action.

If the baseline condition is “no argument label on the first parameter,”
how can you “move the argument label?”  This makes no sense to me.

>      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

These are not examples of moving an argument label.

>   * Move the first argument label to the base name when it describes argument attributes of
>     existing instances.

Same problem here.

>      a.tracksOfMediaType("Wax Cylinder")      // yes
>      a.removeFirstTrackOfMediaType("BetaMax") // yes
>
>      a.tracks(mediaType: "Wax Cylinder")            // no
>      a.removeFirstTrack(havingMediaType: "BetaMax") // no
>
>   * Use first label arguments when the first parameter is semantically
>     distinct from the base name and does not complete a meaningful
>     "sentence"
>
>     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.

“Semantically distinct from the base name” seems a little bit hard to
nail down.  Why is this an improvement over my original phrasing?

>   * Use all argument labels when the relationship between arguments is semantically
>     stronger than the relationship between the first argument and the base name.
>
>     moveTo(x: a, y: b)
>     login(userName: a, password: b)
>     constructColor(red: r, green: g, blue: b, alpha: a)

I have no quibble with the spirit of this item, but it seems completely
unnecessary to say this, because, as I wrote to Jordan, a sentence
describing the primary semantics never ends with the first argument in
these cases.  It's crucial to have as few special clauses as possible in
the guidelines.

>   * Omit labels for argument peers that cannot be usefully distinguished.
>
>     min(number1, number2)
>     zip(sequence1, sequence2)

No change in substance here from what I've already proposed, AFAICT.
Please correct me if I'm wrong.

>   * Use explicit argument labels to describe attributes of an instance
>     that's being created

>     . Your calls should resemble initializers.
>
>      AudioTrack(mediaType: "BetaMax")                   // initializer
>      trackFactory.newTrack(mediaType: "Wax Cylinder")   // yes
>
>      trackFactory.newTrackOfMediaType("Wax Cylinder")   // no

No change in substance again, AFAICT. Please correct me if I'm wrong.

>   * Use first argument labels that would have normally appeared in the base name when
>     building groups of related calls whose implementations are distinguished specifically
>     by their parameters. Your calls should resemble initializers. 
>
>   login(userName: a, password: b) // not loginWithUserName(a, password: b)
>   login(credential: a) // not loginWithCredential(a)

It seems clear to me that the specific means of authorization is not
part of the method's primary semantics, so there's no need to say this,
at least for these examples.  

Furthermore, I'm wary of adding anything that encourages the creation of
“method families,” which have a higher cognitive overhead than many of
the alternatives.  

If you can show some reasonable examples where you want an argument
label despite the primary semantics being describable by a clause ending
with the first argument, we could talk about this one further, but
there's another issue you should be aware of: a guideline that forces
you to change an existing non-overloaded API just because you are adding
an overload, is problematic.  That would seem to be the implication of
this bullet.

>   * Skip first argument labels for initializers when using full width type conversions,
>     that is when initializing from instances of another type.
>
>  extension String { 
>      // Convert `x` into its textual representation 
>      // in the given radix
>      init(_ x: BigInt, radix: Int = 10) 
>  }
>  text = "The value is: "
>  text += String(veryLargeNumber)
>  text += " and in hexadecimal, it's"
>  text += String(veryLargeNumber, radix: 16)

Again no change in substance, AFAICT. Please correct me if I'm wrong.

>   * Use first argument labels when narrowing initial values to make it conform to
>     restrictions within the new type. The label should describe how the instance will be
>     modified:
>
>  extension UInt32 {
>      init(_ value: Int16) // Widening, so no label 
>      init(truncating bits: UInt64)
>      init(saturating value: UInt64)
>  }

Ditto.

> -- E
>
>     On Feb 2, 2016, at 5: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
>
> Thoughts? Thoughts: Swift prizes clarity. Its parameter labeling system emphasizes
> self-documentation and guides code production. In nearly every case, labels follow three
> simple rules: Skip argument labels for a method or function's first parameter Use argument
> labels for a method or function's subsequent parameters Require argument labels for
> initializers These base rules enhance Swift legibility. Unlike other languages whose
> positional argument names have meaning only within the implementation context, Swift's
> labels convey use and meaning at the calling site. This creates better communication,
> enhances maintainability, and adheres to the principle that code is written rarely and read
> and reviewed often. At times, special circumstances may apply to your code as explored in
> the following rules: Skip first argument labels when the first argument completes a
> sentence established in the base name. If the argument describes a call's primary
> semantics, it does not require a label: a.contains(b) // b completes the phrase "a contains
> b" a.mergeWith(b) // b completes the phrase "merge with b" a.readFrom(u, ofType: b) // "a,
> read from u" describes // primary semantics so u gets no // label. // b is an option that
> tunes the // primary semantics Skip the first argument label when a noun in the base name
> describes the first argument's role. a.addObserver(b) // "add b" completes a meaningful
> sentence that // defines the intentended semantics. The first // argument is the
> "Observer". Move the first argument label to the base name when it describes a name or
> identifier that acts as the subject of the base action. 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 Move the first argument label to the base name when it describes
> argument attributes of existing instances. a.tracksOfMediaType("Wax Cylinder") // yes
> a.removeFirstTrackOfMediaType("BetaMax") // yes a.tracks(mediaType: "Wax Cylinder") // no
> a.removeFirstTrack(havingMediaType: "BetaMax") // no Use first label arguments when the
> first parameter is semantically distinct from the base name and does not complete a
> meaningful "sentence" 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. Use all argument labels when
> the relationship between arguments is semantically stronger than the relationship between
> the first argument and the base name. moveTo(x: a, y: b) login(userName: a, password: b)
> constructColor(red: r, green: g, blue: b, alpha: a) Omit labels for argument peers that
> cannot be usefully distinguished. min(number1, number2) zip(sequence1, sequence2) Use
> explicit argument labels to describe attributes of an instance that's being created. Your
> calls should resemble initializers. AudioTrack(mediaType: "BetaMax") // initializer
> trackFactory.newTrack(mediaType: "Wax Cylinder") // yes trackFactory.newTrackOfMediaType
> ("Wax Cylinder") // no Use first argument labels that would have normally appeared in the
> base name when building groups of related calls whose implementations are distinguished
> specifically by their parameters. Your calls should resemble initializers. login(userName:
> a, password: b) // not loginWithUserName(a, password: b) login(credential: a) // not
> loginWithCredential(a) Skip first argument labels for initializers when using full width
> type conversions, that is when initializing from instances of another type. extension
> String { // Convert `x` into its textual representation // in the given radix init(_ x:
> BigInt, radix: Int = 10) } text = "The value is: " text += String(veryLargeNumber) text +=
> " and in hexadecimal, it's" text += String(veryLargeNumber, radix: 16) Use first argument
> labels when narrowing initial values to make it conform to restrictions within the new
> type. The label should describe how the instance will be modified: extension UInt32 { init
> (_ value: Int16) // Widening, so no label init(truncating bits: UInt64) init(saturating
> value: UInt64) } -- E > On Feb 2, 2016, at 5:32 PM, Dave Abrahams via swift-evolution
> 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
>

-- 
-Dave



More information about the swift-evolution mailing list