When to use argument labels (a new approach)

I suspect that you missed SE-0021:

  https://github.com/apple/swift-evolution/blob/master/proposals/0021-generalized-naming.md

which lets you name the arguments in a function reference, e.g:

  let f = self.removeTrack(_:named:)
  let f2 = self.removeTrack(_:byArtist:)

  let ops = [
    self.removeFirstTrack(mediaType:),
    self.addTrack(mediaType:),
    self.populateTrackOperationsForMediaType,
    self.play
  ]
  
  - Doug

···

On Feb 2, 2016, at 10:17 PM, Jonathan Tang via swift-evolution <swift-evolution@swift.org> wrote:

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

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.

Hi Erica and Dave,

I personally like it and I agree with all except the following:

let p = someFont.glyph("propellor")
let p = someFont.glyphWithName("propellor")

I found the second one more clear. I am not familiar with the API and when I read the first one I started wondering what it would do. Then I read glyphWithName() and I knew that I would be getting me a glyph back named "propellor".

Also, the bullet reads:

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

I think it should also mention that the word "Name" and "Identifier" should be omitted from the base name of the function or method.

Everything else looks great in my opinion.

Thank you

···

Sent from my iPhone

On Feb 3, 2016, at 12:07 AM, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

The current syntax doesn’t bother me too much, but I think just omitting the first argument and treating the second as the first could work:
func methodName(, moreThanTwoWords: Type) { … }

It may be less confusing when a function has a labeled variant with the same name:
func methodName(, aLabel: Type) { … }
func methodName(notALabel: Type) { … }

However, I think this would not be common enough to worry about.

Eric

···

On Feb 3, 2016, at 12:19 PM, Radosław Pietruszewski via swift-evolution <swift-evolution@swift.org> wrote:

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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

+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”)
?

Thanks for asking. We thought about this, and on quick examination, we weren't able to find enough
cases like this in Cocoa to make it obviously worth handling specially
in the importer... and as a result I omitted it from my suggestion,
oops. The thought was that if you have a word that needs to “distribute”
over multiple arguments, you keep that outside the parens, per

   a.tracksHaving(mediaType: "Wax Cylinder", genre: "Jazz")

we could discuss whether it's better to go with

   a.tracksHaving(mediaType: "Wax Cylinder", andGenre: "Jazz")

here in order to reinforce the distribution.

[Yes, you could use "matching" here as well. I don't have a strong
preference either way.]

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

I don't know what you mean; AFAICT, both parts apply; you have to apply
them together. You omit the argument label if and only if you can
describe the primary semantics with a complete sentence starting in the
base name and ending in the first argument. The primary semantics of
the method don't end with the mediaType: the genre is as much part of
the primary semantics, so we use an argument label.

···

on Tue Feb 02 2016, Charles Kissinger <swift-evolution@swift.org> wrote:

—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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
-Dave

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

This is important; we want to pick rules that are as simple as possible
and still play out the right way in practice. I will be raising that
point again in this thread.

  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.

Um, okay, I hadn't looked at it that way. But if the variable wasn't b but
some more complicated name or expression, you probably wouldn't think
that it makes a sentence e.g.

  a.addObserver(backgroundController)

We could either say you're allowed to mentally transform that into

  a.addTheObserverIndicatedBy(backgroundController)

(don't forget that can be an arbitrary expression, so you really need
something like “IndicatedBy”, and not just “Called”) or we can say
you're allowed to skip over “Observer.” I think the latter is simpler.

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

Yes, we can extend the guideline to add the distribution pattern used in
the first example above (and mentioned in my reply to Charles
Kissinger). The latter two are more ambiguous than the first one, FWIW;
I can think of misreadings for both of those.

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

Ah... you make an interesting point here. If it was reasonable to leave
out all the parameters, you'd have to allow

    a.tracksHaving()

and likely someone will want a “tracks” property to go with this. I
know one way to disallow the above line without exorbitant cost to users
of the API, which is to build an API that supports this:

    a.tracksWhere(.pianist == "Brahms" && .mediaType == "Wax Cylinder")

or

    a.tracksWhere(.pianist == "Brahms", .mediaType == "Wax Cylinder")

That's doable in a library, but by no means simple to code. So here I
think we're just bumping up against the limits of what the language allows
us to express easily. I think we'll have to declare the "a.tracksHaving()"
problem out of scope for the naming convention discussion.

Since I've rambled afar, please let me know if I've addressed your
concern here.

···

on Tue Feb 02 2016, Paul Cantrell <swift-evolution@swift.org> wrote:

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

  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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
-Dave

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.

Not “sort of” :-).

It's addressed, in practice, by the “primary semantics” clause. When
the association between the first and second parameters is stronger than
the relationship between the first parameter and the base name, the
primary semantics will not be described by the a sentence that ends with
the first argument. As I wrote in an earlier message, it's important to
find minimal rules that cover everything we want to express in practice.

