I'd like to pitch a potential model for getting single trailing closures and subscript labels to be consistent with the language. This builds on @xwu's proposal for allowing optional labels for single trailing closures, and also should be read in the context of the Core Team's response.
A note that I haven't quite done my homework here in terms of reading all of the numerous prior discussions and comments, so forgive me in the event this idea has been brought up before or is a non-starter.
Trailing Closures
The core concept is this: we introduce the ability for API authors to control the call site for single trailing closures as we would with an attribute, but rather than using an attribute we use the (_ label:)
and (label label:)
syntaxes and behaviours as follows:
// Currently: can be called as `doAction {}`
// Under this and @xwu's proposal: can be called as either `doAction {}`
// or `doAction closure: {}`
func doAction(closure: () -> Void) {}
// Currently: Must be called as `doAction {}` when using trailing
// closure syntax.
// Unchanged in this proposal.
func doAction(_ closure: () -> Void) {}
// Currently: Called as `doAction {}`
// Under this proposal, must be called as `doAction closure: {}`.
func doAction(closure closure: () -> Void) {}
The non-trailing-closure variants of these calls would work as they currently do.
The important caveat of this approach is that this is source-breaking for users of any API authors which had an explicitly provided exterior label (the func doAction(closure closure: () -> Void) {}
form), since now such calls must be used with a label. However, in cases where the API author has gone out of their way to label the user parameter separately from the interior parameter name, I suspect it's likely that they intend for the label to be explicitly provided, and this may be acceptable source breakage.
Subscripts
As an extension, we could apply this same principle to make subscript labels consistent with the rest of the language as shown below:
struct SomeType {
// Currently: must be called as object[value]
// In this proposal: can be called as either object[x: value] (preferred)
// or object[value] (source compatibility).
subscript(x: Int) { return x }
// Currently: must be called as object[value]; ambiguous with/
// same meaning as subscript(x: Int)
// Unchanged in this proposal.
subscript(_ x: Int) { return x }
// Currently: must be called as object[x: value]
// Unchanged in this proposal.
subscript(x x: Int) { return x }
}
This has a weaker argument in its favour. It is source breaking for types that have both labelled and unlabelled subscripts where the interior label is the same as the exterior label for another method. It also removes control of the call site for existing APIs, since now users have the option for whether to provide the label. However, it does create a path for future consistency for the language.
For the subscript case, the behaviour could potentially be gated on Swift language versions, wherein code compiled with Swift <= 5 without an explicit exterior label (i.e. subscript(x: Int)
) is treated as if it had been written as subscript(_ x: Int)
.
Evaluation against Evolution Principles
In terms of how these intersect with the Core Team's outlined principles:
-
Source Stability: This proposal is source-breaking for a (hopefully small) percentage of code where it is likely that the caller is using syntax contrary to the API author's intent (i.e. where the
(withParam param:)
syntax is used). It additionally will break subscripts where the author has explicitly given the caller the ability to choose their syntax (subscript(x:)
andsubscript(x x:)
within the same type); however, the migration for this is fairly trivial. As a result of source compatibility concerns this proposal would need to be only applied under a new language version. -
Control of the Call Site: This pitch adds forms for which the author has full control over the call site, although it does not remove control from the caller (for preserving source compatibility) and, for the case of subscripts, introduces a new way for callers to call existing declarations (or at least those compiled in the Swift 6+ language mode), which does run counter to the "Control of the Call Site" principal.
-
Language Consistency and Future Directions: This proposal introduces a consistent model that could potentially support deprecating the old/ambiguous syntax in a number of years. Tooling would guide users to the preferred options (i.e. labelling consistent with standard functions/methods, where
_
indicates no label andlabel:
means that label should be included at the call site. The legacy ways of calling would exist for source compatibility but should not be encouraged going forward.
Even though there are a few issues that I've outlined, and I'm sure a few more I haven't thought of, I'm hoping this might be a launching board for other's improvements and discussions. Even with the issues, I personally feel that this would be an improvement over the current state and would allow both writing more consistent code and designing more consistent APIs going forward.