Skip to content

Higher-Order Functions

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, x| 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.

enespt-br