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

Dave Abrahams dabrahams at apple.com
Thu Feb 4 12:58:35 CST 2016


> On Feb 4, 2016, at 12:23 AM, Radosław Pietruszewski <radexpl at gmail.com> wrote:
> 
> 
> 
>> 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.

Well, to give you an idea, we've been going with the ground rule that the number of APIs that need to be fixed up with NS_SWIFT_NAME because they become actively worse needs to be <1%.  I estimate that we can accept maybe 3% of APIs looking not-quite-right but no worse than before.

If these fall into that 3%, it might be acceptable.

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

Yes, this makes a lot of sense to me.  Maybe we can distinguish the "find" cases from the "get" cases as you suggest below.

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

I'm not sure I know how to make that case without getting into arguments about efficiency.  Some people have a *very* strong resistance to allowing naming guidelines to be dependent on efficiency.  Ideas?

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

I can't disagree.  At least one of us in the guidelines working group has a visceral negative reaction to ending a basename with "With".  I'll have to ask whether that applies to every preposition or not.

> - doable as an automatic translation from ObjC

Hmm, are you sure?  The problem may be identifying those places where "with" functions this way vs. as a vacuous separator.  The latest best heuristic we've got for doing that is to consider it a vacuous separator when there's a verb in the name, but that will fall down on removeFirstTrackWith...
It's all about finding an automated approximation to the actual guidelines that gives a fairly low error rate.

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