Skip to content
Noodle
InstallLearnPlayground
GitHub

Higher-order functions

You do not need function values to complete the Language basics path. Reach for them when an algorithm should receive behavior as data, return a configured operation, or build a reusable transformation pipeline.

A function type lists its parameter types and result:

(Int) -> String
(Int, Int) -> Int
() -> Unit

Parameter names may be included as documentation, but they do not affect type identity:

(value : Int) -> String

A parameter with a function type accepts a named function or lambda with the same boundary:

func apply_twice(transform : (Int) -> Int, value : Int) -> Int do
transform(transform(value))
end
func increment(value : Int) -> Int do
value + 1
end
func main() -> Unit do
Debug.trace(apply_twice(increment, 10));
end

The function value is called with ordinary call syntax. Arguments and results are still checked statically.

A lambda is an anonymous function expression:

func apply_twice(transform : (Int) -> Int, value : Int) -> Int do
transform(transform(value))
end
func main() -> Unit do
doubled = apply_twice(|value : Int| value * 2, 3);
Debug.trace(doubled);
end

Lambda parameter annotations may be inferred when the expected function type already determines them. Add the annotation when the surrounding expression does not provide enough information or when it makes the boundary clearer.

A lambda may capture a value from its defining scope:

func make_adder(amount : Int) -> (Int) -> Int do
|value| value + amount
end
func main() -> Unit do
add_three = make_adder(3);
add_ten = make_adder(10);
Debug.trace(add_three(7));
Debug.trace(add_ten(7));
end

Each returned closure retains its own amount. Capturing a mut local shares that mutable storage location; capturing an ordinary binding retains its value.

Function values can be combined like other values:

func compose(
outer : (Int) -> Int,
inner : (Int) -> Int,
) -> (Int) -> Int do
|value| outer(inner(value))
end
func main() -> Unit do
add_two : (Int) -> Int = |value| value + 2;
triple : (Int) -> Int = |value| value * 3;
transform = compose(triple, add_two);
Debug.trace(transform(4));
end

Lambdas are anonymous and do not become recursive merely because they are stored in a binding. Use a named local function when recursion is required.

Generic higher-order functions remove the fixed Int boundary used here. Continue with Generic programming.