Dive C1.04.3
Building pipelines
Chain the collection functions with the pipe — filter, map, sum — and the data falls through in order.
A real pipeline is a sequence of stages, each a function from a collection to a collection (or
to a single value at the end), wired together with the pipe. The list [1..10] flows
into List.filter, the survivors into List.map, and the
mapped list into List.sum. Written with |>, each stage sits
on its own line in the order it runs. When the same pipeline is needed by name, composition
>> builds it point-free. This dive walks a three-stage pipeline over
[1..10] and names it two ways.
Filter, map, sum
The pipeline below takes the numbers one to ten, keeps the even ones, squares each, and adds them up. Each
|> line is one stage; the comment shows the list as it leaves that stage. The
final stage List.sum collapses the list to a single int.
let isEven x = x % 2 = 0 let square x = x * x [1..10] |> List.filter isEven // [2; 4; 6; 8; 10] |> List.map square // [4; 16; 36; 64; 100] |> List.sum // val it : int = 220
The shape is general: each stage takes the collection and returns the next, so the stages compose into a path the data follows top to bottom. Reorder a stage and the result changes; add a stage between two others and it slots in. The pipe keeps the page in step with the work.
- List.filter
- keeps the elements that satisfy a predicate:
isEvenleaves[2; 4; 6; 8; 10]. - List.map
- applies a function to every element:
squaregives[4; 16; 36; 64; 100]. - List.sum
- adds the elements to one value: the squares sum to
220. - stage
- a function from collection to collection (or, at the end, to a scalar); the pipe chains them.
Toggle the stages over [1..10]
Enable each stage and watch the list change. The input is the fixed list [1..10].
Turn on filter even, then map square, then sum; the readout
shows the list after each enabled stage and the value at the end. With all three on, the pipeline is the one
above and the result is 220.
[1..10] |> filter even |> map square |> sum
Three stages, one list, one number out. Toggle a stage and the data takes a different path — the pipeline is exactly the sequence of stages you switch on.
Pipe the data, or name the pipeline
The pipeline above has the data in hand, so the pipe is the natural fit. To reuse the pipeline — to apply
it to several lists, or pass it to another function — give it a name with composition. Each stage is now a
partially applied collection function, and >> glues them into one. The two
forms produce the same 220.
// the same pipeline, named point-free with composition let sumOfSquaresOfEvens = List.filter isEven >> List.map square >> List.sum // val sumOfSquaresOfEvens : (int list -> int) sumOfSquaresOfEvens [1..10] // val it : int = 220
The rule of thumb: pipe when you have the data, compose to name a reusable pipeline. The piped form reads as a one-off transformation of a list you hold; the composed form is a function you can hand around. Both are built from the same stages, and both land on the same answer.
Pipe the data
[1..10] |> List.filter isEven |> List.map square |> List.sum — the list is in hand.
Name the pipeline
List.filter isEven >> List.map square >> List.sum — one reusable function.
Where this leads
The pipe-and-compose pair is the spine of idiomatic F#: from here on, transformations read as pipelines and reusable transformations read as composed functions. The pattern stands on the functions of the previous module, and it returns in every later one — pattern matching, collections, and the algorithm pipelines of chapter C3 all wire their stages this way.
References
Sources
- Microsoft — F# documentation — the pipe-forward operator and idiomatic data pipelines.
- FSharp.Core API reference —
List.filter,List.map, andList.sum. - The F# Language Specification — the operators and the list module functions.
Related in this course
- C1.04.2 — Function composition — the point-free naming this dive uses.
- C1.04 — The pipe & composition — the module hub and its three dives.
- Elixir — Composition — the same pipelines on the BEAM.