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

David Owens II david at owensd.io
Tue Feb 2 23:21:09 CST 2016


Awesome for putting this together. I think the rules add some needed clarity, but they have the bias that the first argument *should* have a defaulted empty parameter label. I actually think that complicates matters.

Instead, if the premise was that the first argument is treated no differently then other arguments, then the rule becomes simply a list of exceptions on when the label is not necessary:

If the argument's purpose in the primary semantics is clear from the name of the function, then no argument label is required (regardless of argument position).

To be clear, this rule does not change the API guidelines statement that all labelled arguments should be placed after non-labelled arguments.

Examples:

Remove Label(s)

a.contains(b)  // the question of containment of b in a is the primary semantic
a.merging(b)   // combining b with a is the primary semantic

a.moveTo(300, 400)  // moving to coordinates is a primary semantic of a
                    // the labels can be removed when a has a clear semantic
                    // limitation of a two-pair coordinate system

a.readFrom(u, ofType: b)  // u is clearly the target of the primary semantic
                          // "reading", ofType labeled as it's a modifier

Keep Label(s) - the default behavior

a.dismiss(animated: b)  // dismissal is the primary semantic;
                        // `animated` is a modifier to that semantic, so
                        // keep the label

a.moveTo(x: 300, y: 400)  // moving is the primary semantic, however a
                          // has no clear semantic limitation to a 2D
                          // coordinate context so keep the labels

a.read(from: u, ofType: b)  // keep from as there is no non-ambiguous semantic
                            // relationship between u and what is being read
                            // without the label. e.g. without from, it's not
                            // clear that read is reading from u, it could read
                            // from stdin.

The `moveTo` example is also an illustration of why `max` would have no labels:

max(x, y)  // maximum value is primary semantic of the parameter set

I put the `a.readFrom(_:ofType:)` and `a.read(from:ofType:)` as an illustration of two valid API names that would have different applications of the rule depending on choice of the first part of the function identifier. I'll argue below which I think is better, in the general case, and the modification of rule #2 to support it.


> 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

I see this rule as more about how to name the APIs instead of being about the argument labels. This is also strikes at the root of our fundamental disagreement that we cannot seem to resolve. =)

You seem to treat the entire function identifier to two disjoint parts (please correct me if I'm misunderstanding you): the base name, including the relationship to the first parameter, and the supporting labels. The base name is the primary indicator of what the function does, with the inclusion of how the first argument relates to that semantic. The remaining argument labels are there mostly as secondary information.

a.tracksHavingMediaType("Wax Cylinder")
// identifier: tracksHavingMediaType(_:)

However, I treat the entire identifier as a complete whole with no real deference to the base name over the argument labels. So the above example would be broken down like this:

a.tracksHaving(mediaType: "Wax Cylinder")
// identifier: tracksHaving(mediaType:)

**IF** we start with the premise that argument labels are required by default, the simplified ruleset I used above already answers how to put the label on regardless of which "base name" you chose. 

I would then change rule #2 to this: the "base name" of the function describes the semantic intention while the argument labels are used to describe their relationship to that semantic intention or modification of how that intent is to be carried out.

a.tracksHavingMediaType("Wax Cylinder")    // no, `MediaType` relates to the argument, not
                                           // primary intent of the function.

a.tracksHaving(mediaType: "Wax Cylinder")  // yes, the primary intent is to find tracks
                                           // based on some criteria. 

No special rules or exceptions are necessary.

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

The labelling piece is already covered by the simplified rule #1 above and by my modified rule #2, so this rule is redundant. `WithIdentifier` and `WithName` should never be present because they belong to the first parameter, not to the semantic intention of the function.

The choice between `transitionToScene(_:)`, `transitionTo(_:)`, or even `transitionTo(scene:)` is an interesting one. I do not think there is an objectively clear winner based on the limited sample context. Though if we followed the more direct guidance of the modified rule #2, the only real options left would be: `transitionTo(_:)` and `transitionTo(scene:)`.

If `a` were a scene manager and the only transitioning that would take place, it could be argued that `transitionTo(_:)` is enough.
If `a` were a general transitioning engine, then `transitionTo(scene:)` would be the choice based on the modified rules #1 and #2 as the semantic intention of transitioning is clear, but the target would not be, so the `scene` label would be kept.

For clarity: `transitionToScene(_:)` would not be a candidate as rule #2 states that qualifiers for arguments, in this case `Scene`, belong with the argument itself.

In the very least, I hope this better articulates why I don't prefer the treatment of the first argument as special, and why I do not put first argument information into the "base name" of the function.

Cheers!

-David



-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160202/53b904a9/attachment-0001.html>


More information about the swift-evolution mailing list