Closures
Pass functions around and capture surrounding values.
On this page
Functions are values: you can store them in variables, pass them to other functions and return them. An anonymous function can capture values from where it is created.
Function types#
A function type is written fn(parameter types)(result types):
fn double(value: int) int {
return value * 2
}
fn apply_twice(value: int, transform: fn(int)(int)) int {
return transform(transform(value))
}
fn main() {
println(apply_twice(3, double)) // 12
let f: fn(int)(int) = double
println(f(5)) // 10
}
| Type | Accepts |
|---|---|
fn()() |
No arguments, no result |
fn(String)(bool) |
A String, returns bool |
fn(int, int)(int, int) |
Two values, returns two |
fn(String)(int !SyntaxError) |
Returns an int or throws |
?fn()() |
A function or null |
Anonymous functions#
fn(...) { ... } without a name creates a function in place:
fn main() {
let add = fn(a: int, b: int) int { return a + b }
println(add(2, 3)) // 5
let numbers = Array[int]{ 3, 1, 2 }
let doubled = numbers.map(fn(n) { return n * 2 })
println(doubled.join(", ")) // 6, 2, 4
}
Where the function type is already known, such as an argument or a typed variable, the parameter and result types can be left out: fn(n) { return n * 2 }.
Capture values#
fn make_adder(amount: int) fn(int)(int) {
return fn(value: int) int { return value + amount }
}
fn main() {
let add5 = make_adder(5)
println(add5(1)) // 6
let prefix = "Hello"
let greet = fn(name: String) String { return "%prefix, %name" }
prefix = "Goodbye"
println(greet("Ada")) // Hello, Ada
}
A closure copies the current value of each variable it uses when it is created. Changing the variable afterwards does not affect the closure, and a closure cannot assign to a captured variable.
Share state through an object#
To let a closure change something, capture an object and change its properties:
class Counter {
count: int (0)
}
fn main() {
let counter = Counter{}
let bump = fn() { counter.count++ }
bump()
bump()
println(counter.count) // 2
}
Methods as functions#
class Greeter {
prefix: String
fn greet(name: String) String { return this.prefix + name }
}
fn main() {
let greeter = Greeter { prefix: "Hi " }
let bound = greeter.greet // greeter is remembered
println(bound("Ada")) // Hi Ada
let unbound = Greeter.greet // receiver becomes the first argument
println(unbound(greeter, "Sam")) // Hi Sam
}
Store callbacks#
Function values can live in properties and collections:
class Button {
on_click: ?fn()() (null)
fn click() {
let handler = this.on_click
if isset(handler) : handler()
}
}
fn main() {
let button = Button {}
button.on_click = fn() { println("clicked") }
button.click() // clicked
let steps = Array[fn()()]{}
each 0 .. 3 as i {
steps.append(fn() { print(i) })
}
each steps as step : step()
println("") // 012
}
Callbacks that can fail#
Add the error type to the result list. The caller handles the error as with any call:
fn total(inputs: Array[String], parse: fn(String)(int !SyntaxError)) int {
let sum = 0
each inputs as input {
sum += parse(input) !? 0
}
return sum
}
fn main() {
let parse = fn(text: String) int !SyntaxError { return text.to(int) !> }
println(total(.{ "1", "2", "x" }, parse)) // 3
}
Exact signatures#
A function value must match the expected type exactly; argument and result types are not converted. Wrap it when they differ:
fn count() int { return 7 }
fn main() {
let optional: fn()(?int) = fn() ?int { return count() }
println(optional() ?? 0) // 7
}
For C callbacks, the raw fnptr(...)(...) type takes functions that capture nothing; see C interop.
