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.
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;
numsis still[1; 2; 3; 4; 5]after.
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
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
- FSharp.Core API reference — the
Listmodule:map,filter,fold,sum,collect,sortBy. - Microsoft — F# documentation — working with lists and the collection functions.
- The F# Language Specification — the pipe operator and function application.
Related in this course
- C1.03 — Functions — the functions that
mapandfiltertake as arguments. - C1.07 — Collections — the module hub and its three dives.
- Elixir — Folds — the same map, filter, and reduce on the BEAM.