Index
Source code:
github.com/arialdomartini/state-monad-for-the-rest-of-us
Our journey starts with this problem: we want to count the number of leaves of an arbitrary Binary Tree. To keep the problem as simple as possible, we define a Binary Tree as that data structure having Nodes and Leaves, and in which every Node has exactly two branches.
This is a Tree with 2 Leaves:
Node
/ \
Leaf Leaf
and here is a Tree with 3 Leaves:
Node
/ \
Leaf Node
/ \
Leaf Leaf
It is legit to ask ourselves: is a single Leaf a Tree?
Leaf
Let’s agree that it is.
Do null Trees — Trees without Nodes and Leaves — exist?
Let’s do ourselves a favor and agree they don’t.
A Node always contains 2 Leaves. What does a Leaf contain? Nothing: it
just is.
So, a Tree is either a Leaf or a Node made of 2 branches, each
containing another Tree structure. Remember this definition, because
we will translate it more or less literally to F#.
How deep can such Binary Trees be? Arbitrarily deep. For example, this is a legit Tree:
Node
/ \
Leaf Node
/ \
Node Leaf
/ \
Leaf Node
/ \
Node Leaf
/ \
Leaf Node
/ \
Leaf Leaf
Of course, our code should work with any Tree, no matter how deep.
Good. We are diligent developers, so we start from a test. To keep it simple, let’s count the Leaves of a 3-leaves Tree:
module StateMonadTest
open Xunit
open Swensen.Unquote
let numberOfLeaves tree = failwith "Not yet implemented"
[<Fact>]
let ``counts the number of leaves in a tree`` () =
let treeWith3Leaves = failwith "Not yet implemented"
let leaves = numberOfLeaves treeWith3Leaves
test <@ leaves = 3 @>
Let’s fill the gaps. As often happens with Functional Programming, it is convenient to start from modeling problems through their types.
How to model a Tree? That’s an easy task in F#: it’s a matter of translating literally our definition:
A Tree is either a Leaf or a Node containing 2 branches each containing another Tree structure.
Step by step:
A Tree is
is simply translated totype Tree =
be a Leaf
:type Tree = Leaf
or a Node
. The or
part is translated with the |
operator:type Tree = Leaf | Node ???
contains
2
branches. The contain part is translated with
of
, the multiplicity is translated with *
:type Tree = Leaf | Node of (??? * ???)
Tree
itself. So:type Tree = Leaf | Node of (Tree * Tree)
That’s it!
As an aside, this expression makes use of the so called Algebraic Data
Types, which include Sum (or Discriminated Union) Types and Product Types.
Name | Symbol used | Equivalent technique in OOP |
---|---|---|
Sum Types | | |
Inheritance |
Product Types | * |
Multiple fieds in classes |
In C# this would approximately translate to:
abstract record Tree;
record Leaf : Tree;
record Node(Tree Left, Tree Right) : Tree;
In both cases, we have a recursive type, which both languages happily support.
Creating instances in F# is similar, but more concise, than in C# and
Java: you just have to omit the new
keyword and the parenthesis.
Our 3-leaves tree:
Node
/ \
Leaf Node
/ \
Leaf Leaf
can be created with:
let treeWith3Leaves = Node(Leaf, Node(Leaf, Leaf))
This completes the test implementation:
[<Fact>]
let ``counts the number of leaves in a tree`` () =
let treeWith3Leaves = Node(Leaf, Node(Leaf, Leaf))
let leaves = numberOfLeaves treeWith3Leaves
test <@ leaves = 3 @>
Good. Now, let’s see how to make it green.
It is important to understand that in:
type Tree = Leaf | Node of (Tree * Tree)
Tree
is a type, Leaf
and Node
are not: Leaf
and Node
are
ways for creating an instance of type Tree
. This is akin to the
difference between a class and its constructors. In F#, we would call
Tree
, Leaf
and Node
as follows:
Element | Name |
---|---|
Tree |
Discriminated Union Type |
Leaf and Node of (Tree * Tree) |
Union Case |
Haskell uses different names, but the meaning is the same:
Element | Name |
---|---|
Tree |
Type Constructor |
Leaf and Node Tree Tree |
Data Constructor |
We will shortly see why Tree
is a Type Constructor, not just a
Type: as soon as Tree
will take a (type) parameter, we will learn to
see it more as a function than just a type name.
The takeaway here is: given an instance of Tree
, F# can pattern
match on the Case which was used to create the instance. It’s like
the instance remembers which constructor was used to create it. You
can read more about it in Pattern Matching - Identifier Patterns.
This give you all the ingredients to develop a function to calculate the number of leaves.
Pattern matching is easy:
let numberOfLeaves tree =
match tree with
| Leaf -> ???
| Node (l, r) -> ???
Read it as a glorified if/then/else
and ask yourself:
numberOfLeaves
receives a tree, and that tree is a single
Leaf
, how many leaves does that tree have? In other words, the
function needs to calculate the number of leaves of this tree: Leaf
Of course, it has just 1
leaf! So:
let numberOfLeaves tree =
match tree with
| Leaf -> 1
| Node (l, r) -> ???
What about if tree
is a Node
? You only know that a Node
contains
exactly 2
branches. Each branch could be a Leaf, like in:
Node
/ \
Leaf Leaf
or it could be another arbitrarily deep tree, like in:
Node
/ \
Leaf Node
/ \
Node Leaf
/ \
Leaf Node
/ \
Node Leaf
/ \
Leaf Node
/ \
Leaf Leaf
In the general case, you can see the received tree as:
Node
/ \
Tree Tree
This node contains as many leaves as the leaves in the right branch tree, plus the leaves of the left branch tree. Literally translating this sentence to F# leads us to:
let rec numberOfLeaves tree =
match tree with
| Leaf -> 1
| Node (l, r) -> numberOfLeaves l + numberOfLeaves r
Notice that we added the keyword rec
to the function definition, to
make it clear that the function is recursive.
Does this work? You have a test. Run it.
Of course it’s green. As it often happens, if it compiles it works.
Get used to this, it will happen over and over.
Let’s review what you obtained:
type Tree =
| Leaf
| Node of Tree * Tree
let rec numberOfLeaves tree =
match tree with
| Leaf -> 1
| Node(l, r) -> numberOfLeaves l + numberOfLeaves r
[<Fact>]
let ``counts the number of leaves in a tree`` () =
let treeWith3Leaves = Node(Leaf, Node(Leaf, Leaf))
let leaves = numberOfLeaves treeWith3Leaves
test <@ leaves = 3 @>
You can see how the:
| Leaf -> 1
branch is the base case of recursion, while the:
| Node(l, r) -> numberOfLeaves l + numberOfLeaves r
is the (double) recursive step.
Also notice how the results of the 2 recursive calls are composed
with the binary +
function.
In:
let rec numberOfLeaves tree =
match tree with
| Leaf -> 1
| Node(l, r) -> numberOfLeaves l + numberOfLeaves r
the parameter tree
is only used to pattern match. To stress this, F#
provides an alternative, more concise syntax, where
... tree =
match tree with
is replaced by:
... function =
So, our function can be made shorter, as:
let rec numberOfLeaves =
function
| Leaf -> 1
| Node(l, r) -> numberOfLeaves l + numberOfLeaves r
A nice way to interpret this is to squeeze the eyes and imagine it’s a
combination of 2 separate function definitions, one on Leaf
and one
on Node(l, r)
. Haskell would really let you define 2 separate functions:
numberOfLeaves Leaf = 1
numberOfLeaves Node(l, r) = numberOfLeaves l + numberOfLeaves r
If you manage to understand this last snippet, bravo!, give yourself a pat on the back, take a little breather, and get prepared to the next chapter, in which you will invent (or discover) Functors.
Jump to Chapter 2.
Interested in FP? Be the first to be notified when new introductory
articles on the topic are published.