Closures
A brace in expression position is always a closure with inferred or written parameters, var ones too; it captures a const binding as a copy and a var binding as itself, which only a closure handed to a parameter that just calls it may do.
Diese Seite gibt es noch nicht auf Deutsch, deshalb steht sie hier auf Englisch. Zur englischen Seite
There is one closure form. A { in expression position is always a closure, never a block, and it captures the
names of the scope it was written in.
Example#
const double = { x: Int => x * 2 }
const triple: (Int) => Int = { _ * 3 }
const add: (Int, Int) => Int = { a, b => a + b }
print double(4)
print triple(4)
print add(2, 3)Syntax#
{ [var] <parameter>[: <Type>], ... => <statement>* <result expression>? }
{ <statement>* <result expression>? } // Implicit parameters _, _2, _3, ...Rules#
A
{in expression position is always a closure. There is no block expression, so{ 1 }is a closure of no parameters that answers1, never a scope that runs and discards its own bindings.A parameter's type is inferred from the expected type, and has to be written out where there is none.
const double = { x: Int => x * 2 }needs the annotation;const double: (Int) => Int = { x => x * 2 }does not, because the binding's type says whatxis.Kompiliert nichtconst double = { x => x * 2 } // error: The parameters of this closure need types: `{ value: Int => ... }`A closure with no written parameter list uses the implicit parameters
_,_2,_3, one per position.{ _ * 3 }is a closure of one parameter,{ _ + _2 }of two. Naming even one parameter switches the whole closure to named parameters;_and a name cannot mix in the same closure.A tuple position of such a parameter is still written behind a dot:
{ _.0 }, never{ 0 }, because a bare0is the number and nothing else.const pairs = [(1, "one"), (2, "two")] print pairs.map({ _.0 }).toList()An implicit parameter is what a named one would be: where the expected function type takes the parameter as
var,_is avarand changes the caller's value, exactly as{ items => items.append 2 }does.fn filled(body: (var items: List<Int>) => Void): List<Int> { var items: List<Int> = [1] body items items } print filled({ _.append 2 }) // prints [1, 2]The result type is always inferred, never annotated. A closure that needs a written result type is a local
fninstead, becausefnis a declaration and can carry a return type (see Declaring a function); afndeclared by name can be passed exactly where a closure is expected.returnand?inside a closure return from the closure, not from the function around it. There is no non-local return: the valuereturnproduces is the closure's own result, at the point the closure is called, and a?hands its failure to whoever called the closure - so a closure with a?in it has to produce anOptionor aResult(see The question mark operator, rule 8).const clamp = { x: Int, low: Int, high: Int => if x < low { return low } if x > high { return high } x } print clamp(15, 0, 10)A
constbinding is captured as a copy. Reading it inside the closure never sees a later assignment to the outer name, because the outer name cannot be reassigned either (see Bindings).A
varbinding is captured as itself, shared between the closure and the scope it was written in. Both sides see every change; this is the one place in the language where a variable is shared rather than copied.fn total(numbers: List<Int>): Int { var sum = 0 numbers.forEach { sum = sum + _ } sum } print total([1, 2, 3])A closure that captures a
varbinding may only run while the binding exists, and the checker enforces it. A localvar, avarparameter, avar fnreceiver and avarparameter of a closure around it are all the same: such a closure may stand straight as the argument of a call whose parameter only calls it, and nowhere else. Bound to a name, stored in a field, a collection or a case, returned, handed tospawnor to a parameter that keeps it, it is an error. A parameter only calls its closure when it has a function type, its function has a body, and that body calls it (action(value),action value), calls it inside a closure that itself only runs during the call, or hands it on by name to a parameter that only calls it - which is whatforEach,unless, a receiver closure and every block of a DSL do. A lazy stage keeps its closure:items.map { ... }stores it in the stage it answers, so a closure given tomaporfiltermay read avaronly through aconstcopy of it.Kompiliert nichtfn counter(): () => Int { var count = 0 { count = count + 1 count } } // error: This closure captures the `var` binding `count` and may outlive itKompiliert nichtfn makeIncrementer(var target: Int): () => Void { { target = target + 1 } } // error: This closure captures the `var` parameter `target` and may outlive the callThe fix is to hand the value to a
varparameter of the function that runs the closure, or to let the closure return what it computed. The rule is what keeps two copies of a value from ever sharing a variable through a closure they hold, a closure from changing a binding while avaraccess to it runs, and a task from reaching one.varin front of a parameter binds the caller's place, and the function type has to take it asvartoo.{ var point => ... }fills a(var value: Point) => Void: a write topointlands in the value the caller handed over, as it does through avarparameter of afn, and the closure may read the bindings around it while it does, which a namedfncannot. Where nothing is expected,{ var point: Point => ... }is a closure of the type(var point: Point) => Void. Where a function type is expected, a parameter it takes asvaris avarin the closure with or without the word (rule 3); the word says so where the reader sees it. Avarparameter is a reference, so a closure written inside that captures it is held to rule 8.type Point { var x: Float } fn forEachPoint(var points: List<Point>, body: (var value: Point) => Void) { for index in 0..points.length() { body points[index] } } var points = [Point(1.0), Point(2.0)] const step = 0.5 forEachPoint points { var point => point.x = point.x + step } print points // prints [Point(x: 1.5), Point(x: 2.5)]A
varthe function type does not have is an error of the closure. A function value whose parameters differ from the expected ones only in avaris not of the expected type, and a note says which of the two takes the parameter asvar.Kompiliert nichtfn look(body: (value: Int) => Void) { body 1 } look { var number => number = 2 } // error: This parameter is `var` in the closure, but the function type takes it without `var`
What this is not#
A closure is not a block that runs inline. { print "hi" } on its own is a closure value, not a statement that
prints; call it ({ print "hi" }()) or pass it somewhere that calls it.
fn runTwice(action: () => Void) {
action()
action()
}
runTwice { print "hi" }{ print "hi" }
// error: This value is not usedThe closure is built and then thrown away: an unused value is a compile error (see Bindings), and a closure is a value like any other.
A local fn is not a closure. It captures nothing and sees only its own parameters and the top level of its file
(see Declaring a function); write a closure when the code has to reach
into the surrounding scope.
Related#
- Declaring a function -
fn, the declaration a closure cannot fully replace. - Trailing closures - writing a closure argument after the call it belongs to.
- Bindings - what
constandvardecide, which is what rules 6 and 7 build on. - Coming from Rust - closures here have no borrow checker to satisfy.