but I think "describing the primary semantics of the call" could be
more explicit. Even just the word "fully [describing]" would help.

I don't see how. That would imply that, in order to omit the argument
label, any parameters after the first must have no semantic effect at
all.

···

on Tue Feb 02 2016, Jordan Rose <swift-evolution@swift.org> wrote:

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

--
-Dave

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 disagree with this as a premise. IMO they don't have any such bias.
The guideline says, only under these very specific conditions does one
omit an argument label. That would indicate, if anything, a bias
towards *having* a label.

Since you used the phrase “a defaulted empty parameter label,” it sounds
like you're talking about language rules for deciding whether there's an
argument label based on what you write in the declaration. I *really*
don't want to discuss that in this thread.

Between the two points above, I don't know whether to try to analyze
what you wrote below. Have I misunderstood? Please advise.

···

on Tue Feb 02 2016, David Owens II <swift-evolution@swift.org> wrote:

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(_:)

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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
-Dave

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.

I believe everything you've written here is actually expressed, in
practice, by the guidelines I've suggested. I went through many of the
same thought processes in working the guidelines out. The trick is to
capture these thoughts in something simply stated that has the right
effect. If you think the guideline I suggested doesn't have the right
effect, please demonstrate a case where your thought process would lead
to a different API choice.

* * *

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.

Exactly. adding a subview is different, *in its primary semantics* from
adding, say, a gesture recognizer.

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.

Technically the required labels are part of the method name, but I think
we all recognize that the base name of the method (the part before the
opening parenthesis) is naturally more important to a person reading
swift code. The idea of these guidelines is that the primary semantics
of the method should, whenever possible, be expressed in the base name
and the first parameter, and when the first parameter either not not
part of the primary semantics, or (with the base name) not enough to
express the primary semantics, it should be labeled.

···

on Tue Feb 02 2016, Brent Royal-Gordon <swift-evolution@swift.org> wrote:

--
-Dave

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

I imagine that probably leaves you with very odd results for functions of
the form

    a.doThisTo(that, options: [ .animated ], backwards: true)

beecause this method is effectively a single-argument method with some
auxilliary information.

Okay, this is pretty big. I'll have to come back to it, but for now let
me leave you with a thought and a question:

1. Anything that takes this much explanation to describe is too big for
   the guidelines. If it can be condensed (a lot) it might be made to
   work.

2. Does this actually produce different results than the guidelines I
   have suggested?

Thanks,
Dave

···

on Wed Feb 03 2016, plx <swift-evolution@swift.org> wrote:

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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
-Dave

Okay... since you didn't directly address anything I wrote, should I
take this to mean you think I got it all wrong? Or...?

···

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

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

--
-Dave

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

Hi Charles,

FWIW, your personal style notwithstanding, we *are* standardizing on
camelCase, and CamelCase (for types), so writing your examples that way
would help us evaluate them as useful for the guidelines.

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

The difference between this and the first example isn't a preposition;
it's a (IMO needless) noun. What point (NPI) are you trying to
illustrate?

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

The difference here is just in your *choice* of preposition.

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.

I don't see how, but then I can't pick out any regularity in what you're
suggesting so I don't really know how to evaluate it.

···

on Wed Feb 03 2016, Charles Constant <swift-evolution@swift.org> wrote:

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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
-Dave

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.

For better or worse, it is a requirement that Cocoa as imported very
closely approximates full conformance to the guidelines we choose. We
are shooting for consistency across APIs used in swift.

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

That doesn't seem obvious to me. Whether that "should" be in the base
name depends on what guidelines we choose.

- this breaks the symmetry with other methods due to the reason above
  (like the “newTrack” you mentioned yourself)

Yes, it would be more consistent if these two cases were the same.

- doesn’t play well with method families (searching for tracks is
  searching for tracks. the criteria for search are just parameters).

I don't really believe that “method families” are something we want to
optimize for in Swift. There are almost always alternatives that impose
lower cognitive overhead on users.

If we do

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

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

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

That's just the consistency argument again, right?

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.

Unfortunately, this is the reality:

1. The pattern of omitting prefix verbs like “get” and “find” is
   something of a sacred cow; I think it would be very hard to sell to
   certain important people.

2. if we were to standardize on the opposite, we would need an
   objective-C import strategy that would add these verbs automatically.

If you can get a handle on solving #2, it *might* be worth me taking a
shot at solving #1. Otherwise, I'm afraid this idea is dead in the
water. Nothing that leaves glaring inconsistencies between imported
Cocoa and the API guidelines is going to be acceptable.

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)

