Pipes and composition
Two small operators do most of the arranging in F#: `|>` sends a value into a function, and `>>` joins two functions into one that can be used later.
The pipe
x |> f means f x. The value goes in on the left and comes out of the function on the right. On its own that is no improvement; its worth arrives with a chain, where the order of reading matches the order of work: a list is taken, doubled, summed.
Without the pipe the same program is written inside out, because the innermost call happens first: List.sum (List.map double nums). The pipe works because a function takes its main argument last, which is why the helper that varies goes at the end of the parameter list.
The pipe is left-associative and has low precedence, so x |> f |> g is g (f x). Multi-line pipelines are idiomatic because each stage then starts at the same column, and the data reads down the page in the order it is transformed.
Composition makes a new function
>> joins two functions into one: double >> addOne is a function that doubles and then adds one. The pipe sends a value through a pipeline now; composition names the pipeline so it can be used later, handed to List.map, or applied to several values.
The other direction exists too: << composes right to left, so addOne << double is the same function as double >> addOne. This course writes >>, because the arrow then points the way the data travels.
A composition that does not type-check is a useful error: the compiler reports the output type of the left function and the input type of the right one, and the mismatch is usually a stage that returns a string where the next expects an int. Reading those two types is faster than reading both function bodies.
The same work, sent or named
let double n = n * 2
let addOne n = n + 1
let bump = double >> addOne
let sent = 5 |> double |> addOne
let named = bump 5Pipes, then the same pipes as values
Run it and follow `chain 11` through the two functions. Then build `let shout = upper >> exclaim` with the operators the other way round and check that the output does not change.
A one-line lambda in a pipe is often a place where a named function would read better: List.map double says what it does, while List.map (fun n -> n * 2) says how. Prefer the name when you have one.
The value goes last
The pipe is application written the other way round: x |> f is f x. For a chain to work, each function has to be waiting for the value as its last argument, which is why the standard library puts the list last in List.map, List.filter and List.fold. The function and the predicate come first because they describe the job; the data arrives at the end.
That order is a convention you can use in your own code. let clamp low high value = min high (max low value) can be written as value |> clamp 0 100, while a version that takes the value first would need a lambda at every call site. Put the thing that varies last and partial application does the rest.
The pipe is not magic and it is not faster. It is a way of arranging the same calls so the reader follows the data down the page instead of unpacking nested brackets from the inside out.
A pipeline with a name
Composition is the same idea with the value left out. f >> g builds a function that applies f and then g, so (f >> g) x is g (f x). The input type of g has to match the output type of f, or the composition does not type-check. Reading the arrows is a quick way to check that the stages fit, because each output has to be the next input.
Naming the pipeline is useful when it will be used more than once: let slug = clean >> lower >> dash can be handed to List.map, stored in a binding, or applied directly. The pipe is for one value now; composition is for the same trip later. That difference matters at a call site: a named pipeline can be passed where a function is expected, while a pipe is already committed to a value.
A pipe with a value on the right, as in x |> 5, is not a function call. A function that still needs two arguments cannot be piped into directly, so use a lambda or supply the other argument first. And double >> addOne 5 is read as double >> (addOne 5), not as applying the composed function, so keep the brackets.
Try these next
- Write
let clamp low high value = min high (max low value)and use it in a pipe with the limits supplied first. - Write
first,upperandtakeTwoas named stages, compose them intoinitials, then map it over three names. - Write the same three-stage pipeline once with
|>and once with>>, and check that the answers match.
A text pipeline with named stages
Run it and trace the spaces in the first argument through `clean`, `lower` and `dash`. Then add a stage that removes a leading dash, and a second name to the list that is only two letters long.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does 5 |> double |> addOne answer, with double n = n * 2 and addOne n = n + 1?
