The best way to understand it is with an example. Imagine you have list of numbers and you want to find out the total. In imperative programming you could do this:
let numbers = [ 19 ; 52 ; 35 ; 27]
let mutable total = 0
for n in numbers do
total <- total + n
printfn "%A" total // 133
it is a good solution but not functional. Lets use List.fold
to do the same:
numbers
|> List.fold (fun total n -> total + n) 0
|> printfn "%A" // 133
voila! If you squint you can recognize the same elements in both solutions.
- Your state:
total
- Your folder operation that takes the prior state and an element of the list and returns a new state:
total + n
- Your initial value:
0
Lets look at the signature of List.fold
:
val List.fold:
folder: 'State -> 'T -> 'State ->
state : 'State ->
list : 'T list
-> 'State
See how the elements match up?
List.foldBack
is the same but the elements are fed in reverse order. The order of the parameters is also different.
One of the interesting things about fold
is that in many cases it can replace a tail recursive function:
If you didn't have List.fold
and wanted to implement a totalF
function without mutable, how would you do it? You would need recursion, better yet tail recursion:
let rec totalF total ns =
match ns with
| [] -> total
| n :: tail -> totalF (total + n) tail
numbers
|> totalF 0
|> printfn "%A" // 133
Again you can see all the elements as before. In fact if we make total + n
into a parameter of totalF
:
let rec totalF f total ns =
match ns with
| [] -> total
| n :: tail -> totalF f (f total n) tail
numbers
|> totalF (fun total n -> total + n) 0
|> printfn "%A" // 133
You get fold
, the signature and use are the same:
val totalF:
f : 'a -> 'b -> 'a ->
total: 'a ->
ns : 'b list
-> 'a