When to use argument labels (a new approach)


(Dave Abrahams) #1

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


(Matt Whiteside) #2

+1. This list makes sense to me.

Wish I had the time to give this more thought, as argument labels are one of my favorite features of Swift. I’m glad to see them getting the amount of discussion they have though.

Matt

···

On Feb 2, 2016, at 16:32, Dave Abrahams via swift-evolution <swift-evolution@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Charles Kissinger) #3

+1 to all three rules. One question though: is Rule 2 intended to apply to cases where there are multiple “attribute” parameters? Would it be:

a.tracksHavingMediaType(“Wax Cylinder”, genre: “Jazz”)
or:
a.tracks(mediaType: “Wax Cylinder” , genre: “Jazz”)
or, for that matter:
a.tracksMatching(mediaType: “Wax Cylinder” , genre: “Jazz”)
?

The “sentence” requirement of Rule 1 doesn’t seem to be met here, but the “primary semantics” part would still apply?

—CK

···

On Feb 2, 2016, at 4:32 PM, Dave Abrahams via swift-evolution <swift-evolution@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Charles Srstka) #4

+1 to all three of these.

Charles

···

On Feb 2, 2016, at 6:32 PM, Dave Abrahams via swift-evolution <swift-evolution@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Paul Cantrell) #5

+1 for 1, with an asterisk
mixed feelings about 2
+1 for 3

Notes inline below.

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:

Yes. The thoughts of “sentence” and “primary semantics” succinctly capture a lot of good ideas from previous discussion.

    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

All these examples seem totally solid.

  [Note that this covers all the direct object cases and, I believe,
  all the default argument cases too, so maybe that exception can be

Not sure about this one in theory … but yes, I can see how it would play out that way in practice.

  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.

Not sure you need this complicating exception. “a, add observer b” is still a sentence.

* 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.

Yes.

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]

