An anonymous function is written (params) => expr — a single un-annotated parameter
may drop the parentheses and be written x => expr, and a parameter annotation may be
left out wherever the type can be inferred. A function type is written
fn(A) -> B !e, where !e is its effect. A pure function's signature is enough to know
it has no side effects, and a test for one needs no mocks.
pub fn main() -> Unit !io = {
let nums = [1, 2, 3, 4]
let evens = filter(nums, n => n % 2 == 0)
let doubled = map(evens, n => n * 2)
println(to_string(doubled))
}
[4, 8]
A higher-order function forwards its arguments' effects through an effect variable:
the effect of map(f) is the effect of f. The union of two function parameters'
effects is written !(e1 | e2) — pure ∘ pure is still pure, and anything that touches
io is io.
fn compose[A, B, C](f: fn(A) -> B !e1, g: fn(B) -> C !e2) -> fn(A) -> C !(e1 | e2) =
a => g(f(a))
fn inc(x: Int) -> Int = x + 1
fn dbl(x: Int) -> Int = x * 2
pub fn main() -> Unit !io = {
let f = compose(inc, dbl)
println(to_string(f(10)))
}
22
A block as the last argument#
When a call's last argument is a function, it can follow the call as a bare block on the
same line: f(a) { e } is f(a, () => e). A block that takes parameters names them
before a =>, as a lambda does, one bare name or a parenthesised list:
fn twice(body: fn() -> Unit !e) -> Unit !e = {
body()
body()
}
pub fn main() -> Unit !io = {
twice {
println("hi")
}
let total = fold([1, 2, 3], 0) { (acc, x) => acc + x * x }
println("${total}")
let loud = map(["a", "b"]) { s => s ++ "!" }
println("${loud}")
}
hi
hi
14
["a!", "b!"]
The block fills the last parameter whatever came before it, which is why
fold(xs, 0) { ... } reads as "fold from 0, with this". It has to open on the same line
as the call; a { on the next line is a block statement of its own. In an if, while
or for header, and after match, the braces are the body and not an argument, so
parenthesise the call there: if (f(x) { ... }) { ... }. With named arguments and
defaults, it is how a function with many options and a body reads at the call site:
column(gap: 12) { ... }.
See also: spec.en.md §4.3, §4.5, §6