8. Lambdas and the effect system

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))
}
Open in playground
[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)))
}
Open in playground
22