I have reservations about this. It doesn’t generalize perfectly well:

  a.tracksHavingMediaType("Wax Cylinder”, andPianist: “Brahms”)

…strikes me as awkward compared to one of these:

  a.tracksHaving(mediaType: "Wax Cylinder”, pianist: “Brahms”)
  // or
  a.tracksMatchingCriteria(mediaType: "Wax Cylinder”, pianist: “Brahms”)
  // or even
  a.tracks(mediaType: "Wax Cylinder”, pianist: “Brahms”)

…especially since, in a method of that form, _all_ the arguments are likely to have a default value of nil:

  a.tracks(mediaType: "Wax Cylinder”)
  a.tracks(pianist: “Brahms”)

  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

Yes, definitely.

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

Yes, seems solid.

Cheers, P

···

On Feb 2, 2016, at 6:32 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:


(David Owens II) #6

Awesome for putting this together. I think the rules add some needed clarity, but they have the bias that the first argument *should* have a defaulted empty parameter label. I actually think that complicates matters.

Instead, if the premise was that the first argument is treated no differently then other arguments, then the rule becomes simply a list of exceptions on when the label is not necessary:

If the argument's purpose in the primary semantics is clear from the name of the function, then no argument label is required (regardless of argument position).

To be clear, this rule does not change the API guidelines statement that all labelled arguments should be placed after non-labelled arguments.

Examples:

Remove Label(s)

a.contains(b) // the question of containment of b in a is the primary semantic
a.merging(b) // combining b with a is the primary semantic

a.moveTo(300, 400) // moving to coordinates is a primary semantic of a
                    // the labels can be removed when a has a clear semantic
                    // limitation of a two-pair coordinate system

a.readFrom(u, ofType: b) // u is clearly the target of the primary semantic
                          // "reading", ofType labeled as it's a modifier

Keep Label(s) - the default behavior

a.dismiss(animated: b) // dismissal is the primary semantic;
                        // `animated` is a modifier to that semantic, so
                        // keep the label

a.moveTo(x: 300, y: 400) // moving is the primary semantic, however a
                          // has no clear semantic limitation to a 2D
                          // coordinate context so keep the labels

a.read(from: u, ofType: b) // keep from as there is no non-ambiguous semantic
                            // relationship between u and what is being read
                            // without the label. e.g. without from, it's not
                            // clear that read is reading from u, it could read
                            // from stdin.

The `moveTo` example is also an illustration of why `max` would have no labels:

max(x, y) // maximum value is primary semantic of the parameter set

I put the `a.readFrom(_:ofType:)` and `a.read(from:ofType:)` as an illustration of two valid API names that would have different applications of the rule depending on choice of the first part of the function identifier. I'll argue below which I think is better, in the general case, and the modification of rule #2 to support it.

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

I see this rule as more about how to name the APIs instead of being about the argument labels. This is also strikes at the root of our fundamental disagreement that we cannot seem to resolve. =)

You seem to treat the entire function identifier to two disjoint parts (please correct me if I'm misunderstanding you): the base name, including the relationship to the first parameter, and the supporting labels. The base name is the primary indicator of what the function does, with the inclusion of how the first argument relates to that semantic. The remaining argument labels are there mostly as secondary information.

a.tracksHavingMediaType("Wax Cylinder")
// identifier: tracksHavingMediaType(_:slight_smile:

However, I treat the entire identifier as a complete whole with no real deference to the base name over the argument labels. So the above example would be broken down like this:

a.tracksHaving(mediaType: "Wax Cylinder")
// identifier: tracksHaving(mediaType:)

**IF** we start with the premise that argument labels are required by default, the simplified ruleset I used above already answers how to put the label on regardless of which "base name" you chose.

I would then change rule #2 to this: the "base name" of the function describes the semantic intention while the argument labels are used to describe their relationship to that semantic intention or modification of how that intent is to be carried out.

a.tracksHavingMediaType("Wax Cylinder") // no, `MediaType` relates to the argument, not
                                           // primary intent of the function.

a.tracksHaving(mediaType: "Wax Cylinder") // yes, the primary intent is to find tracks
                                           // based on some criteria.

No special rules or exceptions are necessary.

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

The labelling piece is already covered by the simplified rule #1 above and by my modified rule #2, so this rule is redundant. `WithIdentifier` and `WithName` should never be present because they belong to the first parameter, not to the semantic intention of the function.

The choice between `transitionToScene(_:)`, `transitionTo(_:)`, or even `transitionTo(scene:)` is an interesting one. I do not think there is an objectively clear winner based on the limited sample context. Though if we followed the more direct guidance of the modified rule #2, the only real options left would be: `transitionTo(_:)` and `transitionTo(scene:)`.

If `a` were a scene manager and the only transitioning that would take place, it could be argued that `transitionTo(_:)` is enough.
If `a` were a general transitioning engine, then `transitionTo(scene:)` would be the choice based on the modified rules #1 and #2 as the semantic intention of transitioning is clear, but the target would not be, so the `scene` label would be kept.

For clarity: `transitionToScene(_:)` would not be a candidate as rule #2 states that qualifiers for arguments, in this case `Scene`, belong with the argument itself.

In the very least, I hope this better articulates why I don't prefer the treatment of the first argument as special, and why I do not put first argument information into the "base name" of the function.

Cheers!

-David


(Erica Sadun) #7

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 <swift-evolution@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Brent Royal-Gordon) #8

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:

  foo.remove(bar)

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:

  foo.removeFrom(bar)

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
Architechies


(Jonathan Tang) #9

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.

+1

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

Very mixed feelings on this, probably adding up to a +0.2 or so. I'll
mention a couple concerns that I haven't seen anyone raise:

Please consider the first-class function case when naming. Particularly
since Swift encourages passing functions around as objects rather than
using string selectors. #2 implies that the prepositional phrase will
appear when *referencing* the method (vs. calling it):

  let ops = [
    self.removeFirstTrackHavingMediaType,
    self.addTrackWithMediaType
    self.populateTrackOperationsForMediaType
    self.play
  ]

vs.

  let ops = [
    self.removeFirstTrack
    self.addTrack
    self.populateTrackOperations
    self.play
  ]

The second option wins on verbosity, but the first arguably gives more
clarity as to what the methods actually do. Also, the second has a
potentially annoying semantic problem: if you have overloads for these
methods that differ only in keyword, Swift won't be able to disambiguate
them:

  // Compile error: Invalid redeclaration of removeFirstTrack
  func removeFirstTrack(havingMediaType: String) { ... }
  func removeFirstTrack(named: String) { ... }
  func removeFirstTrack(byArtist: String) { ... }

  // Compile error only when the function is referenced
  func removeTrack(n: Int, named: String)
  func removeTrack(n: Int, byArtist: String)
  let f = self.removeTrack // named: or byArtist:?

  // Legal...
  func removeFirstTrackHavingMediaType(_: String) { ... }
  func removeFirstTrackNamed(_: String) { ... }
  func removeFirstTrackByArtist(_: String) { ... }

Unless the Swift compiler were to change to make the former set legal, I
think this is a powerful argument in favor of this proposal, because
otherwise you may find that the compiler prevents you from writing the code
that the guidelines encourage. You might also find that changing the type
of an overload means you have to change the name to prevent a collision,
which could be very surprising to users.

My other reservation is the flip side of this argument: when you use a
function in a first-class context, often you *want* to leave things
unspecified. After all, the whole point of parameterized code is so that
it can be used in more contexts. This comes most often with containers or
other generic functions:

  myStringArray.map(myString.appendContentsOf) // wordy and awkward
  myStringArray.map(myString.append) // cleaner

I'd consider this a less severe problem, but something I'd still like
considered, particularly given the existence of several higher-order
functions in the standard library.

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

+1

···

On Tue, Feb 2, 2016 at 4:32 PM, Dave Abrahams via swift-evolution < swift-evolution@swift.org> wrote:


(Vanderlei Martinelli) #10

+1. This is the propose that make more sense for me until now. :slight_smile:

···

On Tue, Feb 2, 2016 at 10:32 PM, Dave Abrahams via swift-evolution < swift-evolution@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Charles Constant) #11

I find arguments with prepositions easier to read, on the whole, even when
they're not strictly necessary.

Even though, this code makes sense:

* a.move_to( b )*

* draw_line( origin: b, conclusion: c )*

I'd prefer to see this:

* a.move( to_point: b )*

* draw_line( from_point: b, to_point: c )*

Similarly, for the example "*a.read_from( u, of_type: b )*" I find this
easier to read:

* a.read( from_url: u, from_type: b)*

Which makes it easier to understand if it's followed by:

* a.read( from_url: u, to_type: b)*

I find it helpful in my own code. Having said that, I'm not sure if it
would suit huge multi-person projects, since it leads to a lot of similarly
named functions.

···

On Tue, Feb 2, 2016 at 4:32 PM, Dave Abrahams via swift-evolution < swift-evolution@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(plx) #12

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.

## RULES

### 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)

#### REMARKS:

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)

#### REMARKS:

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:

`animate(duration:animations:)`
`animate(duration:animations:completion:)`
`animate(duration:delay:options:animations:completion:)`
`animateUsingKeyFrames(duration:delay:options:animations:completion:)`
`animateUsingSpring(duration:delay:damping:initialVelocity:options:animations:completion:)`

…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)
asset.trackWith(trackID)

// 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.

## GENERAL REMARKS

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”).

