When to use argument labels (a new approach)

<schnipp>

Taking it from the other direction, the way the guidelines are phrased
I worry bit that you wind up with:

// not intended
a.add(subview: v) (or add(v)) // instead of `a.addSubview(v)`

That's allowed as the guidelines were originally posted, but with the
modification to the guidelines that form B1 is to be preferred over form
B2 when both are allowed... which I swore I posted something about in
this thread in response to an excellent point someone made, but now
can't find.

Maybe I dreamt it; until I hear otherwise, *you* get credit for making
the excellent point :-)

It was in response to my email, which I accidentally replied back only
to you and not the list, so I'm the only one who saw your proposed
modification. (Unfortunately I noticed that the list was not there
after I replied a couple more times as well.)

Gah! Well if you'd like to post those messages here, you certainly have
my permission. It might be good to have them as part of the public
record.

···

on Sun Feb 07 2016, Matthew Judge <swift-evolution@swift.org> wrote:

On Feb 7, 2016, at 00:31, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

on Sat Feb 06 2016, plx <swift-evolution@swift.org> wrote:

--
-Dave

<<schnipp>>

Gah! Well if you'd like to post those messages here, you certainly have
my permission. It might be good to have them as part of the public
record.

Here was my original email:

I certainly can live with these guidelines (and although I have a slight preference for something like move(from: a, to: b), it is only a _slight_ preference). However, I do have a suggested tweak to A. that I believe maintains clarity of guidance and covers this case. (See below inline)

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

When arguments' _importance_ can't be usefully distinguished from one another, none should have argument labels or _all should have argument labels_, e.g. min(x,y), zip(x,y,z), move(from: a, to: b), moveTo(x: a, y: b)

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

One other potential concern. I understand (and wholeheartedly agree) that GestureRecognizer should be pet of the base name here, but I'm not sure I see where the guidance explains it to me.

   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

···

On Feb 7, 2016, at 10:50, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

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

Gah! Well if you'd like to post those messages here, you certainly have
my permission. It might be good to have them as part of the public
record.

--
-Dave

Dave's reply to me inline...

I certainly can live with these guidelines (and although I have a
slight preference for something like move(from: a, to: b), it is only
a _slight_ preference). However, I do have a suggested tweak to
A. that I believe maintains clarity of guidance and covers this
case. (See below inline)

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

When arguments' _importance_ can't be usefully distinguished from one
another, none should have argument labels or _all should have argument
labels_, e.g. min(x,y), zip(x,y,z), move(from: a, to: b), moveTo(x: a,
y: b)

That would also imply

move(a, b)
tracksWith(a, b)

are both OK, because the importance of a and b can't be distinguished.

It would also imply that

min(first: a, second: b)

is OK. I don't want any of these to be OK. Do you?

Even if it didn't have these problems, it's asking people to make a
judgement about importance, which makes the rule harder to apply
consistently, and the only practical effect of your proposed change
seems to be to get your preferred form for move(from: a, to: b).

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

One other potential concern. I understand (and wholeheartedly agree)
that GestureRecognizer should be pet of the base name here, but I'm
not sure I see where the guidance explains it to me.

Ah, I see what you mean. The rules as written would also allow

   add(gestureRecognizer: x)

Excellent point! I think the fix is to declare, in B.1., that this form
is to be preferred.

···

on Fri Feb 05 2016, Matthew Judge <matthew.judge-AT-gmail.com> wrote:

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

   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

Gah! Well if you'd like to post those messages here, you certainly have
my permission. It might be good to have them as part of the public
record.

My reply back to Dave...

I certainly can live with these guidelines (and although I have a
slight preference for something like move(from: a, to: b), it is only
a _slight_ preference). However, I do have a suggested tweak to
A. that I believe maintains clarity of guidance and covers this
case. (See below inline)

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

When arguments' _importance_ can't be usefully distinguished from one
another, none should have argument labels or _all should have argument
labels_, e.g. min(x,y), zip(x,y,z), move(from: a, to: b), moveTo(x: a,
y: b)

That would also imply

move(a, b)
tracksWith(a, b)

I would not want either of these two allowed.

I just realized that the rules as you wrote them imply "if A applies, ignore B"... I was thinking of A as special case that modifies the rules in B. So if we refactor the guidelines to:

Guideline 1: B.1
Guideline 2: B.2
Special Case: A.1

I believe they are both still covered by B.1

are both OK, because the importance of a and b can't be distinguished.

It would also imply that

min(first: a, second: b)

is OK. I don't want any of these to be OK. Do you?

I think omit needless words still covers this. I would say zip() is clearly still covered by B.1 as well. (With my above refactoring of the guidelines.)

