Docs/Language basics

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):

Valk
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:

Valk
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#

Valk
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:

Valk
class Counter {
    count: int (0)
}

fn main() {
    let counter = Counter{}
    let bump = fn() { counter.count++ }
    bump()
    bump()
    println(counter.count)  // 2
}

Methods as functions#

Valk
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:

Valk
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:

Valk
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:

Valk
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.