Chapter 11 — Functions as Values, and Closures
Functions in Ingle are values. You can put one in a binding, pass it to another function, and
call it later. A function type is written fn(ArgTypes) -> ReturnType.
fn double(x: int) -> int { return x * 2 }
fn apply(f: fn(int) -> int, x: int) -> int {
return f(x)
}
fn main() -> int {
let g = double // a named function, held as a value
return apply(double, 5) + g(7) // 10 + 14 = 24
}
=> 24
apply takes a function as its first argument — any fn(int) -> int will do — and calls it.
That’s the foundation of every “do this to each element” operation.
Lambdas
A lambda is a function written inline, with |params| expr (or |params| { ... } for a
block). Its parameter types are inferred from where it’s used:
fn main() -> int {
return apply(|x| x + 100, 5) // the lambda is an fn(int) -> int, inferred from apply
}
=> 105
A lambda can capture variables from around it — and it does so by value, copying them in when the lambda is created. That means it can never dangle, and there are no lifetime puzzles:
fn main() -> int {
let n = 100
let add_n: fn(int) -> int = |x| x + n // captures n (by value)
return add_n(5) // 105
}
=> 105
The one rule that will trip you up. A lambda needs to know its types, and it learns them from context — either by being passed straight into a function that expects a function type, or from an explicit annotation on the binding. So
apply(|x| x + 1, 5)is fine (the context isapply’s parameter), andlet f: fn(int) -> int = |x| x + 1is fine (the annotation). But a bare, unannotatedlet f = |x| x + 1is an error — Ingle can’t guess whatxis. The compiler says so plainly: “a lambda needs a function-typed context.” When in doubt, annotate the binding or pass the lambda directly.
Two more honest limits: capturing is read-only (a closure can’t reassign a variable it captured), and you can capture scalars, strings, and enums, but not a struct or an array (that would alias a unique owner — pass it as a parameter instead).
Higher-order functions, the standard-library way
Put functions-as-values together with generics and you get the classic trio, which live in
std/list (more on imports in Chapter 15):
import "std/list" as list
fn main() -> int {
let xs = [1, 2, 3, 4, 5]
let evens = list.filter(xs, |n| n % 2 == 0) // [2, 4]
let doubled = list.map(evens, |n| n * 2) // [4, 8]
let sum = list.reduce(doubled, 0, |acc, n| acc + n) // 12
return sum
}
=> 12
There’s a list.sort too, which takes a “is a before b?” lambda:
import "std/list" as list
fn main() -> int {
let words = ["ccc", "a", "bb"]
let sorted = list.sort(words, |a, b| a.len() < b.len()) // ["a", "bb", "ccc"]
for w in sorted { println(w) }
return sorted.len()
}
a
bb
ccc
=> 3