There are contexts in which "matching" is more ambiguous than "having",
e.g.

    x.trackMatchingMediaType(t) // track the matching media type?

    x.trackHavingMediaType(t) // track is obviously a non-verb here.

Yes, I see how this relates to your "put back the verb" idea.

···

on Wed Feb 03 2016, Radosław Pietruszewski <swift-evolution@swift.org> wrote:

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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
-Dave

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 easiest way to solve this would be to change the default for first
argument labels, but I really don't want to touch that in this thread.
However, I will refer to something Joe Groff points out in
<http://article.gmane.org/gmane.comp.lang.swift.evolution/4096&gt;: the
automatic argument-name-matches-label default you're getting may not
actually be serving the authors of well-written APIs.

···

on Wed Feb 03 2016, Radosław Pietruszewski <swift-evolution@swift.org> wrote:

(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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
-Dave

_______________________________________________ swift-evolution mailing list
swift-evolution@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.

···

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

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

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

--
-Dave

Okay, now I'm finally giving this its due—sorry, too much pressure
earlier in the day...

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'll have to go back and have a look at that
*goes back to have a look*

Are there cases that add something significant to what I've already
written? I didn't see any. If you're saying what I wrote can be
improved by covering specific examples, please tell me which ones.

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.

Characterizing what we've done in the proposed guidelines as emphasizing
“quasi-grammatical rules-and-roles” seems like an unnecessary and
slightly muddled potshot. The grammatical stuff in the current document
is not in any sense “quasi” and has no connection to the admonition to
clarify roles, which I consider to be important. That said, I'm not at
all attached to presenting the guidelines in terms of grammar, so I'm
happy to see how your approach works.

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

I think/hope you mean “semantic” rather than “implementation” here.

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

Okay, well the fewer (ahem) guidelines, the better. Let's pretend you
didn't propose (d) and see how well it works.

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

This seems to be a different way of expressing something I was getting
at with the guidelines I posted to start this thread. I worry that what
it means for an argument to be “the semantic focus” is too vague. Why
is it an improvement over what I wrote?

  - (b) omit labels entirely whenever argument-ordering is irrelevant
    to the output (see below)

I don't think you mean this case:

func f(answer answer: Int = 42, message: String = "Hello, world") {
  print("\(message)! The answer is \(answer)")
}

Why is this an improvement over the way it's phrased in the original
guidelines proposal: “when the arguments are peers that can't be
usefully distinguished”?

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

I think using known and well-defined terms like “sentence” (or even
lesser-known but well-defined terms like “clause”) is probably much
better than using an ill-defined concept like “argument is the semantic
focus.” Even if you can define this concept clearly, it would have to
offer some very compelling advantages to be an improvement over
something that is already well-established. What are those?

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.

Okay, I understand it, but I'm not sure why it's better. Please explain
why this is an improvement over the other approach.

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

Those last two may draw some quibbles from the math domain experts, but
I agree with the spirit.

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

  colorMixer.blend(color1, color2)
  track.fade(from: initialVolume, to: targetVolume)

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.

Right, so we'd want different examples.

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

This “rule” seems pretty darned vague, even after all this explanation.
I don't see how it could possibly be stated succinctly,

Furthermore, as I wrote to Erica, I have concerns about anything that
gives special treatment to method families, specifically:

* I'm wary of adding anything that encourages the creation of
  “method families,” which have a higher cognitive overhead than many of
  the alternatives.
* A guideline that forces you to change an existing non-overloaded API,
  just because you are adding an overload, is problematic.

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

I think we can achieve that goal with succinct, well-defined
guidelines... provided we choose them correctly. That's why I started
the thread.

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

I see your point, here. For me, the most obvious way to distinguish
these would be by using a verb like “find,” “search,” or “seek” for
scenario (b). I'm not sure the difference needs to be expressed by
a different argument labeling choice—but I see how it could be. Will
have to give this one some more thought, thanks.

···

on Wed Feb 03 2016, plx <swift-evolution@swift.org> wrote:

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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
-Dave

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 easiest way to solve this would be to change the default for first
argument labels, but I really don't want to touch that in this thread.
However, I will refer to something Joe Groff points out in
<http://article.gmane.org/gmane.comp.lang.swift.evolution/4096&gt;: the
automatic argument-name-matches-label default you're getting may not
actually be serving the authors of well-written APIs.

True, that would be the easiest — though I fear it’s not the right solution. Though there are seemingly more scenarios in which explicit label is desirable, overall I see a bigger number of methods where it is not.

···

On 04 Feb 2016, at 02:24, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:
on Wed Feb 03 2016, Radosław Pietruszewski <swift-evolution@swift.org> wrote:

(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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
-Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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.

For better or worse, it is a requirement that Cocoa as imported very
closely approximates full conformance to the guidelines we choose. We
are shooting for consistency across APIs used in swift.

Hmm. Understood, but sigh. Personally I think it’s a shame to constrain ourselves to (imho) inferior naming convention because it’s what existing ObjC APIs use. Definitely understandable, but considering this isn’t a _really_ super common pattern, I think it would be just fine to leave the translations in good-enough state (whatever the decision on SE-0005 is), and follow a new convention in new Swift 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

That doesn't seem obvious to me. Whether that "should" be in the base
name depends on what guidelines we choose.

Of course. The way I think of it: the primary semantic is “find a track”, and media type is a criterion for search. I understand method families aren’t something you’re looking to optimize, but you can imagine that _if_ this was a family, it would still be “find a track”, just with different criteria.

- this breaks the symmetry with other methods due to the reason above
(like the “newTrack” you mentioned yourself)

Yes, it would be more consistent if these two cases were the same.

- doesn’t play well with method families (searching for tracks is
searching for tracks. the criteria for search are just parameters).

I don't really believe that “method families” are something we want to
optimize for in Swift. There are almost always alternatives that impose
lower cognitive overhead on users.

Fair.

If we do

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

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

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

That's just the consistency argument again, right?

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.

Unfortunately, this is the reality:

1. The pattern of omitting prefix verbs like “get” and “find” is
  something of a sacred cow; I think it would be very hard to sell to
  certain important people.

Hmm, I didn’t think of that. “get” is sort of understandable, I can see how in some languages you’d do “getFoo()”, whereas in Swift it would probably be a property “foo”. I don’t see a problem with “find”, though, as it really does help convey the intent.

2. if we were to standardize on the opposite, we would need an
  objective-C import strategy that would add these verbs automatically.

If you can get a handle on solving #2, it *might* be worth me taking a
shot at solving #1. Otherwise, I'm afraid this idea is dead in the
water. Nothing that leaves glaring inconsistencies between imported
Cocoa and the API guidelines is going to be acceptable.

Then perhaps let’s meet halfway:

   a.tracksWith(mediaType: “BetaMax”)
   a.removeFirstTrackWith(mediaType: “BetaMax”)
   fac.newTrack(mediaType: “Wax Cyllinder”)

(replace “with” with “matching” or “having” if you prefer)

I don’t love it, but like it much more than “tracksHavingMediaType”:

- still much more consistent about what goes into explicit parameters
- having a “with”/etc helps convey that parameters are the criteria for tracks returned
- doable as an automatic translation from ObjC

···

On 04 Feb 2016, at 02:20, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:
on Wed Feb 03 2016, Radosław Pietruszewski <swift-evolution@swift.org> wrote:

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)

There are contexts in which "matching" is more ambiguous than "having",
e.g.

   x.trackMatchingMediaType(t) // track the matching media type?

   x.trackHavingMediaType(t) // track is obviously a non-verb here.

Yes, I see how this relates to your "put back the verb" idea.

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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
-Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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

+1. Thanks for adding this set.

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

+1. Seems good.

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)

