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

Charles Kissinger crk at akkyra.com
Thu Feb 4 15:43:36 CST 2016

For the examples discussed below:

func indexOf(element: Element) -> Index?
func rangeOf(searchString: String, options: NSStringCompareOptions) ->

would conform to the guidelines given an “implied get” rule, would they not?

trimming() is the problem child.


> On Feb 4, 2016, at 11:47 AM, Dave Abrahams via swift-evolution <swift-evolution at swift.org> wrote:
> 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
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

More information about the swift-evolution mailing list