Delegation

A wise manager delegates. Rather than doing every task themselves, they say: "Here is the problem. Here is someone who knows how to solve it. Let them handle the details."

Programming has the same pattern. Instead of writing detailed loops and conditionals for every operation, we can delegate to functions that handle the mechanics while we focus on the specifics.

These delegating functions are called higher-order functions. They are among the most powerful abstractions in programming.

The Pattern of Iteration

Consider how often we do something like this:

python
    // Double every number in a list
    result = []
    for each x in numbers:
        result.append(x * 2)
    return result

And this:

python
    // Get the name of every player
    result = []
    for each player in players:
        result.append(player.name)
    return result

And this:

python
    // Square every number
    result = []
    for each x in numbers:
        result.append(x * x)
    return result

The pattern is always the same:

The only thing that changes is step 3---the transformation. Everything else is boilerplate.

  1. Start with a collection
  2. Go through each element
  3. Transform it somehow
  4. Collect the results

Map: Delegation of Transformation

What if we could say: "Apply this transformation to every element"---without writing the loop ourselves?

javascript
    function map(collection, transform):
        result = []
        for each item in collection:
            result.append(transform(item))
        return result

Visually, map is an assembly line---each element passes through the same machine:

map: every element passes through the same transformation.

Now our three examples become:

python
    doubled = map(numbers, (x) => x * 2)
    names = map(players, (p) => p.name)
    squared = map(numbers, (x) => x * x)

Each line says what we want, not how to iterate. We delegate the mechanics to map and focus on the transformation.

Filter: Delegation of Selection

Another common pattern is selecting items that match some criterion:

python
    // Get all even numbers
    result = []
    for each x in numbers:
        if x is even:
            result.append(x)
    return result

Again, the structure is always the same. Only the condition changes. We can abstract this:

javascript
    function filter(collection, predicate):
        result = []
        for each item in collection:
            if predicate(item):
                result.append(item)
        return result
filter: a gate that only lets matching elements through.

Now:

python
    evens = filter(numbers, (x) => x is even)
    adults = filter(people, (p) => p.age >= 18)
    active = filter(games, (g) => not g.is_finished)

Reduce: Delegation of Combination

Sometimes we want to combine all elements into a single result:

python
    // Sum all numbers
    total = 0
    for each x in numbers:
        total = total + x
    return total
python
    // Find the maximum
    best = first element
    for each x in rest:
        if x > best:
            best = x
    return best

The pattern: start with an initial value, combine each element with the accumulated result. We can abstract:

javascript
    function reduce(collection, combine, initial):
        accumulated = initial
        for each item in collection:
            accumulated = combine(accumulated, item)
        return accumulated
reduce: elements are combined one by one with an accumulator, producing a single result.

Now:

python
    sum = reduce(numbers, (a, b) => a + b, 0)
    product = reduce(numbers, (a, b) => a * b, 1)
    max = reduce(numbers, (a, b) => bigger(a, b), first)

Composition: Chaining Delegations

The real power emerges when we combine these operations:

    // Get the total score of all active players over level 10
    
    active = filter(players, (p) => p.is_active)
    experienced = filter(active, (p) => p.level > 10)
    scores = map(experienced, (p) => p.score)
    total = reduce(scores, (a, b) => a + b, 0)

Or, written as a chain:

elixir
    total = players
        |> filter((p) => p.is_active)
        |> filter((p) => p.level > 10)
        |> map((p) => p.score)
        |> reduce((a, b) => a + b, 0)
A transformation pipeline: data flows through stages, narrowing and transforming at each step.

This reads like a description of what we want:

  1. Take all players
  2. Keep only the active ones
  3. Keep only those over level 10
  4. Get their scores
  5. Sum them up

No loops. No intermediate variables. Just a declaration of intent.

Back to Chess

Let's apply these patterns to our chess game.

haskell
    // Get all squares where white has pieces
    white_squares = filter(
        all_squares, 
        (sq) => board[sq] is white piece
    )
    
    // Get all possible moves for white
    all_moves = flatMap(
        white_squares,
        (sq) => get_moves_from(sq, board)
    )
    
    // Keep only legal moves (don't leave king in check)
    legal_moves = filter(
        all_moves,
        (move) => not leaves_king_in_check(move, board)
    )

Compare this to writing nested loops with conditionals. The delegating style:

  • Is more readable (each step has a clear purpose)
  • Is more composable (easy to add or remove steps)
  • Separates what from how

"Tell Me What, Not How"

Higher-order functions embody a philosophy: tell me what you want, not how to do it.

When we write:

    evens = filter(numbers, (x) => x is even)

We're saying what we want: the even numbers. We're not saying:

  • Create an empty list
  • Start at the beginning
  • Check each element
  • If it's even, add it
  • Move to the next
  • Continue until done

The "how" is delegated to filter. We trust it to iterate correctly. We focus on what matters: the criterion for selection.

This separation of concerns makes code:

  • Easier to read: Intent is clear
  • Easier to change: Steps are encapsulated
  • Easier to optimize: filter can be improved once, benefiting all uses

The Cost of Abstraction

Higher-order functions aren't free. They introduce:

But for most programs, the benefits far outweigh these costs. The code is shorter, clearer, and less prone to off-by-one errors and forgotten edge cases.

  • Indirection: You must understand what map and filter do
  • New vocabulary: Readers must learn these patterns
  • Sometimes less efficient: Creating intermediate collections

Try It Yourself

Try It Yourself

9 exercises to practice

We've learned to name transformations (functions), treat them as values (first-class functions), and delegate iteration patterns to higher-order functions (map, filter, reduce).

But so far, our values have been unstructured. A number is a number. A string is a string. In the next part, we explore how to categorize and constrain values---introducing the concept of types.