[swift-evolution] When to use argument labels (a new approach)
Radosław Pietruszewski
radexpl at gmail.com
Thu Feb 4 02:23:10 CST 2016
> On 04 Feb 2016, at 02:20, Dave Abrahams via swift-evolution <swift-evolution at swift.org> wrote:
>
>
> on Wed Feb 03 2016, Radosław Pietruszewski <swift-evolution at swift.org> wrote:
>
>> Overall, great guidelines (and +1 to the rules Erica wrote up), and
>> I’m +1 on conveying these nuances in the guidelines.
>>
>>> 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
>>
>> The rationale for doing this is stronger when we talk about automatic
>> translation of Objective-C APIs.
>
> For better or worse, it is a requirement that Cocoa as imported very
> closely approximates full conformance to the guidelines we choose. We
> are shooting for consistency across APIs used in swift.
Hmm. Understood, but sigh. Personally I think it’s a shame to constrain ourselves to (imho) inferior naming convention because it’s what existing ObjC APIs use. Definitely understandable, but considering this isn’t a _really_ super common pattern, I think it would be just fine to leave the translations in good-enough state (whatever the decision on SE-0005 is), and follow a new convention in new Swift APIs.
>
>> But in APIs designed for Swift, I feel like this is wrong IMHO, because:
>>
>> - “media type” is still a parameter, so it shouldn’t be in the base
>> name itself
>
> That doesn't seem obvious to me. Whether that "should" be in the base
> name depends on what guidelines we choose.
Of course. The way I think of it: the primary semantic is “find a track”, and media type is a criterion for search. I understand method families aren’t something you’re looking to optimize, but you can imagine that _if_ this was a family, it would still be “find a track”, just with different criteria.
>> - this breaks the symmetry with other methods due to the reason above
>> (like the “newTrack” you mentioned yourself)
>
> Yes, it would be more consistent if these two cases were the same.
>
>> - doesn’t play well with method families (searching for tracks is
>> searching for tracks. the criteria for search are just parameters).
>
> I don't really believe that “method families” are something we want to
> optimize for in Swift. There are almost always alternatives that impose
> lower cognitive overhead on users.
Fair.
>
>> If we do
>>
>> trackFactory.newTrack(mediaType: "Wax Cylinder") // yes
>>
>> I don’t see why it’s OK to do
>>
>> a.tracksHavingMediaType("Wax Cylinder") // yes
>
> That's just the consistency argument again, right?
Yes.
>
>> Of course just “tracks” is confusing, and we agree on that, but I
>> would strongly recommend that for new APIs we don’t just name the
>> method with a word of an already-existing instance, rather, we start
>> it with a verb:
>>
>> a.findTracks(mediaType: “BetaMax”) // or “searchTracks”, or alternatively “tracksMatching"
>> a.removeFirstTrackMatching(mediaType: “BetaMax”) — ad 2
>> fac.newTrack(mediaType: “Wax Cylinder”)
>>
>> Symmetric, predictable, follows the same convention, plays well with
>> method families (i.e. different search criterion than media type), and
>> no clarity problems.
>
> Unfortunately, this is the reality:
>
> 1. The pattern of omitting prefix verbs like “get” and “find” is
> something of a sacred cow; I think it would be very hard to sell to
> certain important people.
Hmm, I didn’t think of that. “get” is sort of understandable, I can see how in some languages you’d do “getFoo()”, whereas in Swift it would probably be a property “foo”. I don’t see a problem with “find”, though, as it really does help convey the intent.
>
> 2. if we were to standardize on the opposite, we would need an
> objective-C import strategy that would add these verbs automatically.
>
> If you can get a handle on solving #2, it *might* be worth me taking a
> shot at solving #1. Otherwise, I'm afraid this idea is dead in the
> water. Nothing that leaves glaring inconsistencies between imported
> Cocoa and the API guidelines is going to be acceptable.
Then perhaps let’s meet halfway:
a.tracksWith(mediaType: “BetaMax”)
a.removeFirstTrackWith(mediaType: “BetaMax”)
fac.newTrack(mediaType: “Wax Cyllinder”)
(replace “with” with “matching” or “having” if you prefer)
I don’t love it, but like it much more than “tracksHavingMediaType”:
- still much more consistent about what goes into explicit parameters
- having a “with”/etc helps convey that parameters are the criteria for tracks returned
- doable as an automatic translation from ObjC
>> Ad 2: I can see why you don’t like “removeFirstTrack”. It sounds like
>> removing _the_ first track, rather than the first track that matches
>> criteria in parameters list. Perhaps a word like “Matching” would work
>> well to fix this concern. (And sounds/conveys intention better than
>> “with” or “having” IMHO)
>
> There are contexts in which "matching" is more ambiguous than "having",
> e.g.
>
> x.trackMatchingMediaType(t) // track the matching media type?
>
> x.trackHavingMediaType(t) // track is obviously a non-verb here.
>
> Yes, I see how this relates to your "put back the verb" idea.
>
>>
>> Just my 2¢,
>> — Radek
>>
>>> On 03 Feb 2016, at 01:32, 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
More information about the swift-evolution
mailing list