+1. Looks good and I like the distinction, especially between `moveFrom` and `moveTo`.

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

+1 to all of it. Great update.

-David

···

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

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

Personal bugaboo. Guidance should guide: Avoid argument labels when arguments cannot be usefully distinguished from one another.

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

Skip parameter labels when the first argument completes a grammatically meaningful phrase starting with the base name (and apart from trailing nouns).

      a.dismiss(b) // Not unless a is really dismissing some instance of b

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)

When using prepositional phrases, use parentheses after the preposition. Place any supporting words into first argument labels.

a.dismissUsing(animation: b)
a.tracksOf(mediaType: b, composer: c)
a.moveTo(x: 22, y: 99)

AGAINST: this worked better with your "creating new instance rule" and should probably be called out as such. I'd like to see the "if it works like an initializer it should be named with initializer label" bits come back.

a.colorWith(red: r, green: g, blue: b, alpha: a)

I think this should probably be:

a.color(red:, green:, blue:, alpha:)

Notes:

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

When using "with" as your go-to preposition, carefully consider whether other more meaningful prepositions could apply. "With" tends to describe use at a call site rather than method or function semantics.

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.

When the natural semantic relationship between the arguments is stronger than the relationship between the method name and the first argument, use first argument labels, whether the label is a noun or a preposition:

a.move(from: b, to: c)
a.login(username: b, password: c)

-- E, always attempting to be helpful, but understanding your frustration at getting this kind of feedback

···

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

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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

+1 to all involved. These are great, and tie together the previous
revisions well.

I too am not entirely sold on `move(from:to:)`, but I don't think it'll
kill anyone to write it in their own projects.

Cheers!
Zach Waldowski
zach@waldowski.me

···

On Fri, Feb 5, 2016, at 04:32 PM, Dave Abrahams via swift-evolution wrote:

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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution