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

plx plxswift at icloud.com
Thu Feb 4 09:46:40 CST 2016


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

> 
>> #### 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
func trimming(characterSet: NSCharacterSet) -> String
func indexOf(element: Element) -> Index?

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

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.

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.

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

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

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.

> 
> 
>> ## 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 Feb 2, 2016, at 6:32 PM, Dave Abrahams via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>> 
>>> 
>>> This thread is related to the review of new API guidelines, but it's not
>>> a review thread; it's exploratory.  The goal is to come up with
>>> guidelines that:
>>> 
>>> * describe when and where to use argument labels
>>> * require labels in many of the cases people have asked for them
>>> * are understandable by humans
>>> * preserve important semantics communicated by existing APIs.
>>> 
>>> Here's what I'm thinking
>>> 
>>> 1. If and only if the first argument could complete a sentence*
>>>  beginning in the base name and describing the primary semantics of
>>>  the call, it gets no argument label:
>>> 
>>>    a.contains(b)  // b completes the phrase "a contains b"
>>>    a.mergeWith(b) // b completes the phrase "merge with b"
>>> 
>>>    a.dismiss(animated: b) // "a, dismiss b" is a sentence but 
>>>                           // doesn't describe the semantics at all, 
>>>                           // thus we add a label for b.
>>> 
>>>    a.moveTo(x: 300, y: 400) // "a, move to 300" is a sentence 
>>>                             // but doesn't describe the primary 
>>>                             // semantics, which are to move in both
>>>                             // x and y.  Thus, x gets a label.
>>> 
>>>    a.readFrom(u, ofType: b) // "a, read from u" describes
>>>                             // the primary semantics, so u gets no
>>>                             // label. b is an
>>>                             // option that tunes the primary
>>>                             // semantics
>>> 
>>>  [Note that this covers all the direct object cases and, I believe,
>>>  all the default argument cases too, so maybe that exception can be
>>>  dropped.  We still need the exceptions for full-width type
>>>  conversions and indistinguishable peers]
>>> 
>>>  Note: when there is a noun in the base name describing the role of the
>>>  first argument, we skip it in considering this criterion:
>>> 
>>>     a.addObserver(b) // "a, add b" completes a sentence describing 
>>>                      // the semantics.  "Observer" is omitted in 
>>>                      // making this determination.
>>> 
>>> * We could say "clause" here but I think making it an *independent*
>>> clause doesn't rule out any important use-cases (see
>>> https://web.cn.edu/kwheeler/gram_clauses_n_phrases.html <https://web.cn.edu/kwheeler/gram_clauses_n_phrases.html>) and at that
>>> point, you might as well say "sentence," which is a more
>>> universally-understood term.
>>> 
>>> 2. Words that describe attributes of an *already-existing* instance
>>>  should go in the base name rather than in a label:
>>> 
>>>     a.tracksHavingMediaType("Wax Cylinder")      // yes
>>>     a.removeFirstTrackHavingMediaType("BetaMax") // yes
>>> 
>>>     a.tracks(mediaType: "Wax Cylinder")          // no
>>>     a.removeFirstTrack(havingMediaType: "BetaMax") // no
>>> 
>>>  [yes, we could use "With" instead of "Having", but it's more
>>>  ambiguous]
>>> 
>>>  Words that describe attributes of an instance *to be created* should
>>>  go in argument labels, rather than the base name (for parity with
>>>  initializers):
>>> 
>>>     AudioTrack(mediaType: "BetaMax")                   // initializer
>>>     trackFactory.newTrack(mediaType: "Wax Cylinder")   // yes
>>> 
>>>     trackFactory.newTrackWithMediaType("Wax Cylinder") // no
>>> 
>>> 3. (this one is separable) When the first argument is the *name* or
>>>  *identifier* of the subject in the base name, do not label it or
>>>  describe it in the base name.
>>> 
>>>     a.transitionToScene(.GreatHall)               // yes
>>>     a.transitionToSceneWithIdentifier(.GreatHall) // no
>>> 
>>>     let p = someFont.glyph("propellor")           // yes
>>>     let p = someFont.glyphWithName("propellor")   // no
>>>     let p = someFont.glyph(name: "propellor")     // no
>>> 
>>> Thoughts?
>>> 
>>> -- 
>>> -Dave
>>> 
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
> 
> -- 
> -Dave
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160204/e399db24/attachment-0001.html>


More information about the swift-evolution mailing list