jonnifyelixir

F2 · Functional Programming

Higher-order functions

In a functional language, functions are values like any other. A higher-order function is one that takes a function as an argument or returns one — so you can pass behaviour around the way you pass a number or a list.

F1.07 met this idea in the abstract: the sum and product operators each took a function and applied it across a range. Here it is the everyday tool. Enum.map takes a function and runs it over a list; Enum.filter takes a test; Enum.reduce takes a combiner. And a function can build and return another function. Writing the iteration once and passing in the part that varies is how functional code stays small.

Passing a function

The clearest higher-order function takes another function as an argument. Enum.map is the canonical one: it walks a list and applies whatever function you hand it to each element. The list operation is fixed; the behaviour is a parameter. Swap the function and the same call does something new.

Choose the function passed to map.

higher-order function
a function that takes a function as an argument or returns one.
first-class
functions are values — they can be passed, stored, and returned.
capture operator
& makes a function value, e.g. &(&1 * 2) or &String.upcase/1.
function factory
a function whose result is another function.

Enum.map · the behaviour is an argument

Enum.map &(&1 * 2)


      
f = &(&1 * 2) · [1, 2, 3, 4] → [2, 4, 6, 8]

F1.07 · operators

Sum and product each took a function and applied it across a range.

Elixir

Enum.map(list, fun) applies fun to every element — the function is the argument.

One operation, many behaviours. The iteration is written once; the part that changes is a function you pass in.

One shape, many roles

Different higher-order functions expect functions of different shapes. map wants a transformer, one value to another; filter wants a test that returns true or false; reduce wants a combiner that folds an element into an accumulator; sort_by wants a key to compare on. The signature tells you the role.

Inspect what each one asks for.

The function each one expects

Enum.map(list, fun) the function it takes fun : a → b transform each element example &(&1 * 10) [3, 1, 2] → [30, 10, 20]


      
Enum.map takes a → b · a transformer · [3, 1, 2] → [30, 10, 20]

Idea

The combinator is generic; the function you pass gives it a specific job.

Elixir

transformer for map, predicate for filter, combiner for reduce.

Reading the signature of the function an operator wants tells you what the operator does — a transformer maps, a predicate filters, a combiner reduces.

Returning a function

A higher-order function can also return a function. A factory takes some value and hands back a new function that uses it. adder takes n and returns a function that adds n to its argument — the returned function carries n with it. Calling the factory with different values builds different functions.

Set the captured value, then apply the function it builds.

A function factory · build, then apply

5
3
BUILD n 5 adder fn n -> … returns a function fn x -> x + 5 end APPLY x 3 add5 8


      
adder.(5) returns add5 = fn x → x + 5 · add5.(3) = 8

Idea

A function that returns a function lets you fix one input now and the rest later.

Elixir

fn n -> fn x -> x + n end end — the inner function keeps n.

Returning a function captures a value for later use. That captured environment is the closure — the subject of F2.06.

What this lands

Higher-order functions follow from treating functions as values. Pass one into a combinator and the combinator is parameterised by behaviour: map transforms, filter tests, reduce combines — the same idea F1.07 drew as operators over a function. Return one and you have a factory that fixes an input now and the rest later, carrying that value in a closure. Both halves let you write the fixed structure once and vary the behaviour. F2.04 turns to the oldest way to express that structure over a collection: recursion, done so it runs in constant stack space.

Next: F2.04 — Recursion patterns & tail calls — accumulators and tail-call optimisation. The full chapter path is on the F2 overview.