Even if it didn't have these problems, it's asking people to make a
judgement about importance, which makes the rule harder to apply
consistently, and the only practical effect of your proposed change
seems to be to get your preferred form for move(from: a, to: b).

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.

I forgot to carry this comment forward from my reply to the other thread, but I think constraining this to the first parameter is needlessly specific.

"At the call site, parameter(s) that have no argument label must..." still seems to do what we want and covers a case like zip(x,y,z) without needing to apply a special rule. (Although there is an implied "and" to make it a sentence: "zip x, y, and z")

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

One other potential concern. I understand (and wholeheartedly agree)
that GestureRecognizer should be pet of the base name here, but I'm
not sure I see where the guidance explains it to me.

Ah, I see what you mean. The rules as written would also allow

  add(gestureRecognizer: x)

Excellent point! I think the fix is to declare, in B.1., that this form
is to be preferred.

I'm not sure how prevalent my way of thinking about method names is, but I would argue the following both have argument labels, they've just been pulled to the left of the parenthesis:

a.mergeWith(b)
a.addGestureRecognizer(b)

So in my mind, B.2 really is just "move the first argument label into the base name unless B.1 prevents it" which then covers addGestureRecognizer and addObserver styled method names.

···

On Feb 5, 2016, at 22:04, Dave Abrahams <dabrahams@apple.com> wrote:

on Fri Feb 05 2016, Matthew Judge <matthew.judge-AT-gmail.com> wrote:

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

  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

--
-Dave

I think part of where the current guidelines struggle is the ambiguity of the term "argument label" so just for the sake of discussion I'm going to define terms to be explicit (not saying these are precisely the right terms).

- External parameter names: names used to refer to parameters at the use site
a.moveTo(x: 1, y: 2) // external parameter names are "x" and "y"
a.addGestureRecognizer(b) // does not have an external parameter name

- Argument labels: words used to describe the argument being passed in
a.moveTo(x: 1, y: 2) // argument labels are "x" and "y"
a.addGestureRecognizer(b) // argument label is "GestureRecognizer"

Both of these are referred to as "argument labels" in the guidelines and most of the discussion, but they are slightly different (though overlapping) things. For instance, B.1 refers to "external parameter names" and B.2 refers to "argument labels"

I think my distinction between these terms makes the guidelines simpler with fewer caveats:

