Pipelines and Composition
Prerequisites: Functions
The pipe operator |> passes the value on its left as the last
argument of the function on its right. x |> f means f x:
let double x = x * 2
let increment x = x + 1
printfn "%d" (increment (double 5)) // nested calls: read inside-out
5 |> double |> increment |> printfn "%d" // pipeline: read left to right
11
11
Pipelines read in the order things happen. They are the backbone of idiomatic F#, especially with collections:
[1 .. 10]
|> List.filter (fun n -> n % 2 = 0)
|> List.map (fun n -> n * n)
|> List.sum
|> printfn "Sum of even squares: %d"
Sum of even squares: 220
Each line takes the previous result: keep the even numbers, square them,
add them up, print. Putting each step on its own line starting with |>
is the standard style.
Composition
The composition operator >> joins two functions into a new one:
f >> g means "do f, then g".
let double x = x * 2
let increment x = x + 1
let doubleThenIncrement = double >> increment
let incrementThenDouble = increment >> double
printfn "%d" (doubleThenIncrement 5)
printfn "%d" (incrementThenDouble 5)
let normalize = (fun (s: string) -> s.Trim()) >> (fun s -> s.ToLower())
printfn "[%s]" (normalize " HeLLo ")
11
12
[hello]
|>applies functions to a value now.>>builds a new function to use later.<|and<<are the backward versions;<|is occasionally handy to avoid parentheses (printfn "%d" <| square 4),<<is rare.
Why the last argument?
|> fills in the last parameter, so F# library functions take the
data last: List.map f list, String.replicate 3 "ab". Design your own
functions the same way — configuration first, data last — and they'll
fit into pipelines.
Practice
Starting from the list [" apple "; "Banana"; " cherry"], write a
pipeline that trims each word, converts it to upper case, joins them with
" | ", and prints the result.
Show solution
[" apple "; "Banana"; " cherry"]
|> List.map (fun s -> s.Trim().ToUpper())
|> String.concat " | "
|> printfn "%s"
APPLE | BANANA | CHERRY