jonnifyfsharp

Dive C1.07.2

Transforming collections

A handful of functions that each return a new collection — and chain into a pipeline.

Most work on a collection is a transformation: square every element, keep the even ones, add them up. The List module supplies a function for each — List.map, List.filter, List.fold, List.sum — and every one of them returns a new collection and leaves the input untouched. Because each takes the collection last, they line up with the pipe operator |> into a left-to-right pipeline. The same function names live in the Array and Seq modules, so a pipeline written for a list reads the same for an array.

C1.07.2 · part 1

map, filter, sum

List.map applies a function to every element and returns the list of results. List.filter keeps the elements for which a predicate returns true. List.sum adds a list of numbers. None of them mutates the input — each produces a fresh list.

let nums = [1; 2; 3; 4; 5]

nums |> List.map (fun x -> x * x)        // val it : int list = [1; 4; 9; 16; 25]
nums |> List.filter (fun x -> x % 2 = 0)   // val it : int list = [2; 4]
nums |> List.sum                          // val it : int = 15
List.map
apply a function to every element: ('a -> 'b) -> 'a list -> 'b list.
List.filter
keep elements where the predicate is true: ('a -> bool) -> 'a list -> 'a list.
List.sum
add a list of numbers, returning a single value.
non-mutating
each returns a new list; nums is still [1; 2; 3; 4; 5] after.
C1.07.2 · part 2

fold, and the pipeline

List.fold is the general one: it threads an accumulator through the list, combining each element with what came before. List.sum is a fold with (+) and a starting value of 0. Because every function takes its collection last, they compose with |>: the output of one is the input of the next, read top to bottom.

List.fold (+) 0 [1..5]                // val it : int = 15   sum, written as a fold

[1; 2; 3; 4; 5]
|> List.map (fun x -> x * x)              // [1; 4; 9; 16; 25]
|> List.sum                                 // val it : int = 55

List.collect maps each element to a list and concatenates the results; List.sortBy returns a new list ordered by a key. Both follow the same rule — a new collection out, the input unchanged — so both drop into a pipeline beside map and filter.

Run the list through stages

Toggle the stages below. Each enabled stage runs in order on the fixed list [1; 2; 3; 4; 5], and the readout shows the list after each one and the final result. Every combination here was confirmed in dotnet fsi: map squares, filter keeps the even elements, sum adds them. With all three on, the pipeline returns 20.

[1; 2; 3; 4; 5] · through the enabled stages

source [1;2;3;4;5] |> map x*x [1;4;9;16;25] |> filter even [4;16] |> sum 20
  1. source [1; 2; 3; 4; 5]
  2. map x*x [1; 4; 9; 16; 25]
  3. filter even [4; 16]
  4. sum 20
pipeline [1;2;3;4;5] |> List.map (fun x -> x*x) |> List.filter (fun x -> x%2=0) |> List.sum = 20

A pipeline is functions in a row, each handing a new collection to the next. Nothing was mutated; the list flowed through, and a value fell out the end.

Transform

Each function returns a new collection and leaves the input alone — so they can be lined up without surprises.

F# pipeline

The pipe |> threads the collection through map, filter, sum in reading order.

Where this leads

Lists, arrays, and sequences are ordered: they answer "give me the elements in turn". The last dive of the module adds the two containers that answer a different question — Map, for "what value is stored under this key", and Set, for "is this element a member". Both are immutable, and both carry their own map, filter, and fold too.

References

Sources

Related in this course