Swift’s syntax for trailing closures and multiple trailing closures are fantastic for expressibility, but there’s a minor wart: you cannot label the first trailing closure, only the subsequent ones.
Especially in such an otherwise-expressive language, this restriction is a bit jarring, and has real impacts on API design.
Method Pairs
As a simple example, I think it’s helpful to include method pairs like these:
extension Optional {
/// Nullify `self` when `condition` is true, skipping the check when already nil. mutating func nullify(when condition: @autoclosure () -> Bool) { guard case .some = self, condition() else { return } self = nil }
/// Nullify `self` when `condition` is false, skipping the check when already nil. mutating func nullify(unless condition: @autoclosure () -> Bool) { nullify(when: !condition()) }
}…to help write cache-invalidation logic:
// conditionally-invalidate derived value:var someField: Foo { didSet { _someCachedProperty.nullify(when: oldValue != someField) }}
var activeComponents: Set<ComponentID> { didSet { _fooDetails.nullify(unless: activeComponents.contains(.foo)) _barDetails.nullify(unless: activeComponents.contains(.bar)) }}Given the need to use labels to disambiguate between the two methods, we wind up with @autoclosure being the best fit for this API (instead of just using closures).
Fused Functional Chains
As another example, for performance reason I often create “fused” versions of common functional chains: a fused “map, filter”, a fused “filter, map”, and so on.
Without the ability to label the first trailing closure, however, we’re buying performance at the cost of readability. For example, consider a fused operation like mapFilterMap:
extension Sequence { /// More-efficient equivalent to doing "map, filter, map" in 3 separate eager steps. func mapFilterMap<T, R>( map extraction: (Element) throws -> T, filter isIncluded: (T) throws -> Bool, map transform: (T) throws -> R ) rethrows -> [R] { var results: [R] = [] // real version would have a hint for how much to reserve for item in self { let extractedValue = try extraction(item) guard try isIncluded(extractedValue) else { continue } results.append(try transform(extractedValue)) }
return results }}Handy to have, but awkward to use:
// original, highly-readablelet premiumContactInfo = orders .map(\.customer) .filter(\.isPremium) .map(\.contactInfo)
// *hypothetical* replacement with similar readabilitylet premiumContactInfo = orders.mapFilterMap map: { $0.customer } filter: { $0.isPremium } map: { $0.contactInfo }
// *actually-possible* replacement with awkward readability;// feels clunky no matter how you finesse the formattinglet premiumContactInfo = orders.mapFilterMap { $0.customer } filter: { $0.isPremium } map: { $0.contactInfo }Is There Hope?
Sadly, no: this capability has already been discussed-and-decided against, as discussed a bit here.