1. Prune needless and redundant words from the argument labels (i.e. remove any word that can be removed without confusing the semantic intent)
2. If the method reads as part of a grammatical phrase, prefer to locate the first argument label as part of the base name instead of as an external parameter name.
Special Cases/Exceptions:
a. Arguments with a default value should use an external parameter name
b. Arguments with similar semantic importance should be treated the same (use external parameter names for argument labels or don't label any)

Not saying these are right/perfect, but I do think the distinction of guidelines for argument labels and external parameter will make the rules simpler, clearer, and more consistent. (Everything below here is applying these rules to each of the examples used in Dave's original post.)

print(x) // Guideline 1 prunes argument label
a.contains(b) // Same
a.mergeWith(b) // Guideline 1 shortens argument label "WithCollection" to "With"
// Guideline 2 moves "With" into the base name

a.addGestureRecognizer(x)
// Guideline 1 does NOT prune "GestureRecognizer" because it would change the semantic meaning
// Guideline 2 moves it into the base name

- the following 3 examples from the original are treated the same under these rules, for the same reasons mentioned

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

a.encodeWith(b) // Guideline 1 shortens argument label "WithCoder" to "With"
// Guideline 2 says put "With" to the base name

a.moveFrom(b, to: c) // Only change to the results of Dave's examples
// Guideline 1 shortens "fromScene" to "from" and "toScene" to "to"
// Exception (b) prevents Guideline 2 from moving "from" into the base name

* Note that I believe the only change to these guidelines required to recover the behavior of Dave's original guidelines is modifying Exception (b) to language similar to Dave's A.

- the following two examples would be covered by Exception (b) as well

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

Sorry for the long email... Hopefully it's somewhat useful.

I do find some of the rules worded in ways that are hard to apply in
the presence of trailing nouns; I’m not sure the guidelines are strong
enough here to force the “right” outcome.

Consider this example:

// not-intended:
a.tracksWithMediaCharacteristic(b, composer: c)

…applying our guidelines:

- `tracks with media characteristic b` is a grammatical phrase

I went through a series of thoughts on this:

1. I'm not sure it is grammatical (I'll have to consult with a
  linguist).

2. I understand why it sounds natural: this is the somewhat misleading
  result of using a single-character identifier in the example. It's like
  talking about a hypothetical "person A" and "person B". You wouldn't think
  "Person Joe" was grammatical.

3. Yeah, but "umbrellas with color yellow" is perfectly fine, if
  slightly unnatural, and it has the same grammatical structure. My
  wife points out that it's like something you would say in poetry to
  make rhyme or meter work.

4. Again I need to consult with a linguist, but I now have hope that it
  actually is grammatical, meaning the whole “skipping trailing nouns
  on the base name” wrinkle can be eliminated from the guidelines.
  Thanks!

It’s a good example of “grammaticality vs acceptability” issues; for
another other example, “The Color Purple”. Even “Person Joe” is ok in
the right context, but as it’s unusual and tricky to parse it
seemingly requires a pause (or a comma when written), e.g. consider
“The person, Joe, bit the dog, Fido”.

Yep; great points.

For the above example, I think there’s a strong sense that the
phrasing is unusual, and the lack of articles isn’t helping; I’d order
the acceptability like this:

tracks with the “legible” media-characteristic // <- most-natural/least-unusual
tracks with the media-characteristic “legible”
tracks with media-characteristic “legible”
tracks with “legible” media-characteristic // <- worst due to interpretational ambiguity

Well, and the lack of quotation marks in the name doesn't help either.
But when you s/legible/X/ I'd say the third one is perfectly natural and
the other ones are all a bit strange.

…and I think the the perceived acceptability improves
substantially—and seemingly uniformly—with some more context:

let t = (all of a's) tracks with the “legible” media-characteristic
let t = (all of a's) tracks with the media-characteristic “legible”
let t = (all of a's) tracks with media-characteristic “legible”
let t = (all of a's) tracks with “legible” media-characteristic // <- still the worst, for the same reason

…which sort of situation was why I thought it was risky to put too
much weight on grammatical rules in the guidelines: I suspect that a
lot of what needs to be “ungrammatical” for the guidelines will in
practice be closer to “questionable”,

Can you explain what you mean by "needs to be “ungrammatical” for the
guidelines?"

and it can be difficult to keep one’s sense of the “questionable”
calibrated; this is especially true if you spend a lot of time exposed
to “odd" language, e.g. English-like source code.

Heh, indeed. That's why I think it's better to have a solid criterion
like “is it grammatical?” than to use something like “is it questionable?”

Given the perceived oddness of the trailing-noun usage, the best
rule-adjustment might be “is it a complete phrase? If there’s a
trailing noun, you may *either* ignore the trailing noun *or* ignore
the argument—but not both—when making that decision,” except pithier.

I wonder if “use single-letter identifiers to evaluate grammaticality at
a hypothetical use-site” is an approach that would work without any
“ignoring?”

I think we might also want to consider it legitimate to evaluate calls
taking enum parameters using actual enum case names, as they can account
for a lot of words and can be controlled to make calls read
grammatically.

···

on Sun Feb 07 2016, plx <swift-evolution@swift.org> wrote:

On Feb 6, 2016, at 11:31 PM, Dave Abrahams via swift-evolution >> <swift-evolution@swift.org> wrote:
on Sat Feb 06 2016, plx <swift-evolution@swift.org> wrote:

--
-Dave

Re-sending because did not appear on mailing list correctly:

For me, it means the wrong thing.

  moveFrom(b, to: c)

implies the wrong meaning? What meaning does it imply to you?

Swift is not Objective-C. Even if it shares the concept that method
and parameter names form the signature, the parentheses declaration
and call syntax (modeled on C languages) increases the visual and
semantic separation between method and parameter names.

That's why, the way I see it, method names should stand on their own,
without the help of parameter names. I should be able to read the
method calls without the parameter names (like in C) and still
understand the basic idea:

a.dismiss(true)
a.dismissAnimated(true)
a.tracksWithMediaType(b, c)
a.move(b, c)

All these calls, as often seen in C-based languages, are much more
ambiguous without their parameter names, but still read fine.

I understand your philosophy, but I disagree with it (I also don't agree
that any of the above “read fine” for their intended semantics). Swift
lets API designers specify argument labels that are required at the call
site because of a fundamental belief that it makes for better code. This
is a core idea in Swift's design. IMO, asking API designers to write
code as though the argument labels could be omitted at the call site
would be making their life needlessly difficult. It would also be out
of touch with reality, since after all the labels *can't* be omitted.

But:

a.moveFrom(b, c)

This reads much worse.

I'm sorry, I feel like I'm bad at explaining my reasoning.

I am forced to agree, here.

One rule I'd like to see is that the method name before the
parentheses should read fine without the parameter names. That they
should come as a nice addition to document and disambiguate.

Noted. I won't be proposing that rule, since I disagree with it, but you
can always propose an amendment to the guidelines we end up with and see
how it flies.

···

on Sun Feb 07 2016, David Hart <swift-evolution@swift.org> wrote:

--
-Dave

Comments inline:

Looking over these guidelines again, I think I would be quite happy
with them given one very simple change to Rule 2:

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

This eliminates the need for:

a.tracksHaving(mediaType: b)

which I think is inferior to:

a.tracksHavingMediaType(b)

Why do you think it's inferior?

The downside with the latter is that it doesn't scale up. When you want
to add a second criterion, you can't just add a defaulted parameter; you
have to change the signature (breaking code) or add one or more
overloads (creating cognitive weight for users).

Is this really an overriding concern though? I personally would not
choose to optimize for the occasional case of adding a defaulted
parameter at the cost of a general naming/labeling convention that we
both have expressed some discomfort with.

It's not an *overriding* concern; it's one of many. BTW, I don't
consider “some discomfort” on anyone's part to be an overriding concern
either. Most of these changes make Cocoa traditionalists wildly
uncomfortable. While I have compassion for that, the key thing is
understandability of Swift code into the future.

It's a (relatively speaking) minor issue, but IMO not glomming all that
description into the base name also results in code that's easier to
format in a balanced way, simply because there's a natural place to
break the line after the parenthesis.

I view this in a quite different way. Breaking a line after
'a.tracksHaving(‘ leaves the reader in quite a bit of suspense!

Suspense is good in these cases; it means the reader isn't going to stop
at the end of the line. One should feel relief only when one has taken
in all the important parts of the call.

It might be easier to format, but overall readability would suffer.

I don't see how

  a.long.line.ending.with.tracksHavingMediaType(
    mp3)

is any more readable than

  a.long.line.ending.with.tracksHaving(
    mediaType: mp3)

to me, it's less readable.

On the other hand, functions like:

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

would remain as is, because there are multiple objects for the preposition.

This also neatly solves the ‘moveFrom(a to: b)’ problem. There are two
separate prepositional phrases involved, 'from a' and 'to b', each
with a single object, so:

move(from: a to: b)

is, I believe, fully compatible with the guidelines.

I don't see anything in the guidelines I've proposed, even with your
modification, that would cause "from" to be placed inside the
parentheses. The way I read it this case still falls right into B1,
resulting in "moveFrom(a, to: b)”

I must be interpreting B.1 differently than you intended. It appears
to me to be discussing the cases where first arguments *can* be
unlabeled. You seem to be implying here that it says that the first
argument *must* be unlabeled when it forms a part of a grammatical
phrase, otherwise move(from: a, to: b) would be fine. As I read the
guidelines, though, only B.2 (which I would modify) requires
moveFrom(a, to: b), because of the preposition. What am I not getting
here?

Maybe I was mistaken. I'm working on new wording that allows "from" to
move inside the parens under the same guideline that results in argument
labels for all arguments when the method is a factory function. I'll
let you know when that's ready.

···

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

On Feb 7, 2016, at 9:54 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:
on Sun Feb 07 2016, Charles Kissinger <swift-evolution@swift.org> wrote:

—CK

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

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

_______________________________________________
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

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

--
-Dave

[snipped]

If such convention were to be adopted, we could throw away our favorite grep tools.

Functions with many parameters are often more legible when their invocation is split across several lines. And generally speaking, a developer can liberally call a function on a single line, or on several lines:

  attachmentFor(image: …, extraParam: …)
  attachmentFor(
      image: …,
      extraParam: …)

OK so now if I want to look for all invocations of attachmentFor(image:extraParam:) in my code, I have to look for "attachmentFor", and get all the unrelated results attachmentFor(data:…), attachmentFor(fileURL:…), etc.

The “Find>References” function in Xcode will let you specifically find functions like these that differ only in their parameter labels. As I mentioned earlier in this thread, if you’re on Linux or just using different tools, you’re right: it does become a difficult problem to do it reliably.

—CK

···

On Feb 11, 2016, at 4:02 AM, Gwendal Roué via swift-evolution <swift-evolution@swift.org> wrote:

Whereas if the function were named attachmentForImage(_:extraParam:), I could look for "attachmentForImage", and get much more precise search results.

So… I’m happy people discuss how nice `attachmentFor(image:)` looks, and my opinion on how nice or ugly it looks is not my point. My point is that I want my tools to help me doing my job.

Gwendal

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

I can't speak for other tools, but Emacs regexps work across line
boundaries, and <https://www.emacswiki.org/emacs/FindrPackage&gt; will let
you do that kind of searching across directory hierarchies.

···

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

On Feb 11, 2016, at 4:02 AM, Gwendal Roué via swift-evolution <swift-evolution@swift.org> wrote:

[snipped]

If such convention were to be adopted, we could throw away our favorite grep tools.

Functions with many parameters are often more legible when their invocation is split across several lines. And generally speaking, a developer can liberally call a function on a single line, or on several lines:

  attachmentFor(image: …, extraParam: …)
  attachmentFor(
      image: …,
      extraParam: …)

OK so now if I want to look for all invocations of attachmentFor(image:extraParam:) in my code, I have to look for "attachmentFor", and get all the unrelated results attachmentFor(data:…), attachmentFor(fileURL:…), etc.

The “Find>References” function in Xcode will let you specifically find
functions like these that differ only in their parameter labels. As I
mentioned earlier in this thread, if you’re on Linux or just using
different tools, you’re right: it does become a difficult problem to
do it reliably.

--
-Dave

I admit that it’s hard to leave coding habits, and I have slightly over-reacted above.

I can’t wait for Xcode to bring better Swift code navigation tools, that would make lower the usefulness of text searching.

Gwendal

···

Le 11 févr. 2016 à 13:02, Gwendal Roué <gwendal.roue@gmail.com> a écrit :

Hello,

Le 11 févr. 2016 à 12:34, Radosław Pietruszewski via swift-evolution <swift-evolution@swift.org> a écrit :

PS. If someone wanted to do the same thing and review methods in their project, here’s the quick&dirty Ruby script for this: Extract function declarations from your Swift project. Just open up `irb` from your project run and paste this: · GitHub

Thanks, it’s much useful.

  func attachmentForImage(image: UIImage) throws -> Attachment {
  func attachmentForData(data: NSData) -> Attachment {
  func attachmentForFileURL(url: NSURL) throws -> Attachment {

This should be `attachmentFor(image:)`, `attachmentFor(data:)`, and `attachmentFor(fileURL:)`. This seems like a win, since related methods for different data types are grouped more tightly together. (I could also — like you suggested about not optimizing for method families — make an enum, but it would be overkill since it’s not API for public consumption. I should mark those as private.)

If such convention were to be adopted, we could throw away our favorite grep tools.

Functions with many parameters are often more legible when their invocation is split across several lines. And generally speaking, a developer can liberally call a function on a single line, or on several lines:

  attachmentFor(image: …, extraParam: …)
  attachmentFor(
      image: …,
      extraParam: …)

OK so now if I want to look for all invocations of attachmentFor(image:extraParam:) in my code, I have to look for "attachmentFor", and get all the unrelated results attachmentFor(data:…), attachmentFor(fileURL:…), etc.

Whereas if the function were named attachmentForImage(_:extraParam:), I could look for "attachmentForImage", and get much more precise search results.

So… I’m happy people discuss how nice `attachmentFor(image:)` looks, and my opinion on how nice or ugly it looks is not my point. My point is that I want my tools to help me doing my job.

  private func setHandoffState(hash hash: String, fallbackURL: String, title: String? = nil, searchable: Bool) {

This seems OK to me, but I’m not entirely sure if the guidelines
suggest I _should_ put a preposition there, i.e.

  setHandoffStateFor(hash: a, fallbackURL: b, …)

I don’t think I should — I don’t see how “for” adds to clarity here,

Me neither, but it's hard to tell what this method is supposed to mean.
Maybe you could explain (or show a beautifully crafted doc comment
(BCDC) per
<Swift.org - API Design Guidelines
<Swift.org - API Design Guidelines;
;-)

Ahh, but I’m missing BCDCs in many of these places ;)

This is a helper that essentially makes a NSUserActivity from passed
parameters and calls `becomeCurrent` on it. (Pretty much changes
application-global state.) (Not the most elegant solution, I know.)

I don't know whether it's elegant, but given how you described it, I
would probably call it something like:

   createCurrentActivity(hash: a, fallbackURL: b, …)

but I want to make sure if I interpret the proposed guidelines right.

  func actionForCommand(command: UIKeyCommand) -> String? {

This should become “actionFor(command: c)” — which isn’t an obvious

That looks like a call, in which case the API above doesn't allow an
argument label.

I’m sorry — I got confused — what do you mean here?

Sorry, I didn't notice that you both transitioned from declaring
(actionForCommand(_)) to calling (actionFor(command:)) *and* changed the
API at the same time.

So, I would say the guidelines say the call should look like:

    b.action(for: c)

(I think this kind of confusion is probably the
strongest reason for changing the default---I see it come up over and
over).

win, but I certainly don’t mind it.

according to where we're currently headed (prepositions inside the
parens), it would be:

   func action(for command: UIKeyCommand) -> String? {

  private func createAttribute(tag: SecItemAttr, _ data: NSString?) -> SecKeychainAttribute? {

Forgive my barbaric removal of external labels.

Nit: “argument labels,” please.

Noted.

It should be `createAttribute(tag: t, data: d)`.

Again that looks like a call, so for the above declaration it would be

     createAttribute(t, d)

Alternatively, it could be `attributeFor(tag: t, data: d)` — I could
go either way.

Why “for?” Can you explain the semantics of this method (or show a BCDC)?

This is a initializer-like helper method that instantiates a
SecKeychainAttribute from passed parameters.

If it's a factory, I think guidelines prescribe the call should look like:

   makeAttribute(tag: t, data: d)

(adding to my thoughts on the later proposal of moving preposition
inside parens, the symmetry in param labels would likely be broken
here, because it doesn’t seem necessary to add “for”/“with” when I go
with “createAttribute”, but for the noun-only version it would have to
be “attribute(forTag: t, data: d)”.)

  func attachmentForImage(image: UIImage) throws -> Attachment {
  func attachmentForData(data: NSData) -> Attachment {
  func attachmentForFileURL(url: NSURL) throws -> Attachment {

This should be `attachmentFor(image:)`, `attachmentFor(data:)`, and
`attachmentFor(fileURL:)`.

again, the latest draft puts prepositions inside the parens.

Q: For the previous example you suggested the latest draft would be
`action(for:)`. Given these methods take one of three different
parameter types, and the names mostly just repeat type information,
should they all be `attachment(for:)`? Or `attachment(forImage:)` etc?
The third method is trickier because it takes an NSURL, but there is
an assumption this is a _file_ (i.e. device-local) URL.

But without the BCDC it's pretty hard to tell whether these are good
names.

Also an initializer-like helper method that transforms passed
parameters into an Attachment.

Oh, definitely “makeAttachment(x)” at the call site, then, if you can't
just do this with an Attachment initializer!

···

on Thu Feb 11 2016, Radosław Pietruszewski <radexpl-AT-gmail.com> wrote:

This seems like a win, since related methods for different data types
are grouped more tightly together. (I could also — like you suggested
about not optimizing for method families — make an enum, but it would
be overkill since it’s not API for public consumption. I should mark
those as private.)

I'm not trying to say “method families are bad.” I'm just saying I
don't want to build the guidelines around them if possible.

  private func messageCell(label label: String) -> MessageCell {
  private func cellForTask(task: Task, enabled: Bool = true) -> TaskCell {

Hm, those are inconsistent. I could go with `cellFor(message:)` and

don't you mean cellFor(label:) ?

`cellFor(task:)`, which would be consistent with the guidelines. OTOH,
since they return different types, I liked that they had different
names… `taskCellFor(task:)` is redundant. Perhaps `taskCellFor(_:)`
and `messageCellFor(_:)`.

  func generate(fill: Double, text: String) -> CLKComplicationTemplate {

`templateFor(fill:, text:)`

  func update(from old: TaskCommentRowModel?, to new: TaskCommentRowModel) {

That’s the, IMO rare, circumstance where the preposition *does* make sense to go inside the parens.

* * *

I mentioned this in some other post before, but I also found a bunch
of methods where the first parameter should be labeled, but isn’t,
because without an easy shortcut like the old #param, I was just too
lazy to do The Right Thing.

PS. If someone wanted to do the same thing and review methods in their
project, here’s the quick&dirty Ruby script for this:
Extract function declarations from your Swift project. Just open up `irb` from your project run and paste this: · GitHub
<https://gist.github.com/radex/b425c73afde84d88e4ca&gt;

HTH,
— Radek

On 08 Feb 2016, at 21:55, Dave Abrahams via swift-evolution >>>> <swift-evolution@swift.org> wrote:

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

Dave,

First of all, thank you for enduring our nitpicks and complaints and
continuing to explore the subject :) I think we’re all better off for
it, and getting closer to the solution with each iteration.

You asked:

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?

And I think, by this standard, the guidelines you proposed seem to be
a success. Looking through Doug’s diffs, I see a lot of method names
that I don’t *love*, but I couldn’t find something I would hate.

That's great news! Keep in mind, though, that Doug's results are just
the result of trying to approximate application of the guidelines by
using heuristics. To do a complete evaluation also need to think about
what applying the guidelines will do to your own code.

Thanks,

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

--
-Dave

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

--
-Dave

but I want to make sure if I interpret the proposed guidelines right.

  func actionForCommand(command: UIKeyCommand) -> String? {

This should become “actionFor(command: c)” — which isn’t an obvious

That looks like a call, in which case the API above doesn't allow an
argument label.

I’m sorry — I got confused — what do you mean here?

Sorry, I didn't notice that you both transitioned from declaring
(actionForCommand(_)) to calling (actionFor(command:)) *and* changed the
API at the same time.

So, I would say the guidelines say the call should look like:

   b.action(for: c)

It should be `createAttribute(tag: t, data: d)`.

Again that looks like a call, so for the above declaration it would be

    createAttribute(t, d)

Alternatively, it could be `attributeFor(tag: t, data: d)` — I could
go either way.

Why “for?” Can you explain the semantics of this method (or show a BCDC)?

This is a initializer-like helper method that instantiates a
SecKeychainAttribute from passed parameters.

If it's a factory, I think guidelines prescribe the call should look like:

  makeAttribute(tag: t, data: d)

So that’s interesting, because there’s a difference in convention between a factory/initializer-like function:

  makeFoo(this:, that:)

and a getter/finder-like function:

  foo(withThis:, that:)

I think that (in particular the difference in the treatment of argument labels) is the part that makes me most uncomfortable about the latest draft. Perhaps it isn’t such a big deal, since you usually don’t see the two next to each other, and as was noted, most finder-like functions only come with a single parameter, while initializers and factory methods very commonly have many parameters...

Best,
— Radek

Sorry, are you saying that your opinion that there's a big problem still
holds, or that what Doug wrote addresses your concerns?

···

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

Given the above clarification, does this opinion still hold?

Thanks, yes it does.

--
-Dave

What I was getting at there is that
‘a.tracksHavingMediaTypeWaxCylinder’ doesn’t seem like a sentence to
me. It is a clause, no doubt, but doesn’t seem to be an independent
one.

Agh, you're right. If only we used verbs in these cases... but that is
a whole nother discussion. I guess I'll have to say "clause" instead of
"sentence," which really sucks. Or I can say there's an implied "get"
at the front if there's no verb. bleah.

The implied ‘get' approach doesn’t seem too bad to me. Would it suffice to stick with ‘sentence' and add the condition that if there is an implied ‘get' or ‘set' that would form a sentence, then Rule 1 still applies?

That's roughly what I was suggesting we might do. But is there such a thing as an implied "set?" I don't think so. Why bring that into it?

I can’t think of any cases where people use an implied verb other than something in the 'get' family (find, acquire, etc.). Looking through my own code, the very concise set of rules in your original post work perfectly (IMO) with the exception of the “missing get” problem.

Thanks; nice to hear not *everybody* thinks they need to be completely reworked for reasons that I can't yet understand ;-)

Cheers,
-Dave

···

On Feb 4, 2016, at 10:06 AM, Charles Kissinger <crk@akkyra.com> wrote:

On Feb 3, 2016, at 3:54 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:
on Wed Feb 03 2016, Charles Kissinger <swift-evolution@swift.org> wrote:

Why isn't "to" more predictable than "toPoint?" It's certainly more
universal.

I also forgot to mention, "to" is so much more universal, that it stops
being very useful. If we rewrite my contrived example as...

    func rename( fromName="index", fromEx="html", toName="converted",
toExt="shtml" )

The we have labels that I can easily imagine the programmer might want to
search code for, or plug in to a second, similar function. I can imagine
working on a file and thinking "what other functions did i use here that
change an extension" - searching for "toExt" is helpful there, whereas I'm
not sure why I would want to find "to[Anything]." I also find it helps me
understand what functions do, when I'm browsing suggestions from code
completion, but that's only my opinion for me to make, so it's hard for me
to make a convincing argument.

Why isn't "to" more predictable than "toPoint?" It's certainly more
universal.

Function arguments can be unordered, if they have default values. A
contrived example here...

func rename( from nameA="index", from extA="html", to nameB="converted", to
extB="shtml" )

Maybe I'm confused, but that seems like it has to be illegal at worst, and
confusing at best. But if you don't consistently use "from" and "to" then
you're back to making special rules about everything.

I think it's a confusing and probably bad API no matter how you choose
those labels.

  file.rename(from: ("index", "html"), to: ("converted", "shtml"))

It wouldn't matter if every argument has a unique Type, or if you take the
time to create structs or tuples to bundle every piece of data together
about "from" or about "with" etc. Maybe that's usually the case, but it's
nice to have freedom to be lazy.

Let me be clear: these API guidelines are *not* going to optimize for
churning out APIs without careful consideration; it's thoroughly
incompatible with their mission. If you want that freedom, you're free
to ignore the guidelines. Sorry to be blunt, but that's how it is.

···

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

--
-Dave

This issue is going to surface any time a method with a preposition and a single defaulted argument. I would say:

copy(with: )

Although I think we might have problematic results no matter what guidelines we use for

func doSomethingWith(completionHandler: (()->Void)? = nil)

since this can be called as

doSomethingWith() // doSomething()

Or

doSomethingWith {...} // doSomething {...}

···

On Feb 6, 2016, at 15:47, Jean-Daniel Dupas via swift-evolution <swift-evolution@swift.org> wrote:

Le 6 févr. 2016 à 21:15, Douglas Gregor via swift-evolution <swift-evolution@swift.org> a écrit :

Sent from my iPhone

On Feb 6, 2016, at 9:21 AM, Thorsten Seitz <tseitz42@icloud.com> wrote:

So the preposition should move into the argument label if the argument is optional?

copy(withZone: zone = nil)

Zone is redundant with type information.

copy(with:)?
copy(withZone:)?

Wouldn’t it be possible to simply drop the copyWithZone: method that is deprecated for some times now (To quote the doc: Zones are ignored on iOS and 64-bit runtime on OS X. You should not use zones in current development).

-Thorsten

Am 06.02.2016 um 14:45 schrieb Matthew Judge via swift-evolution <swift-evolution@swift.org>:

Very first method

copyWith(zone: Zone = nil)

can be called as

copyWith()

I'm assuming this is still something we don't want right?

On Feb 6, 2016, at 02:16, Douglas Gregor via swift-evolution <swift-evolution@swift.org> wrote:

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:

[snip goals]

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

Here’s a link:

   Split first selector piece into base name/first argument label. by DougGregor · Pull Request #10 · apple/swift-3-api-guidelines-review · GitHub

Feedback welcome!

   - Doug

_______________________________________________
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

_______________________________________________
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

Unfortunately, the frameworks often call the copyWithZone() method directly on objects that support NSCopying, rather than copy(). For example, @property (copy) properties in Objective-C, and @NSCopying properties in Swift behave this way:

···

On Feb 6, 2016, at 2:47 PM, Jean-Daniel Dupas via swift-evolution <swift-evolution@swift.org> wrote:

Wouldn’t it be possible to simply drop the copyWithZone: method that is deprecated for some times now (To quote the doc: Zones are ignored on iOS and 64-bit runtime on OS X. You should not use zones in current development).

--

import Foundation

class Foo: NSObject, NSCopying {
  override func copy() -> AnyObject {
    print("copy() called")
    return Foo()
  }
  
  func copyWithZone(zone: NSZone) -> AnyObject {
    print("copyWithZone() called")
    return Foo()
  }
}

class Bar {
  @NSCopying var foo: Foo = Foo()
}

let bar = Bar()

bar.foo = Foo()

outputs:

copyWithZone() called

--

Even if this could be changed, and the frameworks completely audited to replace every call of copyWithZone() with a call to copy(), the fact that copyWithZone() has always been part of the API contract means that third-party libraries and frameworks could be calling it, which subsequently means that they would break if copyWithZone() were to go away. Calling copyWithZone() directly isn’t completely unheard of, actually; if an object is implementing its own copyWithZone() method and one of its properties conforms to NSCopying, it may well call copyWithZone() to do the copy, because it’s already got this zone pointer right here, and hey, why not. Add the fact that this was actually correct practice way back in the day when zones were still in use, and I’m sure there’s at least some amount of code like this out there.

One thing you could do, of course, is use compiler magic to turn override func copy() into func copyWithZone(_), but I’m not sure it’s worth it just for this.

Charles

So the preposition should move into the argument label if the argument is optional?

copy(withZone: zone = nil)

That's a good idea.

It seems unfortunate that the placement of the preposition should change depending on whether there is a default argument or not, especially since it is reasonable to imagine that an API evolves to gain a default argument later on.

  - Doug

···

On Feb 6, 2016, at 10:08 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:
on Sat Feb 06 2016, Thorsten Seitz <swift-evolution@swift.org> wrote:

-Thorsten

Am 06.02.2016 um 14:45 schrieb Matthew Judge via swift-evolution >>> <swift-evolution@swift.org>:

Very first method

copyWith(zone: Zone = nil)

can be called as

copyWith()

I'm assuming this is still something we don't want right?

On Feb 6, 2016, at 02:16, Douglas Gregor via swift-evolution >>>> <swift-evolution@swift.org> wrote:

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:

[snip goals]

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

Here’s a link:

  Split first selector piece into base name/first argument label. by DougGregor · Pull Request #10 · apple/swift-3-api-guidelines-review · GitHub

Feedback welcome!

  - Doug

_______________________________________________
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

_______________________________________________
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