Skip to content

Instantly share code, notes, and snippets.

@rnapier
Forked from erica/closure.md
Last active May 14, 2016 16:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rnapier/b80b50cceac90445d820468341c750b6 to your computer and use it in GitHub Desktop.
Save rnapier/b80b50cceac90445d820468341c750b6 to your computer and use it in GitHub Desktop.

Enhancing closure argument flexibility

Introduction

This proposal loosens closure requirements to provide developer flexibility. It removes the _ in requirement for ignored parameters and allow closures to use any, all, or none of the implicit $n variables as desired.

*The Swift-evolution thread about this topic can be found here: Removing "_ in" from empty closures

Motivation

Swift closures that do not explicitly declare an internal parameter list must reference all arguments using implicit $N shorthand names. This requirement nullifies the effectivenes of Swift's $N syntactic sugar for closures that do not use all their arguments. Eliminating this requirement means:

  • {} becomes a valid 'noop' closure in any context requiring a Void-returning closure. Implementations discard unnecessary code cruft, and streamline the minimum implementation from { _ in } to {}.
  • { expression } is a valid closure in any context requiring return value. For example, when declaring a default closure argument the expression can simply offer a literal such as { 42 }.

Furthermore, the current approach is inconsistent in how it treats $0 versus other parameters. $0 is treated as the entire tuple, while $1 is treated as the second element of the tuple. For example, given the closure:

typealias Closure = (Int, String) -> Any
let f: Closure = { $0 }
f(1, "Test")          // (.0 1, .1 "Test")
let g: Closure = { $1 }
g(1, "Test")         // "Test"

This proposal changes $0 to be of type Int (the first parameter) in this case.

This proposal only applies to closures that would be eligible for $N shorthand names. Closures that name some parameters must still provide placeholders for all of them. For example:

Given a closure of type (Int, String) -> Int, the following closures would be valid:

{ 1 }
{ $0 }
{ x, y in x }
{ x, _ in x }
{ _, _ in 1 }

The following closure would not be valid:

{ x in x }

Detailed Design

Parameterized closures types should accept closures that do not explicitly mention any or all of the implicit arguments, as Swift currently does. These should all be valid:

let _: () -> Void = {}
let _: (Int) -> Void = {}
let _: (Int, Int) -> Int = { 5 }
let _: (Int, Int) -> Int = { $0 }
let _: (Int, Int) -> Int = { $1 }

The empty closure literal {} should autopromote to satisfy any void-returning closure type (T...) -> Void.

// Current
doThing(withCompletion: { _ in })
let x: (T) -> Void = { _ in }

// Proposed
doThing(withCompletion: {})
let x: (T) -> Void = {}

Alternatives Considered

  • Encourage optional closures. Rather than simplify the {} case, encourage APIs to use optional closures in cases where this would be meaningful. This approach does not scale to non-Void cases, and does not align well with current Cocoa APIs (most completion handlers are not optional in Cocoa).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment