The trouble with argument labels: some thoughts

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 loginWithCredential(_) and 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.

Providing Guidance

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:

  1. It does not form part of a grammatical phrase describing the primary semantics. For example,
     x.dismiss(animated: y)
    

    [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
    
  2. 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)
    
  3. 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]

12 Comments

  • A couple of corrections:

    “Objective-C style, where the first argument label is omitted by default and its information is subsumed into the method name”

    That is simply wrong. The Objective-C message name (coming from Smalltalk) is the concatenation of all the keywords. There is no distinction between “method name” and “labels”.

    min(number1, number2) -> number1 min: number2.
    zip(sequence1, sequence2) -> sequence1 zip: sequence2.

    I don’t see the problem with “peered” arguments, except in the weird crash of keyword selectors into a bracket function call syntax that is Swift. These problems are purely home-grown and gratuitous.

    That said, I like your analysis of “going hungarian”. To me, that pretty much sums up the whole Swift enterprise.

    • I changed the sentence to “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,” adding “Swift” to make it clear that I was discussing Swift mimicking. The functions are functions, not methods, so did not edit.

    • Just wanted to mention that Obj-C supports anonymous peered arguments too. min:: is a valid selector.

  • Well stated. I didn’t realize how weird it felt trying to make the naming of methods and first arguments feel right given the competing feel of Swift and Objective-C. This article really succeeded in identifying and investigating the root of that nagging feeling.

  • 100% agree. Just to make the rule a bit more precise for myself, I modified your proposed rule slightly to say: ““Skip argument labels for a method or function’s first parameter unless excluding it creates parameter ambiguity.”

  • Label everything, or label nothing. Consistency is all I want.

  • Great post! I agree with you.

  • I believe initially Swift did not use the parameter labels as part of the function’s name. I thought one of the proposals from Doug Gregor changed that. If that is indeed the case, I think the screwy dropping of the label on the first parameter should go away. To me it makes sense that a function name consists of a ‘verb-y’ action before the parens and then the the ‘noun-y’ stuff you need to accomplish that action inside.

    My preferred outcome would be your choice 2, but with a very short list to nonexistent list of exceptions.

  • I too appreciate consistency. My vote is keep all argument labels on by default (no distinction between functions, methods, and initializers) but let the developers choose to drop the first one if they want.

  • It’s also not an Objective-C “style”. It’s the attempt to map one thing onto a completely different thing. The first thing is Smalltalk keyword messages, where the name of the message is the concatenation of keywords. The second is C++/Java/C# style method calls.

    The two are simply incompatible ways of naming things and what you get is *necessarily* a mess, and pretty much an irreconcilable mess. As you have discovered, the least bad option, now that the mess has been made, is to allow people to muddle through as best as they can. Trying to get something consistent out of this mess is at best hopeless. The hole is already dug, time to stop digging… 🙂

    And yes, you can have unnamed arguments in Objective-C. Since I never liked the moveToX:y: style naming, that’s what I adopted for my DrawingContext ( https://github.com/mpw/MPWDrawingContext ).

    So:

    -moveto:(float)x :(float)y;
    -lineto:(float)x :(float)y;
    -translate:(float)x :(float)y;

    etc.

    Not sure what your point is with the functions. Certainly the claim that these “are fundamentally different” from instance methods is incorrect. They work perfectly fine as keyword messages, as I showed with a min: b. and s zip: t. Again, the only problem is the weird idea that these sorts of things are “labeled arguments”, and this is purely a Swift craziness introduced in trying to map from keyword syntax (which is perfectly consistent) to Java/C++/C# syntax (which by itself is also consistent).

  • I like the current label behavior enough, but with the removal of the octothorp syntax in favor of “doubling-up” names to include a first-argument label (when it matches the internal binding), perhaps it would make more sense if methods were to have first-argument labels by default (like initializers); typing ‘_’ to omit a label is cleaner than doubling-up names to include a label, and it does lead to more consistent behavior.

    But, if the practice of omitting the first-argument label remains the rule rather than the exception, perhaps this wouldn’t be such a good idea.

  • If there is a tight association between parameters, make it one parameter only? `x.move(point:coordinates)`