## REMARKS ON RULE 3

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@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Howard Lovatt) #13

+1 for 1 and 3
-1 for 2

I didn't take to 2, the no examples seemed as least as clear as the yes,
e.g. I liked:

    a.tracks(mediaType: "Wax Cylinder")

and it is consistent with everything else. Therefore I suggest you just
delete clause 2.

(Very nice write up of your thoughts!)

···

On 3 February 2016 at 11:32, Dave Abrahams via swift-evolution < swift-evolution@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
  -- Howard.


(Tino) #14

I'm quite certain that it isn't feasible to construct a set of rules that always ensure "perfect" labeling (therefore I'm not that happy that Swift has several different rules for label handling that push users towards a choice that is quite common, but doesn't fit in a significant amount of cases — but that's another topic :slight_smile:

My personal guidelines are:
- When a method has a "target" object, this should be the first parameter and have no label ("addSubview", "moveToPoint").
- The same is true when there are several targets (many examples for this a global functions like "max", "add" — and vararg arguments, which fit in fine).
- If the target is a combination of parameters, each parameter deserves its own label ("moveTo(x: 1, y: 2)" is a perfect example; I consider the target to be a tuple of two numbers)

Based on this, there are some rules of thumb:
- Parameters that specify how something should be done always have a label ("popViewController(animated: Bool)": "self" is the only target, "animated" is just a detail)
- Command names consisting of more than three words have a bad smell:
verb, [adjective/adverb], noun is all you need for clear instructions.
I wrote "command" instead of "method" on purpose, because query-like statements often need more words. Example: "isDescendantOfView(container)"
Methods like
func exchangeSubviewAtIndex(_ index1: Int <https://developer.apple.com/library/ios/documentation/Swift/Reference/Swift_Int_Structure/index.html#//apple_ref/swift/struct/s:Si>,
         withSubviewAtIndex index2: Int <https://developer.apple.com/library/ios/documentation/Swift/Reference/Swift_Int_Structure/index.html#//apple_ref/swift/struct/s:Si>)
still fit into that scheme, as they look like a shortcut combining a query with a command.

Tino


(Radek Pietruszewski) #15

Overall, great guidelines (and +1 to the rules Erica wrote up), and I’m +1 on conveying these nuances in the guidelines.

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

The rationale for doing this is stronger when we talk about automatic translation of Objective-C APIs.

But in APIs designed for Swift, I feel like this is wrong IMHO, because:

- “media type” is still a parameter, so it shouldn’t be in the base name itself
- this breaks the symmetry with other methods due to the reason above (like the “newTrack” you mentioned yourself)
- doesn’t play well with method families (searching for tracks is searching for tracks. the criteria for search are just parameters).

If we do

   trackFactory.newTrack(mediaType: "Wax Cylinder") // yes

I don’t see why it’s OK to do

   a.tracksHavingMediaType("Wax Cylinder") // yes

Of course just “tracks” is confusing, and we agree on that, but I would strongly recommend that for new APIs we don’t just name the method with a word of an already-existing instance, rather, we start it with a verb:

a.findTracks(mediaType: “BetaMax”) // or “searchTracks”, or alternatively “tracksMatching"
a.removeFirstTrackMatching(mediaType: “BetaMax”) — ad 2
fac.newTrack(mediaType: “Wax Cylinder”)

Symmetric, predictable, follows the same convention, plays well with method families (i.e. different search criterion than media type), and no clarity problems.

Ad 2: I can see why you don’t like “removeFirstTrack”. It sounds like removing _the_ first track, rather than the first track that matches criteria in parameters list. Perhaps a word like “Matching” would work well to fix this concern. (And sounds/conveys intention better than “with” or “having” IMHO)

Just my 2¢,
— Radek

···

On 03 Feb 2016, at 01:32, Dave Abrahams via swift-evolution <swift-evolution@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Radek Pietruszewski) #16

By the way. I know this isn’t the main topic for the thread, but I would adding back a shortcut syntax for making a variable name the external label. It’s tiring to have to write:

methodName(moreThanTwoWords moreThanTwoWords: Type) // exaggerated for dramatic effect.

Most methods, in my experience, still don’t need a label for the first argument, but in the ones that do, we shouldn’t penalize doing so with ugly repetitiveness.

(The Q is what syntax should do this. The old “#argument” obviously feels wrong, as “#” is otherwise reserved as “macro-like, or compiler-generated”.)

— Radek

···

On 03 Feb 2016, at 01:32, Dave Abrahams via swift-evolution <swift-evolution@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Dave Abrahams) #17

Hi Erica,

Thanks so much for going to the trouble to write this up. The problem is, I didn't know it was coming (or didn't see it in time) and composed this reply: <http://news.gmane.org/find-root.php?message_id=m24mdpuj78.fsf%40eno.apple.com>. I'm not sure where to go from here but I think that the email would probably be a better jumping off point since it contains specific concerns about what you wrote, i.e. is a response to most of the content you linked above. What do you think?

