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

Dave Abrahams dabrahams at apple.com
Wed Feb 3 19:27:30 CST 2016


on Wed Feb 03 2016, plx <swift-evolution at swift.org> wrote:

>> On Feb 3, 2016, at 5:02 PM, Dave Abrahams via swift-evolution
>> <swift-evolution at swift.org> wrote:
>> 
>> 
>> 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.
>
> This is a multi-argument function and is addressed in the multi-argument section.
>
>> 
>> 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.
>
> The proposed rules are short (and stated tersely). There’s probably a
> way to expand them into a 1-2 sentence/each that doesn’t need as much
> explanation.
>
>> 
>> 2. Does this actually produce different results than the guidelines I
>>   have suggested?
>
> On “Swift-stdlib” code I don’t think it gets different results.
>
> On application code it falls in the camp that wants
> e.g. tracksWith(mediaCharacteristic...) and tracksWith(mediaType…),
> rather than `tracksWithMediaCharacteristic(...` and
> `tracksWithMediaType(…`.

I can understand there's a good argument for going that way.

> Other than that, it tries to avoid any reference to grammatical rules
> in its formulation.

OK, I'll give it a good looking-over.  Thanks for addressing my
question.

>> 
>> 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
>> 
>> _______________________________________________
>> 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