Mojo Higher Order Functions

A higher-order function is a function that accepts another function as an argument, returns a function, or both. Higher-order functions let you express patterns like "apply this operation to every element" or "keep only elements that satisfy this condition" without writing repetitive loops. They make code shorter, more composable, and easier to reason about.

The Assembly Line Analogy

  Without higher-order functions:
    Loop 1: double every item
    Loop 2: filter items above 10
    Loop 3: sum the remaining items
    → Three separate loops, all similar boilerplate

  With higher-order functions:
    map(double, items)        → new list with every item doubled
    filter(above_10, items)   → new list with only large items
    reduce(add, items, 0)     → one number: the sum

  Each function is a specialized assembly station.
  You chain stations together to build a pipeline.

map — Apply a Function to Every Element

fn map_list(data: List[Int], func: fn(Int) -> Int) -> List[Int]:
    var result = List[Int]()
    for i in range(len(data)):
        result.append(func(data[i]))
    return result

fn double(x: Int) -> Int: return x * 2
fn square(x: Int) -> Int: return x * x
fn negate(x: Int) -> Int: return -x

fn main():
    var nums = List[Int](1, 2, 3, 4, 5)

    var doubled  = map_list(nums, double)
    var squared  = map_list(nums, square)
    var negated  = map_list(nums, negate)

    for i in range(len(doubled)):
        print(doubled[i], end=" ")   # 2 4 6 8 10
    print("")
    for i in range(len(squared)):
        print(squared[i], end=" ")   # 1 4 9 16 25
    print("")
map diagram:
  Input:  [1,  2,  3,  4,  5]
          │    │    │    │    │
        double double ... double
          │    │    │    │    │
  Output: [2,  4,  6,  8, 10]

filter — Keep Elements That Satisfy a Condition

fn filter_list(data: List[Int], predicate: fn(Int) -> Bool) -> List[Int]:
    var result = List[Int]()
    for i in range(len(data)):
        if predicate(data[i]):
            result.append(data[i])
    return result

fn is_even(x: Int) -> Bool: return x % 2 == 0
fn is_positive(x: Int) -> Bool: return x > 0
fn above_threshold(x: Int) -> Bool: return x > 3

fn main():
    var nums = List[Int](1, 2, 3, 4, 5, 6, 7, 8)
    var mixed = List[Int](-3, -1, 0, 2, 5, -7, 8)

    var evens    = filter_list(nums, is_even)
    var positives = filter_list(mixed, is_positive)
    var big      = filter_list(nums, above_threshold)

    for i in range(len(evens)):
        print(evens[i], end=" ")   # 2 4 6 8
    print("")
    for i in range(len(positives)):
        print(positives[i], end=" ")   # 2 5 8
    print("")
filter diagram:
  Input:  [1,  2,  3,  4,  5,  6,  7,  8]
  Test:   odd  even odd  even odd  even odd  even
  Keep:   ✗   ✓    ✗   ✓    ✗   ✓    ✗   ✓
  Output: [2,  4,  6,  8]

reduce — Collapse a List to One Value

fn reduce_list(data: List[Int], func: fn(Int, Int) -> Int, initial: Int) -> Int:
    var accumulator = initial
    for i in range(len(data)):
        accumulator = func(accumulator, data[i])
    return accumulator

fn add(a: Int, b: Int) -> Int: return a + b
fn multiply(a: Int, b: Int) -> Int: return a * b
fn max_of(a: Int, b: Int) -> Int: return a if a > b else b

fn main():
    var nums = List[Int](1, 2, 3, 4, 5)

    var total   = reduce_list(nums, add,      0)   # 15
    var product = reduce_list(nums, multiply, 1)   # 120
    var maximum = reduce_list(nums, max_of,   nums[0])   # 5

    print("Sum:", total)       # Sum: 15
    print("Product:", product) # Product: 120
    print("Max:", maximum)     # Max: 5
reduce with add, initial=0:
  Step 1: acc=0,  func(0,  1) = 1
  Step 2: acc=1,  func(1,  2) = 3
  Step 3: acc=3,  func(3,  3) = 6
  Step 4: acc=6,  func(6,  4) = 10
  Step 5: acc=10, func(10, 5) = 15
  Result: 15

Chaining map, filter, and reduce

fn main():
    var scores = List[Int](45, 72, 88, 33, 91, 65, 78)

    # Step 1: keep only passing scores (>= 60)
    var passing = filter_list(scores, fn(x: Int) -> Bool: return x >= 60)

    # Step 2: apply a 10% bonus to each passing score
    var bonused = map_list(passing, fn(x: Int) -> Int: return x + x // 10)

    # Step 3: find the total of bonused scores
    var total = reduce_list(bonused, fn(a: Int, b: Int) -> Int: return a + b, 0)

    print("Passing scores:", len(passing))   # 5
    print("Total after bonus:", total)
Pipeline diagram:
  [45,72,88,33,91,65,78]
       │
  filter(>=60)
       │
  [72, 88, 91, 65, 78]
       │
  map(+10%)
       │
  [79, 96, 100, 71, 85]
       │
  reduce(+)
       │
  431

apply_n — Repeat an Operation N Times

fn apply_n(func: fn(Int) -> Int, value: Int, times: Int) -> Int:
    var result = value
    for _ in range(times):
        result = func(result)
    return result

fn main():
    var doubled_5_times = apply_n(double, 1, 5)
    print(doubled_5_times)   # 32  (1→2→4→8→16→32)

    var squared_3_times = apply_n(square, 2, 3)
    print(squared_3_times)   # 256  (2→4→16→256)

Generic Higher-Order Function

fn apply_to_pair[T: AnyType, R: AnyType](
    a: T, b: T, func: fn(T, T) -> R
) -> R:
    return func(a, b)

fn main():
    fn sum_ints(x: Int, y: Int) -> Int: return x + y
    fn max_floats(x: Float64, y: Float64) -> Float64:
        return x if x > y else y

    print(apply_to_pair[Int, Int](10, 20, sum_ints))          # 30
    print(apply_to_pair[Float64, Float64](3.5, 7.2, max_floats)) # 7.2

Higher-Order Functions vs Explicit Loops

Explicit loop:                  Higher-order function:
  var result = List[Int]()        var result = map_list(data, double)
  for i in range(len(data)):
      result.append(data[i]*2)

Both produce the same output.
Higher-order functions:
  ✓ Less code to write and read
  ✓ Name the intent (map, filter, reduce) not the mechanism
  ✓ Reuse the same operation across different data
  ✓ Easy to chain into pipelines
  ✗ Slight overhead for very tight inner loops (use vectorize there)

Key Takeaways

A higher-order function takes another function as an argument or returns one. map transforms every element using a function. filter keeps only elements where a predicate returns true. reduce collapses a list to a single value by applying a combining function repeatedly. Chain these three patterns to build expressive data pipelines. Pass named functions or inline closures as arguments — Mojo treats both identically. Use higher-order functions for clarity on general data transformations and use vectorize for maximum performance on tight numerical loops.

Leave a Comment

Your email address will not be published. Required fields are marked *