-Dave

···

On Feb 3, 2016, at 6:18 PM, Erica Sadun <erica@ericasadun.com> wrote:

https://gist.github.com/erica/dd18ec0d29958f9fb518


(Dave Abrahams) #18

Given all the awesome feedback I've gotten on this thread, I went back
to the drawing board and came up with something new; I think this one
works. The previously-stated goals still apply:

* describe when and where to use argument labels
* require labels in many of the cases people have asked for them
* are understandable by humans (this means relatively simple)
* preserve important semantics communicated by existing APIs.

Please keep in mind that it is a non-goal to capture considerations we
think have a bearing on good names (such as relatedness of parameters):
it's to create simple guidelines that have the right effect in nearly
all cases.

A. When arguments can't be usefully distinguished from one another, none
   should have argument labels, e.g. min(x,y), zip(x,y,z).

B. Otherwise,

  1. At the call site, a first parameter that has no argument label must
     form part of a grammatical phrase that starts with the basename, less
     any trailing nouns.

       print(x)
       a.contains(b)
       a.mergeWith(b)
       a.addGestureRecognizer(x)
            ^~~~~~~~~~~~~~~~~ trailing noun

     This phrase must have the correct semantic implications, so, e.g.

       a.dismiss(b) // no, unless a is really dismissing b
       a.dismissAnimated(b) // no, not grammatical
       a.dismiss(animated: b) // yes, using a label

  2. If the first argument is part of a prepositional phrase, put the
     parenthesis immediately after the preposition.

       a.encodeWith(b)
       a.moveFrom(b, to: c)

     Thus, if words are required for any reason between the preposition
     and the first argument, they go into the first argument label.

       a.tracksWith(mediaType: b, composer: c)
       a.moveTo(x: 22, y: 99)

