Index
Source code:
github.com/arialdomartini/state-monad-for-the-rest-of-us.
In the previouls chapter you
distilled map
, a function exhibiting 3 powerful features:
It sounds like a mighty function and in fact it is.
One may mistake, though, the possibility to pass an arbitrary
transformation function with the ability to perform any arbitrary
transformation. The two are not the same and, unfortunately, Functors
have limits to the trasformations they can produce.
Let’s put this statement to the test.
During its execution, map
traverses the tree. Very well: we want it
to keep a track of the order with which it visits the leaves. That
is, if it visits:
Leaf "one"
Leaf "two"
Leaf "three"
it should transform the tree:
Node
/ \
Leaf "one" Node
/ \
Leaf "two" Leaf "three"
into:
Node
/ \
Leaf ("one", 1) Node
/ \
Leaf ("two", 2) Leaf ("three", 3)
Let’s call this operation indexing a tree.
Here is the requirement:
[<Fact>]
let ``indexes a tree`` () =
let tree = Node(Leaf "one", Node(Leaf "two", Leaf "three"))
let indexed = map index tree
test <@ indexed = Node(Leaf ("one", 1), Node(Leaf ("two", 2), Leaf ("three", 3))) @>
If you come from an imperative language, you don’t have to think twice to find the solution:
let counter = 1
let index v =
let indexedLeaf = (v, counter)
counter = counter + 1
indexedLeaf
Now, F# is a grumpy functional zealot and will refuse to compile:
counter = counter + 1
“Variables must be immutable”, it will bemoan.
Not only does it demand you to explicitly declare counter
as
mutable:
let mutable counter = 1
but it also insists that you use a special syntax for mutating the value:
counter <- counter + 1
To add insult to injury, Rider will underline all the 4 occurrences of
counter
to warn you with alarming voice “Mayday, mayday! A mutable
variable!”. They really go to great lenghts to make it hard to deviate
from purity, don’t they?
Anyway, once bow to F#’s will and run the test, you can finally
confirm that your impure index
function does work:
let mutable counter = 1
let impureIndex v =
let indexedLeaf = (v, counter)
counter <- counter + 1
indexedLeaf
[<Fact>]
let ``indexes a tree`` () =
let tree = Node(Leaf "one", Node(Leaf "two", Leaf "three"))
let indexed = map impureIndex tree
test <@ indexed = Node(Leaf ("one", 1), Node(Leaf ("two", 2), Leaf ("three", 3))) @>
Now, give yourself 15 mins for a coding challenge. Try to get to the same green test, this time adding only one single extra constraint:
This means:
index
so that it is referential transparent.About the last comment, here’s a trick you can use: whatever function
you write, if it is pure, given the same argument it must always return
the same value. So, it must be always safe to invoke it twice.
So, write your test as follows:
[<Fact>]
let ``indexes a tree`` () =
let tree = Node(Leaf "one", Node(Leaf "two", Leaf "three"))
map index tree |> ignore // intentionally invoked twice
let indexed = map index tree
test <@ indexed = Node(Leaf ("one", 1), Node(Leaf ("two", 2), Leaf ("three", 3))) @>
Another option is to define this function:
let invokedTwice f =
fun v ->
f v |> ignore
f v
and then, instead of:
let indexed = map index tree
to use:
let indexed = map (index |> invokedTwice) tree
It will not take much to convince yourself that indexing a tree using a pure Functor is just not possible.
The problem is that, while traversing the tree, your algorithm must retain some form of memory, some kind of state.
So, you can draw an important conclusion:
Implementing stateful algorithms with pure functions is not possible.
Correct?
Nothing further from the truth.
You will prove that statement wrong in the very next chapter. Then you will build on top of that demonstration discovering Applicative Functors first, and finally the mighty State Monad.
Treat yourself with an icecream, and when you feel ready, jump to the chapter 5.