CHAPTER 3
We’ll start this section with some easier concepts that have less equivalence in C# (at least without some considerable work) but deepen our understanding of F# and add to our “thinking like an F# programmer” toolbox. We’ll begin with closures and discriminated unions, and then move on to active patterns and continuation passing style (CPS). We’ll revisit recursion that uses CPS and then take on two of the most difficult topics for developers new to functional programming: monads and computation expressions.
Closure is a concept you should be familiar with already, as it plays an important role in lambda expressions in C#. “In programming languages, a closure…is a function …together with a referencing environment—a table storing a reference to each of the non-local variables (also called free variables or upvalues) of that function. A closure—unlike a plain function pointer—allows a function to access those non-local variables even when invoked outside its immediate lexical scope.”[46]
For example, in C# we can demonstrate “print the value of the lexical scope” closure:
using System; namespace Closures { delegate void Func(); class Program { static Func[] funcs = new Func[10]; static void CreateFuncs() { for (int i = 0; i < funcs.Length; i++) { int j = i; // Create a lexical scope. funcs[i] = () => Console.Write("{0} ", j); } } static void Main(string[] args) { CreateFuncs(); for (int i = 0; i < funcs.Length; i++) { funcs[i](); } } } } |
Resulting in the output:
0 1 2 3 4 5 6 7 8 9 |
We can write something similar in F#:
let rec createFuncs i arrayOfFuncs = match i with | 10 -> arrayOfFuncs | n -> createFuncs (n+1) ((fun () -> printfn "%d" n) :: arrayOfFuncs) let funcs = List.rev (createFuncs 0 []) List.iter (fun f -> f()) funcs;; |
The previous code results in the same output (each number on a separate line). Closures are relied upon frequently in functional programming because one is often creating partial function applications that pass in values (or functions) that are part of the current lexical scope. One should become very comfortable with closures in order to be proficient at functional programming.
“Discriminated unions provide support for values that can be one of a number of named cases, possibly each with different values and types. Discriminated unions are useful for heterogeneous data; data that can have special cases, including valid and error cases; data that varies in type from one instance to another; and as an alternative for small object hierarchies. In addition, recursive discriminated unions are used to represent tree data structures.”[47]
You can think of discriminated unions as providing additional semantic meaning to a type, by reading a discriminated union as something like “A type that can be an x, a y, or a z (etc.) where x, y, and z are of type a, b, and c, respectively.” This does not hold true in all cases, as we will see later.
Discriminated unions have no direct equivalent in imperative, object-oriented languages. The closest one can get is to implement an object hierarchy. Take for example two shapes: a circle and a rectangle. In an object-oriented paradigm, we would typically write an object model:
public class Shape { public abstract double Area { get; } } public class Circle : Shape { public double Radius { get; set; } public override double Area { get { return 3.14 * Radius * Radius; } } } public class Rectangle : Shape { public double Height { get; set; } public double Width { get; set; } public override double Area { get { return Width * Height; } } } |
In F#, we can implement a discriminated union:
type Shape = | Circle of double | Rectangle of double * double |
Note that in Visual Studio 2013, we can assign labels to the types: [48]
type Shape = | Circle of radius : double | Rectangle of width : double * height : double |
For our purposes, we’ll stay with the implementation of discriminated unions supported in Visual Studio 2012.
Note that the closest construct in C# would be a class, as illustrated earlier.
We can then calculate the area of our shapes using a function and pattern matching on the type:
let area shape = match shape with | Circle r -> 3.14 * r * r | Rectangle (w, h) -> w * h |
Since this function only does matching on a single parameter with lambda expressions for each case, we use this shorthand syntax:
let area = function | Circle r -> 3.14 * r * r | Rectangle (w, h) -> w * h |
We can then compute the areas of our two shapes (F# console):
> area (Circle(1.0));; val it : float = 3.14 > area (Rectangle(2.0, 4.0));; val it : float = 8.0 |
Comparing this to the C# code earlier, you could say that we have “de-virtualized” the class hierarchy and explicitly implemented the equivalent of a virtual table.
A discriminated union does not need to specify the “type of” for the case-identifier. We can construct something similar to an “enum” in C# using (F# console):
> type Vehicle = | Truck | Car let vehicles = [Truck, Car];; type Vehicle = | Truck | Car val vehicles : (Vehicle * Vehicle) list = [(Truck, Car)] |
The salient difference with C#’s enumerations is that the case-identifiers (here “Truck” and “Car”) are not assigned values—the identifier is just that, an identifier.
type Assembly = | Subassembly of string * Assembly | Part of string |
Here I am defining an assembly that can be either a sub-assembly that has a name and further sub-assemblies, or the assembly can be the actual part, having a name.
One finds discriminated unions frequently in describing a node in a tree. For example, the following code defines a binary tree type as being either Empty (with no type) or having a Node with a three-element tuple type, being:
type 'a BinaryTree = | Empty | Node of 'a BinaryTree * 'a * 'a BinaryTree |
This is one way of defining a binary tree. Note that the case-identifier Empty does not specify any “of-type” information—it merely serves as a placeholder identifier, allowing us to construct trees like this (F# console):
> let tree = Node( Node( Node(Empty, 1, Empty), 5, Empty), 10, Node(Empty, 20, Empty));; val tree : Tree<int> = Node (Node (Node (Empty,1,Empty),5,Empty),10,Node (Empty,20,Empty)) |
With this example we have constructed a small tree that looks like this:

Expression trees are another excellent example of where discriminated unions can be very effective. This example, borrowed from MSDN’s description of discriminated unions,[49] illustrates the concept:
type Expression = | Number of int | Add of Expression * Expression | Subtract of Expression * Expression | Multiply of Expression * Expression | Divide of Expression * Expression | Variable of string |
Pattern matching is a common technique in functional programming languages. A match expression looks a little like a case statement in an imperative language, but there are significant differences. First, it’s an expression, not a statement. Second, in its most distinctive form, each case represents an alternative way a value of the type of the value being matched on could have been constructed, which provides a powerful generic way to take things of a given type apart. In some languages, the compiler will tell you whether the cases are exhaustive for the type or not. There are various extensions to the basic concept. A noteworthy one specific to F# is active patterns.
“Active patterns enable you to define named partitions that subdivide input data, so that you can use these names in a pattern-matching expression just as you would for a discriminated union. You can use active patterns to decompose data in a customized manner for each partition.”[50]
Similar to a discriminated union in which we create identifiers that may or may not be associated with types, with active patterns we can name “partitions” of a pattern in an active recognizer. For example:
let (|Prefix|_|) (p:string) (s:string) = if s.StartsWith(p) then Some(s.Substring(p.Length)) else None |
This active recognizer will allow us to match the identifier Prefix when the prefix p is found at the start of the string “s”. Incidentally this code is a “partial active pattern” because we don’t care what the rest (the “_”) of the string is.
We use the active recognizer in a match statement:
let activePatternTest s = match s with | Prefix "F#" rest -> printfn "Started with 'F#', rest is '%s'" rest | Prefix "Is" rest -> printfn "Started with 'Is', rest is '%s'" rest | _ -> printfn "Don't know what you're talking about." |
This allows us to test for a particular pattern as determined by the active recognizer function. So, for example, this is the output of three pattern-matching tests (F# console):
> activePatternTest "F# is great!" activePatternTest "Is F# great?" activePatternTest "Yes it is!";; Started with 'F#', rest is ' is great!' Started with 'Is', rest is ' F# great?' Don't know what you're talking about. |
You can use active patterns to decompose a value into different representations. For example, let’s say you want to decompose the result of a division into either the result itself or a quotient and its remainder. We can write two active patterns (please note that for the following examples, I am not dealing with division by zero):
let (|DivisionResult|) (numerator, divisor) = numerator / divisor let (|QuotientRemainder|) (numerator, divisor) = (numerator / divisor, numerator % divisor) |
Now let’s say we have two functions, and among other things, they will print the result either as a floating point result or as a quotient and divisor:
let printResult numerator divisor = match (numerator, divisor) with | DivisionResult(r) -> printfn "Result = %f" r let printQuotientDivisor numerator divisor = match (numerator, divisor) with | QuotientRemainder(q, d) -> printfn "Quotient = %d, Divisor = %d" q d |
We can then call these two functions and obtain different results based on how we intend to match the input with the active pattern (F# console):
> printResult 1.0 3.0;; Result = 0.333333 val it : unit = () > printQuotientDivisor 1 3;; Quotient = 0, Divisor = 1 val it : unit = () |
These two examples are certainly trivial. The real power of active patterns comes about when combining several options in the match expression using partial active patterns. For example, let’s say that we do not want a match in the floating point division if the fractional part (everything to the right of the decimal point) is more than two digits, so we use the quotient-remainder format instead. First, let’s write a small helper function to determine the length of the fractional part:
let moreThanTwoDigits n = let strN = n.ToString() let idx = strN.IndexOf('.') if idx > 0 then // Is there a decimal point? let remainder = strN.Substring(idx) remainder.Length > 3 // Including the '.', returning true or false. else false // No decimal point. |
Next, we write a partial active pattern ending in the wildcard (_). This tells us it’s a partial pattern and that we will be returning None or Some:
let (|DivisionResult|_|) (numerator: int, divisor: int) : float option = let r = float(numerator) / float(divisor) if moreThanTwoDigits r then None else Some r |
(The casting is used to force the function to accept only integers and to return an optional float.)
The quotient-remainder pattern stays the same (and it also is not a partial pattern):
let (|QuotientRemainder|) (numerator, divisor) = (numerator / divisor, numerator % divisor) |
Finally, we can write a function that prints the result based on the match with the active pattern:
let printResult numerator divisor = match (numerator, divisor) with | DivisionResult(r) -> printfn "Result = %f" r | QuotientRemainder(q, d) -> printfn "Quotient = %d, Divisor = %d" q d |
Now let’s see the result (F# console):
> printResult 2 4;; Result = 0.500000 val it : unit = () > printResult 10 3;; Quotient = 3, Divisor = 1 val it : unit = () |
Here we can see that for 10/3, we fail to match the active pattern DivisionResult, but we do match the active pattern QuotientRemainder.
“[A] continuation represents the remainder of a computation given a point in the computation.”[51] Continuations are usually not encountered in imperative programming, but they have definite uses in a functional programming environment, which we’ll discuss further later on. Basically, the idea is that a function is provided with another function that is invoked with the result of the first function’s computation. So for example, rather than writing the database reader with a pipeline to create the Person records:
let selectNames = createReader db sql |> createPersonListRec |
I could change the implementation of createReader to accept a continuation function and make a call using CPS:
let selectNames = createReaderCps db sql createPersonListRec |
Notice the pipeline (|>) token has disappeared and we’re providing the function to which we want to pass the result. The createReaderCps function is then defined as:
open System open System.Data open System.Data.SqlClient let createReaderCps (connection : SqlConnection) sql f = let command = connection.CreateCommand() command.CommandText <- sql let result = command.ExecuteReader() f result |
Notice how the last action of the createReaderCps function is to invoke the provided function f with the result of the ExecuteReader() call.
We’re used to writing imperative code such that when an invalid condition is discovered, the method handles it in a particular way: throwing an exception, issuing a message, returning null, etc. The “fail” handling is intrinsic to the method. For example, in F# I can create a map of PersonIDs and the records associated with them:
let nameMap = createReader db sql |> createPersonListRec |> Seq.map (fun r -> r.PersonID, r) |> Map.ofSeq |
I could access the record using Map.tryFind (F# console):
> Map.tryFind 1 nameMap;; val it : Person option = Some {PersonID = 1; FirstName = "Ken"; LastName = "Sánchez";} > Map.tryFind 1234 nameMap;; val it : Person option = None |
However, let’s use continuations to specify what we want to do once we find the record. For this, we’ll create a function that takes two functions (the success and fail) as well as the key and map:
let findRecord success fail key map = let record = Map.tryFind key map match record with | None -> fail key | _ -> success record |
Now we can provide the behavior (inversion of control) for the success and failure states. A very trivial example is printing the record value or “not found”:
> findRecord (fun r -> printfn "%A, %A" r.Value.LastName r.Value.FirstName) (fun r -> printfn "not found") 1 nameMap;; "Sánchez", "Ken" val it : unit = () > findRecord (fun r -> printfn "%A, %A" r.Value.LastName r.Value.FirstName) (fun r -> printfn "not found") 1234 nameMap;; not found val it : unit = () |
(In this code I’m de-optioning the result by using the Value property of the option, which is generally not recommended, but in this case, because I know the function is only being called with a matching record, I know that it is safe to implement in this way.)
Let’s say that our records are cached and that if the key isn’t found in the cache, we want to hit the database to return the record and add it to our cache. Since caches are inherently mutable, we’re going to use the System.Collections.Generic namespace to instantiate a simple cache that only handles a single table:
open System.Collections.Generic let cache = new Dictionary<int, Person>() |
Note: Because we are using a mutable structure, it is not thread-safe! The cache dictionary should be locked before updating and retrieving items.
Next, we create a function that updates the cache given a Person record (which we defined in Chapter 2). Note that this function returns the person record as well, which makes it easier to use with pipelining and binding the record to a variable:
let updateCache p = cache.[p.PersonID] <- p p |
I’m going to create a helper function for constructing a Person record from the SqlDataReader; this is intended to be used when we know we have only one record to read:
let createPersonRecord (reader : SqlDataReader) = let id = Convert.ToInt32(reader.["BusinessEntityID"]) let fname = reader.["FirstName"].ToString() let lname = reader.["LastName"].ToString() {PersonID = id; FirstName = fname; LastName = lname} |
Next, we write a function that will retrieve a single record from the database or throw an exception. We will use this function when the “get record from the cache” fails. As this is an example only, it uses a bit of brute force:
let getPersonFromDatabase id = printfn "Querying database" let sql = "select BusinessEntityID, FirstName, LastName from Person.Person where BusinessEntityID = " + id.ToString() let reader = createReader db sql match reader.Read() with | false -> failwith "record not found" | true -> let ret = reader |> createPersonRecord |> updateCache reader.Close() ret |
Now we can write our continuation function. Note how we require the function onFail as the first parameter, which, if the record isn’t in the cache, we call with the desired ID. This puts it on the caller to provide the implementation for what to do when the record isn’t in the cache, thus turning the implementation inside-out—inversion of control.
Also note the call to TryGetValue. In F#, “out” parameters are returned, along with the method call result, in the form of a tuple. This is a very nice convenience because we can get both the success state of the TryGetValue as well as the value if successful, without resorting to reference types[52]—or worse, a mutable type to receive the out value.
let getRecord onFail id = let found, p = cache.TryGetValue(id) match found with | false -> onFail id | true -> printfn "From cache" p |
We can now take advantage of partial application by creating two partial functions: one that handles getting data from the database in a “connected” environment, and one that simply throws an exception in a “disconnected” environment:
let getRecordConnected = getRecord getPersonFromDatabase let getRecordDisconnected = getRecord (fun _ -> failwith "not in cache") |
Let’s give it a test drive (F# console):
> getRecordConnected 21;; Querying database val it : Person = {PersonID = 21; FirstName = "Terry"; LastName = "Eminhizer";} > getRecordConnected 21;; From cache val it : Person = {PersonID = 21; FirstName = "Terry"; LastName = "Eminhizer";} > getRecordDisconnected 21;; From cache val it : Person = {PersonID = 21; FirstName = "Terry"; LastName = "Eminhizer";} > getRecordDisconnected 22;; System.Exception: not in cache |
Note that the first call to getRecordConnected queries the database for ID 21, and the second call gets the record from the cache. Note also how, in the disconnected state, we can only get records from the cache—otherwise an exception is thrown.
Recall that for the compiler to translate recursion to iteration, the recursive call must be the last call made in the function. There are times when this is difficult to achieve, especially when working with two or more different “branches” in a recursive process, such as parsing a binary tree.[53] Using our Tree discriminated union described earlier, we can see CPS is a necessary device to employ when, for example, tail-recursively determining the size of a tree. Let’s look at a straightforward recursive implementation that counts non-empty nodes:
let getTreeSize tree = let rec treeSize tree acc = match tree with | Empty -> acc | Node (left, _, right) -> treeSize left (acc+1) + treeSize right 0
treeSize tree 0 |
The second half treeSize right 0 is a bit non-intuitive—we increment the accumulator because we have a node. It doesn’t matter whether we do this when recursively calling to process a left branch or a right branch; we just don’t do it for both left and right recursive calls—otherwise we will get double the count.
When we call it using our small tree defined earlier, we see that we get four nodes (F# console):
let tree = Node(Node(Node(Empty, 1, Empty), 5, Empty), 10, Node(Empty, 20, Empty)) getTreeSize tree;; val getTreeSize : tree:Tree<'a> -> int val tree : Tree<int> = Node (Node (Node (Empty,1,Empty),5,Empty),10,Node (Empty,20,Empty)) val it : int = 4 |
The problem is, this call is not tail-recursive, so we run the risk of a stack overflow.
To make this tail-recursive, we have to use CPS to pass in the continuation operation:
let getTreeSizeRec tree = let rec treeSize tree f = match tree with | Empty -> f 0 | Node (left, x, right) -> treeSize left (fun lacc -> treeSize right (fun racc -> f (lacc + racc + 1) ) ) treeSize tree (fun x -> x) |
Here we are using double-recursion with left and right accumulators in a CPS to create a tail-recursive function. This is not easy to wrap one’s head around, so let’s put in some trace statements and see how this is working:
let getTreeSizeRec tree = let rec treeSize depth caller tree f = printfn "Depth: %d Caller: %s" depth caller match tree with | Empty -> printfn "Empty" f 0 | Node (left, x, right) -> printfn "Node: %d" x treeSize (depth+1) "left" left (fun lacc -> printfn "lacc: %d, x: %d" lacc x treeSize (depth+1) "right" right (fun racc -> printfn "lacc: %d, x: %d, racc: %d, returning = %d" lacc x racc (lacc+racc+1) f (lacc + racc + 1) ) ) treeSize 0 "root" tree (fun x -> x) |
Here is the output (F# console):
> let tree = Node(Node(Node(Empty, 1, Empty), 5, Empty), 10, Node(Empty, 20, Empty)) let treeSize = getTreeSizeRec tree ;; Depth: 0 Caller: root Node: 10 Depth: 1 Caller: left Node: 5 Depth: 2 Caller: left Node: 1 Depth: 3 Caller: left Empty lacc: 0, x: 1 Depth: 3 Caller: right Empty lacc: 0, x: 1, racc: 0, returning = 1 lacc: 1, x: 5 Depth: 2 Caller: right Empty lacc: 1, x: 5, racc: 0, returning = 2 lacc: 2, x: 10 Depth: 1 Caller: right Node: 20 Depth: 2 Caller: left Empty lacc: 0, x: 20 Depth: 2 Caller: right Empty lacc: 0, x: 20, racc: 0, returning = 1 lacc: 2, x: 10, racc: 1, returning = 4 val tree : int Tree = Node (Node (Node (Empty,1,Empty),5,Empty),10,Node (Empty,20,Empty)) val treeSize : int = 4 |
We now have a better understanding of how this function works. Refer to Appendix A for a complete “stack” trace based on the previous output. The “magic” of this code happens in-line with that call to f with the sum of the left accumulator + the right accumulator + 1.
treeSize left (fun lacc -> treeSize right (fun racc -> f (lacc + racc + 1) |
A very simple example will hopefully suffice to illustrate this: when the function is called with a single node (Empty, 10, Empty):
treeSize right (fun racc -> f (0 + racc + 1) |
treeSize right (fun racc -> f (0 + racc + 1) |
With a more complicated tree, f is the function passed into each recursive call, thus each recursion “nests” the computation for the argument of fun lacc or fun racc. Once the function can evaluate, the value becomes the input for the previously nested function. Again, the trace in Appendix A should make this clearer, and I have illustrated the trace using a pseudo-stack for the function—once the innermost function evaluates, the stack is popped and the value is supplied as the parameter to the previous function on the stack.
One question to ask when writing in a functional programming style is: “Can this computation be passed-in rather than hardwired in the function itself?” Earlier, in discussing higher-order functions, we showed how to write a function that takes a function as a parameter. This is a very powerful technique, and something similar can be done with continuations. For example, the code that determines the size of the tree can be refactored such that the “continuation,” in this case lacc + racc + 1, is passed in by the caller (I’ve renamed the function to the more generalized foldTree):
let foldTree computation tree = let rec folding tree f = match tree with | Empty -> f 0 | Node (left, x, right) -> folding left (fun lacc -> folding right (fun racc -> f (computation lacc x racc) ) ) folding tree (fun x -> x) |
This results in a generalized tree fold operation. The previous computation, counting leaves, can now be passed in as part of the call:
let treeSize = foldTree (fun lacc _ racc -> lacc + racc + 1) tree |
We can pass in other computations, for example, adding all the values of the tree, given that a is of type integer:
let nodeSum = foldTree (fun lacc x racc -> lacc + x + racc) tree |
with the result (F# console):
> let nodeSum = foldTree tree (fun lacc x racc -> lacc + x + racc);; val nodeSum : int = 36 |
The “magic” here (besides being an excellent example of higher-order functions) is in the meaning of “accumulator”—in the first case, it’s an incrementing count, and in the second case, it’s a sum.
Lastly, we can employ a partial function application to create re-usable functions:
let getTreeSize<'a> = foldTree (fun lacc (x : 'a) racc -> lacc + racc + 1) let getSumOfNodes = foldTree (fun lacc x racc -> lacc + x + racc) |
And now we can call our partial applications (F# console):
> getTreeSize<int> tree;; val it : int = 4 > getSumOfNodes tree;; val it : int = 36 |
Notice in this code example that we have to specify the type for the getTreeSize function, as the type cannot be inferred since it isn’t used in the computation!
This is the identity function:
(fun x -> x) |
It is an extremely useful construct when “seeding” the call to a recursive function that requires an initial function. This construct is so useful that it is actually an operator:[54]
id |
You can use id anywhere you would otherwise explicitly write (fun x -> x).
The time has come to deal with the “m” word—monads. “Computation expressions in F# provide a convenient syntax for writing computations that can be sequenced and combined using control flow constructs and bindings. They can be used to provide a convenient syntax for monads, a functional programming feature that can be used to manage data, control, and side effects in functional programs.”[57]
But what is a monad?
“In functional programming, a monad is a structure that represents computations defined as sequences of steps. A type with a monad structure defines what it means to chain operations, or nest functions of that type together. This allows the programmer to build pipelines that process data in steps, in which each action is decorated with additional processing rules provided by the monad. As such, monads have been described as "programmable semicolons"; a semicolon is the operator used to chain together individual statements in many imperative programming languages, thus the expression implies that extra code will be executed between the statements in the pipeline. Monads have also been explained with a physical metaphor as assembly lines, where a conveyor belt transports data between functional units that transform it one step at a time.”[58]
One of the uses for a monad is to retain state without resorting to mutable variables. In this section, we will attempt to wrap our heads around computation expressions and monads as this ends up being a real mind-bender, especially coming from an imperative programming lifestyle. Also, monads have important uses with regard to functional programming and really have no equivalent (except loosely in terms of aspect-oriented programming) in the imperative world.
Continuations are a key aspect of computation expressions. First off, when we write the following:
let a = 5 let b = 6 let c = a + b |
We need to realize that this is already a shorthand for the verbose in syntax:[59]
let a = 5 in let b = 6 in let c = a + b c |> ignore // Expression required to finish the let block. |
If we now pipe the value into a function of the same name as the identifier, we can rewrite the previous code using CPS:
let n = 5 |> (fun a -> 6 |> (fun b -> a + b |> (fun c -> c))) |
But now, let’s make this more interesting by actually writing a function for the pipeline operator. The pipeline operator is defined as |> x f = f x. In other words, f is called with the parameter x. We can write our own pipeline operator as a function easily enough:
let pipeFnc x f = f x |
Now, our three let statements can be written as:
let n = pipeFnc 5 (fun a -> pipeFnc 6 (fun b -> pipeFnc (a+b) (fun c -> c))) |
But wait! We can now introduce additional computations in pipeFnc! For example, we can log the values of x:
let pipeFnc x f = printfn "x = %d" x f x |
When we now call our converted let statements, we get (F# console):
let n = pipeFnc 5 (fun a -> pipeFnc 6 (fun b -> pipeFnc (a+b) (fun c -> c)));; x = 5 x = 6 x = 11 val n : int = 11 |
We have successfully added additional code that does something behind the scenes and represents a very simple example of a computation expression workflow.
If instead we move these operations into a class and use the F# computation expression syntactical sugar names, we would start with a class type that defines methods that control how the expression executes. This class type is known as a builder type and is usually given a builder name:
// Define the builder type, which is a class type. type LoggerBuilder() = // Define the custom operation. let log x = printfn "x = %d" x // Called for let! (as well as do!) member b.Bind(x, f) = log x f x // Called for "return". member b.Return(x) = x |
Using a de-sugared syntax so that the use of our LoggerBuilder class looks similar to how we were using the pipeFnc before, we can write:
let logger = new LoggerBuilder() let n = logger.Bind(5, fun a -> logger.Bind(6, fun b -> logger.Bind(a + b, fun c -> logger.Return(c)))) |
The usage is equivalent to our CPS version, except that we are using member functions of a class and results in the same output, and the result exactly matches our CPS version.
Now let’s look at the sugared syntax:
// Construct the logger. let logger = new LoggerBuilder() let loggedWorkflow = logger // Using the LoggerBuilder... { let! a = 5 // Bind and compute expression. let! b = 6 // Bind and compute expression. let! c = a + b // Bind and compute expression. return c // Return c. } |
The result is the same (F# console):
n = 5 n = 6 n = 11 val loggedWorkflow : int = 11 |
Let’s try our hand at something called the Maybe monad—a computation expression that stops its computation when an invalid “state” is encountered. A simple example of this is doing a series of division operations where the denominator “may be” 0.
The “builder” class is simple enough (in all examples you will find of this computation expression, you will see it called MaybeBuilder, but I prefer the more semantically correct MayBeBuilder, so I will use that in the following examples):
// Again we define a class... type MayBeBuilder() = // with the member Bind so our sugared "let!" works. member b.Bind(x, f) = match x with | None -> None // The value “may be” invalid, in which case we stop. | Some n -> f n // Continue with the workflow if state remains valid // and with the member Return so our sugared "return" works. member b.Return(x) = Some x |
We will also need a divideBy function that returns “None” if dividing by zero, or “Some n” to represent the result:
let divideBy numerator divisor = match divisor with | 0 -> None | n -> Some (numerator / n) |
Now we can execute a workflow. For example (F# console):
> let mayBeDivided = MayBeBuilder { let! a = (20 |> divideBy) 2 let! b = (a |> divideBy) 5 return b };; val mayBeDivided : int option = Some 2 |
Or, if we try dividing by 0 (F# console):
> let mayBeDivided = MayBeBuilder { let! a = (20 |> divideBy) 0 let! b = (a |> divideBy) 5 return b };; val mayBeDivided : int option = None |
Again, this is just syntactical sugar for a continuation passing style of writing a workflow. One interesting thing about the Maybe monad that is not to be overlooked is that once the computation is deemed to be invalid (it returns “None”), the workflow stops.
A state monad does not solve the overall issue of maintaining state throughout the lifetime of the application. For this, the mutable dictionary from the System.Collections.Generic namespace is one solution. Rather, a state monad maintains state within a specific workflow. I will take a simple example of generating pseudo-random numbers[60] (manually!), using a “multiply with carry” method. A mutable implementation in F# would look like this:
let mutable m_w = 1 let mutable m_z = 2 let getRandom a b = m_z <- (36969 * (a &&& 65535) + (a >>> 16)) m_w <- (18000 * (b &&& 65535) + (b >>> 16)) uint32((m_z <<< 16) + m_w) |
Resulting in (F# console):
> getRandom 1 2;; val it : uint32 = 2422836384u > getRandom m_z m_w;; val it : uint32 = 1907405312u > getRandom m_z m_w;; val it : uint32 = 3696412319u |
When we convert this to an immutable form, we need to always return the new state along with the new random number:
let getRandomImmutable state = let m_z = (36969 * (fst state &&& 65535) + (fst state >>> 16)) let m_w = (18000 * (snd state &&& 65535) + (snd state >>> 16)) let newRand = uint32((m_z <<< 16) + m_w) printfn "%d" newRand newRand, (m_z, m_w) |
Resulting in a tuple of tuples—(random number, (state)):
> let ri1 = getRandomImmutable (1, 2);; val ri1 : uint32 * (int * int) = (2422836384u, (36969, 36000)) > let ri2 = getRandomImmutable (snd ri1);; val ri2 : uint32 * (int * int) = (1907405312u, (1366706961, 648000000)) > let ri3 = getRandomImmutable (snd ri2);; val ri3 : uint32 * (int * int) = (3696412319u, (710454127, 820233887)) |
It certainly is inconvenient to have to extract the value from the tuple and pass on the state every time we make this function call.
To create a state monad, we will first define an operator that allows us to chain functions together. Why? Because getRandomImmutable is a function taking a state and returning a value and a state, and what we’re doing in the previous code by always passing in the new state to get the next random number is a manual form of chaining the function calls. Here’s our new operator:
let (>>=) x f = (fun s0 -> let v, s = x s0 f v s) // Note! Continuation passing style! |
This operator is borrowed from the functional programming language Haskell, which defines this operator as “Monad sequencing operator with value passing.” Here, x is function that returns a value and a new state given the initial state, s0. The operator is a partial function application because it is provided with x and the next function in the chain, but it isn’t given s0, the initial state.
Let’s look just at what this expression does:
let v, s = x s0 |
And let’s pretend that x is this function (see how it is returning a tuple, a value, and the next state):
let p = (fun t -> t * 10, t + 1) |
So, for example:
> let v, s = p 1;; val v : int = 10 val s : int = 2 |
Here we have a “state system” that returns 10 times the current state value and the state incremented by 1.
The next state in the >>= operator is to apply the function f, which is on the right-side of the operator. We can do some useful things here:
let getState = (fun v s -> s) let getValue = (fun v s -> v) |
We can see the results here (F# console):
> 1 |> (p >>= getValue);; val it : int = 10 > 1 |> (p >>= getState);; val it : int = 2 |
Now, the trick here is that we want the continuation function f to have the right signature so that we can continue to use the >>= operator in our monadic sequence. Well, what would this be? It’s a function that takes a value and returns the function that performs the computation, in other words, p. So we can write this:
let q v = printfn "%d" v p |
Now we can write a monadic sequence (F# console):
> 1 |> (p >>= q >>= q >>= getValue);; 10 20 val it : int = 30 |
In other words, p evaluates with the value of 1, calls q which returns the partial function application of p, whose call is “finished” by the caller f v s. We could explicitly specify the state parameter as well:
let q v s = printfn "%d" v p s |
But with partial function application, we don’t need to!
Now we can take what we learned about the >>= operator and apply it with our random number generator, which already conforms to the form of taking a state and returning a tuple of value and state. All we need to do is write this in a continuation passing style, providing the next function in the chain (F# console):
> (1, 2) |> (getRandomImmutable >>= (fun a -> getRandomImmutable >>= (fun c -> getRandomImmutable) >>= getValue));; 2422836384 1907405312 3696412319 |
Now let’s write this with a StateBuilder class:
type StateBuilder() = member m.Bind(x, f) = x >>= f member m.Return a = (fun s -> a, s) |
The Bind function is itself bound to the function we created earlier: a function that takes a state and returns a function that takes a value and a state. Thus, Bind is a function that takes a function accepting a state and returning a state and a value, which is passed to a function taking a value and a state.
That last part is where the continuation passing style comes into effect, allowing us to chain the function calls to getRandomImmutable.
The de-sugared call using the StateBuilder looks like this (note how it’s in the same continuous passing style form as all our previous monads):
let state = new StateBuilder() let printRandoms = (1, 2) |> state.Bind(getRandomImmutable, fun a -> state.Bind(getRandomImmutable, fun b -> state.Bind(getRandomImmutable, fun c -> state.Return 0)));; |
Here, we start with a “seed” state that is passed to our monad sequence function, which returns a value and a state. The value is “a” (or “b”, or “c”) and the state “value” completes the partial application of the next call, which is again a function that takes a state and returns a value and a state. Again, if we wanted to be explicit about how state is being handled, we could write:
let randoms2 = (1, 2) |> state.Bind(getRandomImmutable, fun a s -> s |> state.Bind(getRandomImmutable, fun b s -> s |> state.Bind(getRandomImmutable, fun c s -> s |> state.Return 0)));; |
But it’s the partial function application syntax that allows us to easily translate the first CPS example to the sugared computation expression:
let printRandomValues = state { let! a = getRandomImmutable let! b = getRandomImmutable let! c = getRandomImmutable return 0 } |
The result (F# console):
> computeRandomValues (1, 2);; 2422836384 1907405312 3696412319 val it : int * (int * int) = (0, (710454127, 820233887)) |
Observe the final return from the call to computeRandomValues. It’s a tuple with our return value of 0 and the current state of the random number generator. Simply printing a result isn’t very useful (and it’s a side effect, actually), so let’s modify this function slightly to return a list of random values:
let computeRandomValues = state { let! a = getRandomImmutable let! b = getRandomImmutable let! c = getRandomImmutable return a :: b :: c :: [] };; |
And now the result (having commented out the printfn statement in getRandomImmutable) is a list of our three random numbers from the initial seed (F# console):
> fst (computeRandomValues (1, 2));; // Throw away the final state. val it : uint32 list = [2422836384u; 1907405312u; 3696412319u] |
Hopefully these exercises have demystified monads and computation expressions. There are several more behaviors that a computation expression “sugars” that you can explore further on the MSDN pages on this topic.