Functions & Closures

Functions & Closures

Weave has first-class functions, closures, and lambdas. Functions can be passed as arguments, returned from other functions, and stored in containers.

Defining Functions

fn add(a, b) {
    a + b    # last expression is implicitly returned
}

result = add(2, 3)    # 5

Default Parameters

Required parameters must come first:

fn greet(name, greeting: "Hello") {
    greeting + ", " + name + "!"
}

greet("Alice")                            # "Hello, Alice!"
greet("Bob", "Hi")                        # "Hi, Bob!"
greet(greeting: "Hey", name: "Carol")     # "Hey, Carol!"

Named Arguments

All arguments can be passed by name using the same key: value syntax as containers:

fn div(a, b) { a / b }

div(b: 3, a: 2)    # 0.6666...

Early Return

fn find_first(items, target) {
    i = 0
    while i < items.len {
        if items[i] == target { return i }
        i += 1
    }
    false  # this lets caller run "if find_first(...)" 
}

Lambdas

Lambdas are anonymous functions declared with ^:

double = ^(x) { x * 2 }
double(5)    # 10

# Multi-line
process = ^(x) {
    x = x + 1
    x * 2
}

# Most commonly used inline with pipelines
[1, 2, 3] *> ^(x) { x * 2 }    # [2, 4, 6]

Lambdas are scoped to their declaring block:

fn outer(a, b) {
    add = ^(a, b) { a + b }
    add(a + 1, b + 1)
}
# add is not accessible here

Variadic Arguments

The *args syntax collects remaining arguments into a container:

fn add(a, b) { a + b }

fn partial(func, arg1) {
    ^(*args) { func(arg1, *args) }
}

add_5 = partial(add, 5)
add_5(10)    # 15

Closures

Functions capture variables from their enclosing scope. The captured values persist even after the enclosing function returns:

fn make_adder(n) {
    a = 1
    ^(x) { x + n + a }    # captures n and a
}

add_6 = make_adder(5)
add_6(10)    # 16

Factory Functions

Closures are the idiomatic way to build configurable functions:

fn make_multiplier(factor) {
    ^(x) { x * factor }
}

fn make_filter(predicate) {
    ^(items) {
        result = []
        items *> ^(item) {
            if predicate(item) { result << item }
        }
        result
    }
}

triple = make_multiplier(3)
filter_positive = make_filter(^(x) { x > 0 })

[1, 2, 3] *> triple                    # [3, 6, 9]
[-2, -1, 0, 1, 2] |> filter_positive   # [1, 2]

Partial Application

Create specialized functions by pre-filling arguments:

fn partial(func, arg1) {
    ^(*args) { func(arg1, *args) }
}

fn multiply(factor, value) { value * factor }
fn add(amount, value) { value + amount }

triple = partial(multiply, 3)
add_100 = partial(add, 100)

# Works naturally in pipelines
[1, 2, 3] *> triple *> add_100    # [103, 106, 109]

Configurable Pipeline Components

fn column_extractor(column_name) {
    ^(row) { row[column_name] }
}

fn sum(v, acc: 0) { acc + v }

get_amount = column_extractor(:amount)
get_quantity = column_extractor(:quantity)

sales = read("sales.csv", :csv)
total_amount = sales *> get_amount &> sum
total_items = sales *> get_quantity &> sum