Skip to content

06 · Functions

Higher-Order Functions

Functions as argument or return, composition, map/filter/reduce, and closures with mutable state.

On this page

A higher-order function receives or returns another function. In Zolo, the type fn(T) -> R describes a functional value. This allows abstracting the how of an operation without repeating the what:

apply_twice, factory multiplier, compose, and the classic map/filter/reduce.

06-higher-order.zolo
Playground
// Feature: Higher-order functions

// Syntax: `fn f(g: fn(T) -> R)` — function as argument or return value.

// When to use: abstract the "how" over the "what" — strategy

// pattern, callbacks, decorators, pipeline transformations.


use std::Array

// Function takes a function and applies it twice.

fn apply_twice(f: fn(int) -> int, x: int) -> int {
  return f(f(x))
}

fn increment(x: int) -> int {
  return x + 1
}

fn square(x: int) -> int {
  return x * x
}

print(apply_twice(increment, 5))  // 7

print(apply_twice(square, 3))  // 81


// Function that RETURNS another function (factory).

fn multiplier(factor: int) {
  return |x| x * factor
}

let triple = multiplier(3)
let quad = multiplier(4)
print(triple(7))  // 21

print(quad(7))  // 28


// Composition: `compose(f, g)(x) == f(g(x))`.

fn compose(f: fn(int) -> int, g: fn(int) -> int) {
  return |x| f(g(x))
}

let inc_then_square = compose(square, increment)
print(inc_then_square(5))  // (5+1)^2 = 36


// Map/filter/reduce — bread-and-butter of higher-order.

let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

let doubled = nums.map(|x| x * 2)
let evens = nums.filter(|x| x % 2 == 0)
let sum = nums.reduce(|acc: int, x: int| acc + x, 0)

print(doubled)  // [2, 4, ..., 20]

print(evens)  // [2, 4, 6, 8, 10]

print(sum)  // 55

When a closure captures a let mut from the outer scope, it can accumulate state between calls. Each call to the factory function creates an independent instance — the lightweight equivalent of an object with a single method:

Counter, running-average accumulator, and the once pattern (runs only on the first call).

08-stateful-closures.zolo
Playground
// Feature: Stateful closures — closures with mutable state
// Syntax: a closure captures `let mut` from the outer scope
// and modifies that value across calls.
// When to use: counters, lightweight generators, "memory"
// without creating a struct, factories of incrementers.

// Classic counter — each call increments.
fn make_counter() {
  let mut count = 0
  return |step| {
    count = count + step
    return count
  }
}

let counter = make_counter()
print(counter(1))  // 1
print(counter(1))  // 2
print(counter(5))  // 7

// Counters are INDEPENDENT — each has its own `count`.
let c1 = make_counter()
let c2 = make_counter()
c1(10)
c1(10)
c2(1)
print(c1(0))  // 20 (accumulated 10+10)
print(c2(0))  // 1

// Accumulator with memory.
fn make_accumulator() {
  let mut total = 0
  let mut hits = 0
  return |x| {
    total = total + x
    hits = hits + 1
    return total / hits  // running integer average
  }
}

let avg = make_accumulator()
print(avg(10))  // 10
print(avg(20))  // 15
print(avg(30))  // 20
print(avg(40))  // 25

// "Once" — closure that only runs on the first call.
fn make_once(f: fn() -> int) {
  let mut done = false
  let mut result = 0
  return || {
    if !done {
      result = f()
      done = true
    }
    return result
  }
}

let init = make_once(|| 42)
print(init())  // 42
print(init())  // 42 (cached)

Challenge

Use make_counter from the stateful-closures example to create two independent counters, advance them at different rates, and verify that the values do not mix.

Global index

Find your way through Zolo

Try an idea

Start here

9 results

9 results

enespt-br