Trailing closures
When the last parameter of a call is a function, the closure argument can follow the call as a brace instead of sitting inside the parentheses.
If the last parameter of a function is itself a function, a call may write that argument as a { ... } after the
call instead of inside its parentheses.
Example#
const numbers = [1, 2, 3, 4]
const doubled = numbers.map({ _ * 2 })
const same = numbers.map { _ * 2 }
const total = numbers.fold 0 { sum, number => sum + number }
print doubled.toList()
print same.toList()
print totalSyntax#
<call>(<other arguments>) { <closure> }
<call> <other arguments> { <closure> }Rules#
A closure that is the last parameter of a call may be written after it, as a brace.
numbers.map({ _ * 2 })andnumbers.map { _ * 2 }fill the same parameter.A trailing closure fills the last parameter of the call, whatever position it was declared in. If that parameter already has a value - positionally or by label - the trailing closure is a compile error, because it would fill something that is already filled.
Does not compilefn withTransform(transform: (value: Int) => Int, seed: Int): Int { transform(seed) } const result = withTransform({ value: Int => value + 1 }, seed: 1) { value: Int => value * 2 } print result // error: The trailing closure fills `seed`, which was already given by nameThe implicit parameter of a trailing closure is always
_(_2,_3, ...), never a name taken from the expected function type.fn map<Output>(transform: (value: Item) => Output)documents its parameter asvalue, but that name is not in scope inside the closure - it acts at a distance, and renaming the parameter of a library function would silently break every caller that relied on it._or a written parameter name are the two ways to read the value.Does not compilefn mapped<Output>(items: List<Int>, transform: (value: Int) => Output): List<Output> { var result: List<Output> = [] for item in items { result.append transform(item) } result } const doubled = mapped([1, 2, 3]) { value * 2 } // error: Cannot find `value` hereThe diagnostic's note names the fix exactly:
The parameter of this closure is \`; to name it, write `{ value => ... }`. Writing` or naming the parameter explicitly both work.fn mapped<Output>(items: List<Int>, transform: (value: Int) => Output): List<Output> { var result: List<Output> = [] for item in items { result.append transform(item) } result } const doubled = mapped([1, 2, 3]) { _ * 2 } const named = mapped([1, 2, 3]) { value => value * 2 } print doubled print namedA trailing closure always belongs to the outermost command call of the statement. Commands do not nest, so the argument of a command call cannot itself contain a trailing closure - the closure the reader sees last belongs to whichever command started the statement.
Does not compilefn transformed(items: List<Int>, transform: (Int) => Int): List<Int> { var result: List<Int> = [] for item in items { result.append transform(item) } result } fn show(value: List<Int>) { print value } const numbers = [1, 2, 3, 4] show transformed(numbers) { item: Int => item * 2 } // error: The trailing closure fills `value`, which was already given by name // error: `transformed` takes 2 arguments, 1 was givenA trailing closure is also the body of the head of
if,for,whileandmatchwhen the head is a command call, for the same reason: a command's{is read as its body, exactly like the built-in statements.
What this is not#
A trailing closure is not the closure of an inner call written with parentheses. Passing a closure to a call
that is itself an argument needs the parentheses form for the inner call, so that the trailing { at the end of the
statement is unambiguous about which call it belongs to.
fn show(value: List<Int>) {
print value
}
fn transformed(items: List<Int>, transform: (Int) => Int): List<Int> {
var result: List<Int> = []
for item in items {
result.append transform(item)
}
result
}
const numbers = [1, 2, 3, 4]
show transformed(numbers, { _ * 2 })fn show(value: List<Int>) {
print value
}
fn transformed(items: List<Int>, transform: (Int) => Int): List<Int> {
var result: List<Int> = []
for item in items {
result.append transform(item)
}
result
}
const numbers = [1, 2, 3, 4]
show transformed(numbers) { item: Int => item * 2 }
// error: The trailing closure fills `value`, which was already given by name
// error: `transformed` takes 2 arguments, 1 was givenRelated#
- Closures - the one closure form a trailing closure is an argument of.
- Arguments and labels - how the rest of a call's arguments are matched to parameters.
- Command calls - command position, and why a command's arguments cannot nest a trailing closure.