jonnifyfsharp

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.

C1.04.3 · part 1

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: isEven leaves [2; 4; 6; 8; 10].
List.map
applies a function to every element: square gives [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

[1..10] filter isEven |> map square |> sum
all three on · [1..10] |> filter isEven |> map square |> sum = 220

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.

C1.04.3 · part 2

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

Related in this course