Debate has been raging for weeks about the scope and content of guidance to include in Apple’s API guidelines, particularly when it comes to labeling arguments in methods and functions.
A Swift signature consists of a method name followed by a series of argument labels, parameter name, and types, and concludes with a return type.
methodName(argumentLabel parameterName: parameterType, ...) -> returnType
Swift 2 adopted a convention that follows Objective-C style, where the Swift first argument label is omitted by default and its information is subsumed into the method name. In nearly every case, argument labels followed three simple rules:
- Skip argument labels for a method or function’s first parameter
- Use argument labels for a method or function’s subsequent parameters
- Require argument labels for initializers
These Objective-C-based language defaults enable devs to structure readable code sentences from API calls, for example:
myHandle.readFrom(file, ofType: utiSpecifier)
reads as “read from file of type utiSpecifier“. Retaining this kind of meaningful sentence structure has been a high priority for the Swift team.
The challenges of the Objective-C Legacy
Despite strong influences to the contrary, Swift is not Objective-C and it’s becoming less Objective-C with every update. Language evolution means that Objective-C-based naming rules must be re-considered in the light of certain Swift realities.
Many methods are overloaded. A
login method may take a credential argument or a username/password pair:
login(credential: myCredential) login(username: "bob", password: "123")
While you could use
loginWithUsername(_, password), these renamed forms are less clear. Both alternatives use with, an indication that there’s something fundamentally askew in your naming choices.
“With” better describes use from a call site rather than method or function semantics. So when using “with” as your go-to preposition, carefully consider whether you should use first argument labels or whether other more meaningful prepositions could apply.
Many Swift functions are fundamentally different from Objective-C instance methods. Calls like
min(number1, number2) zip(sequence1, sequence2)
offer no intrinsic “sentence”. They are operations on arguments that are essentially anonymous. They provide no useful way to distinguish one from another. Apple calls these “peered” arguments.
Peers also pop up in method calls, when there’s a strong semantic connection between parameters. These next examples are peered but in a non-anonymous fashion: (x, y) and (red, green, blue, alpha).
mySprite.moveTo(x: newXLocation, y: newYLocation) view.fadeToBackgroundColor(red: r, green: g, blue: b, alpha: a)
In each case, you could move the first argument from the label to the method name (
moveToX(_, y:)) but you’d lose your core peer grouping for the sake of Objective-C conventionality.
Some Swift methods are more initializer-like than method like. A similar issue occurs when building factory-style methods that create new instances outside of normal Swift initializer construction.
Vehicle.buildStandardBus(type: .School, color: .Yellow, district: .CherryCreek)
The arguments for this call aren’t “peers”. They consist of unrelated elements intended to initialize fields. Despite that, their call pattern better follows a fully-specified initializer pattern than an Objective-C-like version. Once again, it makes sense to use first argument labels.
Apple at this point has three ways it can go:
- It can stick to Objective-C style guidance, providing a list of exceptions where you should use first labels overrides;
- It can use Label-style guidance, recommending argument labels in all cases, with a long list of exceptions where you should omit first labels and pick up context from the base name; or
- It can entirely drop guidance on label naming, allowing overrides at the developer’s discretion.
After weeks of discussion about whether parameters describe instance attributes, whether parameters are semantically distinct from base name actions, how to interpret whether prepositions modify first arguments, and so forth, I’m now leaning towards option three.
There’s a scenario I call “going Hungarian“, by which I mean introducing rules that are so complex and overspecified that one loses sight of the basic principles of keep your naming clean, short, readable, and consistent. I’m afraid that the currently proposed rules have, in fact, gone Hungarian and Apple would be better served by stepping back and dropping the issue.
This was not my opinion going into the discussion. But after reading extensively through the ongoing discussions, the concerns, the tweaks, and the minutia that keep being brought up, I’ve come to the conclusion that Swift legitimately breaks the Objective-C paradigm. There are sufficient and valid reasons to drop the “one size fits all don’t use first arguments” rule under Swift’s new reimagined type system and module interfaces.
I’m not saying change the way Swift defaults its argument labels or modify its implementation. I like it exactly the way it is. I’m saying I now support dropping the effort to define when and where to use first argument labels and not to.
This opinion will not change the ongoing effort to modernize Foundation APIs, and convert familiar calls to more Swift-like ones. I’m just trying to say that the developer-facing API guidance should perhaps step back and do less rather than come up with rules (no matter how parsimonious) about whether and when to use first argument labels and when not to.
My proposed replacement rule for this whole mess is this: “Skip argument labels for a method or function’s first parameter unless it makes more sense not to.” The choice is left as an exercise for the programmer, who should use their own judgement. So long as the result is clean, short, readable, and consistent, I’m good with it.
Disagree? Let me know why. Drop a comment, a tweet, or an email.
Update: A new set of proposed rules from Apple:
A. Try to form a grammatical phrase including the first argument and describing the primary semantics at the call site.
B. The first argument gets a label when and only when:
- It does not form part of a grammatical phrase describing the primary semantics. For example,
[more examples needed] Note that parameters with defaults never describe the primary semantics. so are always labeled.
func invert(options options: SomeOptionSet = ) // yes func invert(_ options: SomeOptionSet = ) // no
- The method is a factory method; such calls should mirror initializers, with no preposition. For example,
let x = UIColor(red: r, green: g, blue: b) let y = monitor.makeColor(red: r, green: g, blue: b)
- It is part of a prepositional phrase
a. The label normally starts with the preposition. For example,
x.move(from: a, to: b) x.loadValues(forKeys: ["fox", "box", "lox"])
b. …unless the preposition would break a very tight association between parameters:
x.moveTo(x: a, y: b)
[encourage grouping parameters into higher-level concepts, e.g. Point, in these cases]