[swift-evolution] When to use argument labels (a new approach)
Dave Abrahams
dabrahams at apple.com
Thu Feb 4 13:47:34 CST 2016
on Thu Feb 04 2016, plx <swift-evolution at swift.org> wrote:
>> On Feb 4, 2016, at 12:17 AM, Dave Abrahams via swift-evolution
> <swift-evolution at swift.org> wrote:
>>
>>
>> Okay, now I'm finally giving this its due—sorry, too much pressure
>> earlier in the day…
>
> No need to apologize, you have a commendable dedication to replies.
>
> Especially now that the disagreements are getting to be increasingly-minor.
>
>> on Wed Feb 03 2016, plx
> <swift-evolution at swift.org
> <mailto:swift-evolution at swift.org>> wrote:
>>
>>> 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.
>
> On a re-read your thread-starter has been toned-down enough from
> before it’s a lot less in that direction, but see the main response
> below.
>
>>
>>> ## 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?
>
> I address this in the main response.
>
>>
>>
>>> - (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”?
>
> My framing made more sense to me because “peers” and “can’t easily be
> distinguished” seemed vaguer than what I *meant*, but based on the
> counterexample you supplied my phrasing must indeed be too awful to
> use.
>
> I was trying to replace “peers that can’t usefully be distinguished”
> (what are peers? what does “can’t be usefully distinguished” actually
> mean?) with something stronger (perhaps "any permutation of arguments
> produces indistinguishable results”), but that wouldn’t actually cover
> all cases either (e.b. `isLessThan`).
We can always try to refine what I wrote to make it clearer, but in this
thread I'd like to focus on the *substance* of the guidelines for first
argument labels.
>>> #### 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 is the main reply.
>
> First, I think it’s fair to say that, as-formulated, you can’t simultaneously:
>
> - (a) take the sentence/clause rule seriously (or “use it rigorously”)
> - (b) justify many of the existing standard-library APIs (and the intended Cocoa imports)
>
> Consider the following examples:
>
> // today:
> func rangeOfString(searchString: String, options mask: NSStringCompareOptions) -> NSRange
> func stringByTrimmingCharactersInSet(characterSet: NSCharacterSet) -> String
> func indexOf(element: Element) -> Index?
>
> // ideal tomorrow, i assume:
> func rangeOf(searchString: String, options: NSStringCompareOptions) ->
> NSRange
s.find(subString, .ignoringCase | .ignoringDiacriticalMarks) -> Range<String.Index>
> func trimming(characterSet: NSCharacterSet) -> String func
s.trimming(.whitespaceAndNewlines)
Gah; you're right, here. Keeping a linguistic basis requires backing
off all the way from “sentence” to “phrase.” That's actually where I
started with this guideline, but thought I'd be able to strengthen it.
Saying “sentence or noun phrase” (if it works) might be better than just
“phrase” because “noun phrase” is always a term of art, whereas “phrase”
might just be interpreted casually.
> indexOf(element: Element) -> Index?
Also a noun phrase.
> …how do we (b) justify the lack of first-argument labels in that
> “ideal tomorrow” while still also (a) actually applying the
> sentence/clause rule?
Well, I think it's just that the rule needs a little adjusting.
> At least to my eyes, “a, range of b”, “a, trimming b”, and “a, index
> of b”, are questionable even as clauses, let alone as sentences.
Yes. Phrases, though?
> It seems hard to resolve the above without either:
>
> - introducing a *lot* of wiggle-room (“completes a sentence, or would
> only require some vacuous filler words to complete a sentence”)
> - complicating it tremendously (one rule for noun-like method names,
> another for verb-like method names, another for -ing method names,
> etc.)
> - weakening the condition from an if-and-only-if *rule* to a “may
> consider, but also remember other guidelines like ‘omit needless
> words’"
>
> …and thus although I generally agree with what I see as the *intent*
> behind the sentence/clause rule, I’m not sure it’s actually a usable
> rule as-formulated.
I agree. But I think minor adjustments fix it.
> If you accept that the “sentence/clause rule” is gone, what’s left of
> the proposed guideline is the “describes the primary semantics”
> aspect.
>
> Here, I prefer a formulation for “primary semantic focus” because it
> seems it leads to easier dispute-resolution. EG, for `addObserver`, it
> seems easier to get agreement that `playbackController` is somehow the
> “focus” in uses like the below:
>
> // ignoring the `options:context:` part:
> playerItem.addObserver(playbackController, forKeyPath: “status”)
> playerItem.addObserver(playbackController, forKeyPath: “duration”)
> playerItem.addObserver(playbackController, forKeyPath: “tracks”)
I would argue that the receiver is the obvious focus, so this seems much
less clear to me.
> …than to win an argument over whether or not “add observer” describes
> *enough* of the “primary semantics” of this method to fall under the
> proposed guideline.
That seems less ambiguous to me than primary focus, and I believe a few
examples in the guidelines would be enough to settle most debates.
>
>
> But that’s just an opinion, and on reflection I don’t think “primary
> semantic focus” is the clearest formulation, either; I just don’t have
> anything better.
>
>>
>>> 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.
>
> Understood, and noted, so I won’t press it, other than to point out
> that method families have come up independently a few times, which may
> deserve some consideration.
>
> My own sense is they are rather more common in application-level code
> than would be ideal in standard library code.
They do occur. I want them to look/feel good. I just don't want to
make special rules for them, if possible. Let's try to come up with
guidelines that work for everything with fewer exceptions.
--
-Dave
More information about the swift-evolution
mailing list