Functions and closures
A function declaration needs a type on every parameter, and a return type if it returns a value. There are no default arguments, no named arguments and no variadic functions.
uses print
fn makeAdder(base: Int): fn(Int): Int { return fn(n: Int): Int { return base + n }}
fn apply(f: effect fn(String), text: String) { effect f(text)}
constant addFive = makeAdder(5)
effect print("${addFive(1)} ${addFive(10)}")
apply(print, "direct call")
apply( fn(s) { effect print("wrapped: ${s}") }, "hi")Output:
6 15direct callwrapped: himakeAdder returns a closure, an anonymous function value that reads base from the scope it was created in. A closure parameter can often skip its own type annotation, as s does above, when the expected function type is already known from context, here the f: effect fn(String) parameter of apply. See section 5.3.
apply’s first parameter is effect fn(String), an effectful function type, so the body has to mark its own call with effect, and a caller can hand it either a real capability (print) or a closure that happens to call one internally. A plain fn(String) parameter could not accept print at all: pure and effectful are different types, and only pure is assignable to effectful, never the other way round. That is how a function signature tells you, just by its type, whether it can possibly reach the outside world. See section 5.5 and the effects chapter.
Capture and fresh bindings
Section titled “Capture and fresh bindings”A closure shares its captured bindings with the code around it: it sees them by reference, not a copy taken when the closure was created. A closure that assigns a variable changes what the enclosing code, and every other closure over that binding, sees:
uses print
fn makeCounter(): fn(): Int { variable n = 0 return fn(): Int { n = n + 1 return n }}
constant next = makeCounter()
effect print("${next()} ${next()} ${next()}")Output:
1 2 3Each execution of a declaration creates a new binding, though, so a closure created inside a loop does not share the loop variable with closures from other iterations:
uses print
variable makers: [fn(): Int] = []
for i in range(0, 3) { makers = makers.append(fn(): Int { return i * 10 })}
constant results = makers.map(fn(make) { return make()})
effect print(results.map(fn(n) { return "${n}"}).join(","))Output:
0,10,20If i were shared across iterations, this would print 20,20,20. See section 5.4 for the precise rule, including what a closure is and is not allowed to assign.