Notes:

a. I would recommend prepositions other than "with" in nearly all
   cases, but that's not the point of these rules.
b. I can understand the aesthetic appeal of

    a.move(from: b, to: c)

   but I believe it is not a clear enough improvement to justify
   additional complexity in the guidelines.

Questions:

1. I'm not expecting these guidelines to make everybody optimally happy,
   all the time, but they shouldn't be harmful. Are there any cases for
   which they produce results you couldn't live with?

2. Are there any cases where you'd be confused about how to apply these
   guidelines?
                               
Thanks in advance for all your valuable input!

P.S. Doug is presently working on generating new importer results, based
     on these guidelines, for your perusal. They should be ready soon.

···

--
-Dave


(Jordan Rose) #19

I tend to agree with Paul and Charles' concerns here. I think it's still missing Erica's point about some paramaters being more tightly coupled to each other than to the base name. That's sort of addressed with this:

   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.

but I think "describing the primary semantics of the call" could be more explicit. Even just the word "fully [describing]" would help. Given that, "a.tracksHavingMediaType(.WaxCylinder, genre: .Jazz)" would be incorrect, because you can't ignore the "genre" part when describing the call. 'tracks(mediaType:genre:)' or 'tracksMatching(mediaType:genre:)' would both be fine, though the former includes a new convention that plural noun basenames mean filtered fetches.

Then the next problem is that the first version of the library may only have one form, and "tracksMatching(mediaType:)" would seem redundant, so it's just "tracksMatching(_:)". Then the two-argument form is added later, and suddenly they're not quite symmetrical. On second thought, maybe that's okay as long as they have the same base name.

Jordan


(Matt Whiteside) #20

I have one comment (below) about this part:

I have reservations about this. It doesn’t generalize perfectly well:

  a.tracksHavingMediaType("Wax Cylinder”, andPianist: “Brahms”)

…strikes me as awkward compared to one of these:

  a.tracksHaving(mediaType: "Wax Cylinder”, pianist: “Brahms”)
  // or
  a.tracksMatchingCriteria(mediaType: "Wax Cylinder”, pianist: “Brahms”)
  // or even
  a.tracks(mediaType: "Wax Cylinder”, pianist: “Brahms”)

…especially since, in a method of that form, _all_ the arguments are likely to have a default value of nil:

  a.tracks(mediaType: "Wax Cylinder”)
  a.tracks(pianist: “Brahms”)

I prefer the original:

a.tracksHavingMediaType("Wax Cylinder", andPianist: "Brahms”)

to any of these,

a.tracksHaving(mediaType: "Wax Cylinder", pianist: "Brahms")
// or
a.tracksMatchingCriteria(mediaType: "Wax Cylinder", pianist: "Brahms")
// or even
a.tracks(mediaType: "Wax Cylinder", pianist: "Brahms”)

Because to me, the 3 alternatives, especially the last one, all read more like hashmap accesses than method calls. In other words, they make it appear as if you are passing in arbitrary keys to be queried, rather than calling one specific method.

Matt

···

On Feb 2, 2016, at 18:59, Paul Cantrell via swift-evolution <swift-evolution@swift.org> wrote: