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

Dave Abrahams dabrahams at apple.com
Wed Feb 3 17:02:40 CST 2016


on Wed Feb 03 2016, plx <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).

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

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

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

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

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

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

Thanks,
Dave

> Everything that follows is my preferences, but I generally agree with
> Erica’s suggestions in the concrete cases. I also think the emphasis
> on quasi-grammatical rules-and-roles is something of a dead-end for
> design guidelines and won’t include such considerations in what
> follows.
>
> ## RULES
>
> ### I. Single-Argument Functions:
>
> #### RULES:
>
> - general rule: don’t label the first argument
> - exceptions:
>   - (a) the argument has a default value (`removeAll(keepCapacity: Bool = default)`)
>   - (b) the function acts-like a constructor (covered in your rule 2)
>   - (c) the “ecosystem rule” (see section III)
>   - (d) the semantics of the argument are non-obvious (see below)
>
> #### REMARKS:
>
> I’m not sure (d) actually exists, though; every concrete example I can
> think up either falls under rule (b) or rule (c). It may not actually
> need to be a rule (other than as, perhaps, the underlying motivation
> for rules (b) and (c)).
>
> My intent with (d) was to address a similar concern as in Erica’s
> `init(truncating …)` and `init(saturating …)`: “if a reasonable reader
> would be unclear which of N plausible implementation choices you are
> making, you may wish to label the argument, even if you only have a
> single such function”…but, again, it’s hard to find any examples for
> (d) that aren’t also some mixture of (b) and/or (c).
>
> ### II. Multi-Argument Functions:
>
> #### RULES:
>
> - general rule: label all arguments
> - exceptions:
>   - (a) omit the first label whenever the first argument is the
> semantic focus, and the other arguments are some mix of “details,
> adjustments, or modifiers”
>   - (b) omit labels entirely whenever argument-ordering is irrelevant to the output (see below)
>
> #### REMARKS:
>
> For (a), the assumption is that we have a general consensus that “in
> methods for which one of the arguments is the semantic focus, that
> argument should be the first argument”; this seems pretty widely
> followed.
>
> This rule seems to cover e.g. `addObserver(_:forKeyPath:)` and
> `addObserver(_:selector:name:object:)` and `encodeObject(_:forKey:)`
> and `present(_:animated:completion:)` (née
> `presentViewController(_:animated:completion:)`), and so on.
>
> A point to bring up is that under these rules, the “evolution” of a
> name would be different: the just-so story for how
> `addObserver(_:forKeyPath:)` came to be so-called is that it *began*
> as `add(observer:forKeyPath:)`, but b/c the `observer` argument is the
> semantic focus it "made sense to move `observer` into the method
> name”; that is, the assumption is that functions like
> `addObserver(_:forKeyPath:)` are considered to be exceptions to the
> "base convention” and need to be justified.
>
> Also note that "counter-examples" to rule (a) are anything for which
> no one argument is easily-identifiable as the semantic focus.
>
> EG, in a function like:
> `adjudicate(plaintiff:defendant:circumstances:)` we can colorably
> claim `circumstances` is a modifier-type parameter, but we don’t—and
> shouldn’t!—treat either `plaintiff` or `defendant` as the
> semantic-focus. If you have two focuses then you have no focus, as it
> were.
>
> For (b), the intuition is that whenever argument-order is irrelevant,
> arguments should be unlabelled; thus e.g.:
>
> - min/max: don’t label the arguments
> - hypot: don’t label the arguments
> - copysign: ideally, label the arguments
> - atan2: ideally, label the arguments
>
> …and so on. Note that these examples are all "free functions”; there
> don’t seem to be many natural examples that *aren’t* free
> functions. Also, please don’t be mislead by your familiarity with
> e.g. `copysign` and/or `atan2`; they are used here to illustrate a
> general principle (argument-ordering) only, but in practice such
> highly-familiar “legacy functions” might be best-off given
> special-case handling.
>
> ### III. Naming Functions/Ecosystem Rule
>
> The previous sections essentially assumed the function names are
> already-chosen (in line with existing conventions) and voice specific
> argument-labeling preferences.
>
> This section deals with a few changes to how function names should be chosen.
>
> The over-arching consideration is what I’ve been calling the
> “Ecosystem rule”: whenever a method a member of a “method family"—or
> could foreseeably become a member of such—one should aim for
> consistency in the base name, and use argument-labels as necessary;
> note that method families need not *require* argument labels:
>
> `contains(_: Point)`
> `contains(_: Line)`
> `contains(_: Shape)`
>
> …but they *may* require them, as for example in the `login` function
> that has already been discussed.
>
> The “ecosystem-rule" can also be applied somewhat more-broadly;
> consider the following name suggestions:
>
> `animate(duration:animations:)`
> `animate(duration:animations:completion:)`
> `animate(duration:delay:options:animations:completion:)`
> `animateUsingKeyFrames(duration:delay:options:animations:completion:)`
> `animateUsingSpring(duration:delay:damping:initialVelocity:options:animations:completion:)`
>
> …where the first three form an obvious family, and the next two are
> obvious “cousins” of that family due to choice of base names.
>
> A corollary of this policy is that the rule (3) suggestion—of omitting
> something like `…ForIdentifier...` or `(forIdentifier:…)`—will
> sometimes be overruled out of ecosystem concerns, but I suspect this
> will be somewhat rare in practice.
>
> For example, consider the following naming suggestions for the “tracks” example:
>
> // solo method (not part of any family)
> asset.trackWith(trackID)
>
> // family
> asset.allTracksWith(mediaCharacteristic: …)
> asset.allTracksWith(mediaType: ...
>
> // the below, instead of `trackWith` or `track(
> asset.firstTrackWith(mediaCharacteristic: ...)
> asset.firstTrackWith(mediaType: …)
>
> …or the same again, but perhaps dropping the `With` if that’s the overall preference.
>
> In any case, the overall goal behind the "ecosystem rule” is that
> similar things should be named similarly, and when semantic
> differences are small-enough it makes sense to use argument labels to
> make distinctions; different base names should be for functions that
> are at least a little different from each other.
>
> ## GENERAL REMARKS
>
> Note that with the way I’ve tried to formulate these rules the Swift
> standard library should largely stay as-is. In particular:
>
> - methods without an identifiable “semantic focus” seem rare in a
> standard-library context; IMHO they occur naturally, but only really
> within UI/application-level code, not “basic building blocks” code
> - "method families” seem somewhat unnatural in “Swift-y” code outside
> of a small number of special-case scenarios (`contains`, various
> `init` families, etc.); they seem more common in UI/application-level
> code (e.g. for Objective-C interoperation), as default arguments cover
> most of the motivating use-cases
>
> …and most of the intent in these rules is to free up some room in the
> guidelines so that application-level code can be written to the
> guidelines without going through bizarre contortions (e.g. no one
> would ever have *chosen* `func
> dismissViewControllerAnimated(_:completion:)`, and we shouldn’t have
> to chose between either (a) using equally-awkward constructs in our
> own code or (b) being “non-guideline-compliant”).
>
> ## REMARKS ON RULE 3
>
> Separately, I think rule 3 is a hair too coarse to be a good guideline as-stated.
>
> I would split the “asking for X by name/identifier/etc.” into two cases:
>
> - (a) asking for X by some well-known/canonical $ID (e.g., such that
> it is a *major* error if no X is found for $ID)
> - (b) asking for X by some identifier (without a strong expectation as
> to whether or not such an X will or won’t be found)
>
> …and at least as a code-reader:
>
> - I have no objection to the proposed rule (3) in scenario (a)
> - I find rule (3) very odd in scenario (b)
> - I think very differently about scenario (a) and scenario (b), and
> would thus prefer that they look different
>
> …and that’s my thoughts, here.
>
>> On Feb 2, 2016, at 6:32 PM, Dave Abrahams via swift-evolution <swift-evolution 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) 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
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

-- 
-Dave



More information about the swift-evolution mailing list