When a call returns a value, I almost always use in-line closures. I prefer to surround functional calls with parens. I know my style is not universally popular, as many developers prefer the look of raw braces. It’s not uncommon to see calls like this:
numbers.sorted { $0 < $1 }.forEach { print($0) }
The functional sort is chained to the non-functional loop, both using braces. Mandatory functional parens do two things. They differentiate expressions and they add the clarity of role labels that are lost with trailing closures. Compare these with the preceding call:
numbers.sorted(by: { $0 < $1 }).forEach { print($0) } // or even numbers.sorted(by: <).forEach { print($0) }
In each case, the combination of name and label (in this case, sorted(by:)
) is clearer and more easily read. Many APIs are designed without initial labels for Void
-returning closures. They’re intended from the start to act as language-like constructs, so adding labels is either superfluous or detracts from the call when omitted:
public func forEach(_ body: (Self.Element) throws -> Void) rethrows
Omitting labels shouldn’t cloud intent even when a closure meant for functional calls is used. I could imagine common API naming rules to help guide labels and functional use (for example, “omit labels for Void
-returning closure arguments” or “include role hint labels for functional closure arguments used to process and pass through values”).
Such rules might help but there’s one class of closures that doesn’t normally follow. Completion handlers, especially those sourced from Cocoa and Cocoa Touch, are almost always labeled as completion
or some close variant. They’re designed for non-returning (and often asynchronous) execution. For example, you might see:
func run(_ action: SKAction, completion block: @escaping () -> Void)
or
func dataTask(with: URL, completionHandler: (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
These items are normally called as trailing closures, so why shouldn’t their API naming follow suit? Wouldn’t it make more sense to have _ completionBlock:
or _ completionHandler:
than to use explanatory labels that are most commonly hidden when called?
What do you think? Let me know.
2 Comments
Nice to see another judicious trailing-closure user!
As you likely remember, I posted a related question in July and got some interesting comments. https://forums.swift.org/t/trailing-closure-blog-post/14668 I will totally write that post as soon as I conquer autorenewing subscriptions for my app Immigration.
A cleaner design may have been to treat “trailing closure” as a special argument label name, instead of the weird set of special cases it is now, so you’d write your signature something like
func forEach({} body: (Element) -> Void)
to say that thebody
argument is supposed to be a trailing closure.