F Sharp For Fun and Profit
F Sharp For Fun and Profit
of Contents
Getting started
Introduction
1.1
1.2
1.3
1.4
F# syntax in 60 seconds
1.5
Learning F#
1.6
Troubleshooting F#
1.7
1.8
1.8.1
1.8.2
1.8.3
1.8.4
1.8.5
2.1
2.1.1
F# syntax in 60 seconds
2.1.2
2.1.3
2.1.4
2.1.5
2.1.6
Conciseness
2.1.7
Type inference
2.1.8
2.1.9
2.1.10
2.1.11
2.1.12
Convenience
2.1.13
2.1.14
Functions as interfaces
2.1.15
Partial Application
2.1.16
Active patterns
2.1.17
Correctness
2.1.18
Immutability
2.1.19
2.1.20
2.1.21
2.1.22
Concurrency
2.1.23
Asynchronous programming
2.1.24
2.1.25
2.1.26
Completeness
2.1.27
2.1.28
2.1.29
2.1.30
Thinking Functionally
The "Thinking Functionally" Series
3.1
3.1.1
Mathematical functions
3.1.2
3.1.3
3.1.4
Currying
3.1.5
Partial application
3.1.6
3.1.7
Defining functions
3.1.8
Function signatures
3.1.9
Organizing functions
3.1.10
3.1.11
2
3.1.12
Understanding F#
The "Expressions and syntax" Series
4.1
4.1.1
4.1.2
Overview of F# expressions
4.1.3
4.1.4
4.1.5
4.1.6
4.1.7
Exceptions
4.1.8
Match expressions
4.1.9
4.1.10
4.1.11
4.1.12
4.2
4.2.1
Overview of types in F#
4.2.2
Type abbreviations
4.2.3
Tuples
4.2.4
Records
4.2.5
Discriminated Unions
4.2.6
4.2.7
Enum types
4.2.8
4.2.9
Units of measure
4.2.10
4.2.11
4.3
4.4
4.4.1
Classes
4.4.2
4.4.3
3
Interfaces
4.4.4
Object expressions
4.4.5
4.5
4.5.1
Understanding continuations
4.5.2
Introducing 'bind'
4.5.3
4.5.4
4.5.5
4.5.6
4.5.7
4.5.8
4.5.9
4.5.10
4.5.11
4.6
4.7
4.7.1
4.7.2
4.7.3
4.8
4.8.1
4.8.2
Functional Design
The "Designing with types" Series
5.1
5.1.1
5.1.2
5.1.3
5.1.4
5.1.5
Constrained strings
5.1.6
Non-string types
5.1.7
5.1.8
4
5.2
5.3
5.3.1
5.3.2
Functional Patterns
How to design and code a complete program
6.1
6.2
6.2.1
6.3
6.3.1
Monoids in practice
6.3.2
6.3.3
6.4
6.4.1
6.4.2
6.4.3
6.4.4
6.5
6.5.1
6.5.2
6.5.3
6.6
6.6.1
Understanding bind
6.6.2
6.6.3
6.6.4
6.6.5
6.6.6
6.6.7
6.7
6.7.1
Catamorphism examples
6.7.2
5
Introducing Folds
6.7.3
Understanding Folds
6.7.4
6.7.5
6.7.6
6.8
6.8.1
6.8.2
6.8.3
Testing
An introduction to property-based testing
7.1
7.2
8.1
8.2
8.3
8.4
8.5
8.6
8.6.1
8.6.2
8.6.3
Enterprise Tic-Tac-Toe
Enterprise Tic-Tac-Toe, part 2
Writing a JSON parser from scratch
8.7
8.7.1
8.8
Other
Ten reasons not to use a statically typed functional programming language
9.1
9.2
9.3
9.4
9.5
9.6
Introduction
About F#
If you are completely new to F#, F# is a general purpose functional/hybrid programming
language which is great for tackling almost any kind of software challenge.
F# is free and open source, and runs on Linux, Mac, Windows and more. To download and
install F#, or to find out more, go to F# Foundation site at fsharp.org.
Getting started
Next, before randomly dipping into the posts, you should read the "why use F#?" page and
then the whole "why use F#" series. After that the "site contents" page provides suggestions
for further reading on functions, types and more.
There is a page with some advice on learning F#, and if you have problems trying to get
your code to compile, the troubleshooting F# page might be helpful.
I will assume that you do not need instruction in the basics of programming and that you are
familiar with C#, Java, or a similar C-like language. It will also be helpful if you are familiar
with the Mono/.NET library.
On the other hand, I will not assume that you have a mathematical or computer science
background. There will be no mathematical notation, and no mysterious concepts like
"functor", "category theory" and "anamorphism". If you are already familiar with Haskell or
ML, this is probably not the place for you!
Introduction
Getting started
Installing and using F# will get you started.
Why use F#? An interactive tour of F#.
Learning F# has tips to help you learn more effectively.
Troubleshooting F# for when you have problems getting your code to compile.
and then you can try...
Twenty six low-risk ways to use F# at work. You can start right now -- no permission
needed!
Tutorials
The following series are tutorials on the key concepts of F#.
Thinking functionally starts from basics and explains how and why functions work the
way they do.
Expressions and syntax covers the common expressions such as pattern matching, and
has a post on indentation.
Understanding F# types explains how to define and use the various types, including
tuples, records, unions, and options.
Designing with types explains how to use types as part of the design process, making
illegal states unrepresentable.
Choosing between collection functions. If you are coming to F# from C#, the large
number of list functions can be overwhelming, so I have written this post to help guide
you to the one you want.
Property-based testing: the lazy programmer's guide to writing 1000's of tests.
Understanding computation expressions demystifies them and shows how you can
create your own.
Functional patterns
These posts explain some core patterns in functional programming -- concepts such as
"map", "bind", monads and more.
Railway Oriented Programming: A functional approach to error handling
State Monad: An introduction to handling state using the tale of Dr Frankenfunctor and
the Monadster.
10
Worked examples
These posts provide detailed worked examples with lots of code!
Designing for correctness: How to make illegal states unrepresentable (a shopping cart
example).
Stack based calculator: Using a simple stack to demonstrate the power of combinators.
Parsing commmand lines: Using pattern matching in conjunction with custom types.
Roman numerals: Another pattern matching example.
Calculator Walkthrough: The type-first approach to designing a Calculator.
Enterprise Tic-Tac-Toe: A walkthrough of the design decisions in a purely functional
implementation
Writing a JSON Parser.
Specific topics in F#
General:
Four key concepts that differentiate F# from a standard imperative language.
Understanding F# indentation.
The downsides of using methods.
Functions:
Currying.
Partial Application.
Control Flow:
Match..with expressions and creating folds to hide the matching.
If-then-else and loops.
11
Exceptions.
Types:
Option Types especially on why None is not the same as null.
Record Types.
Tuple Types.
Discriminated Unions.
Algebraic type sizes and domain modelling.
Controversial posts
Is your programming language unreasonable? or, why predictability is important.
Commentary on 'Roman Numerals Kata with Commentary'. My approach to the Roman
Numerals Kata.
Ten reasons not to use a statically typed functional programming language. A rant
against something I don't get.
We don't need no stinking UML diagrams or, why in many cases, using UML for class
diagrams is not necessary.
12
Although F# is great for specialist areas such as scientific or data analysis, it is also an
excellent choice for enterprise development. Here are five good reasons why you should
consider using F# for your next project.
Conciseness
F# is not cluttered up with coding "noise" such as curly brackets, semicolons and so on.
You almost never have to specify the type of an object, thanks to a powerful type inference
system.
And, compared with C#, it generally takes fewer lines of code to solve the same problem.
// one-liners
[1..100] |> List.sum |> printfn "sum=%d"
// no curly braces, semicolons or parentheses
let square x = x * x
let sq = square 42
// simple types in one line
type Person = {First:string; Last:string}
// complex types in a few lines
type Employee =
| Worker of Person
| Manager of Employee list
// type inference
let jdoe = {First="John";Last="Doe"}
let worker = Worker jdoe
Convenience
Many common programming tasks are much simpler in F#. This includes things like creating
and using complex type definitions, doing list processing, comparison and equality, state
machines, and much more.
And because functions are first class objects, it is very easy to create powerful and reusable
code by creating functions that have other functions as parameters, or that combine existing
functions to create new functionality.
13
Correctness
F# has a powerful type system which prevents many common errors such as null reference
exceptions.
Values are immutable by default, which prevents a large class of errors.
In addition, you can often encode business logic using the type system itself in such a way
that it is actually impossible to write incorrect code or mix up units of measure, greatly
reducing the need for unit tests.
// strict type checking
printfn "print string %s" 123 //compile error
// all values immutable by default
person1.First <- "new name" //assignment error
// never have to check for nulls
let makeNewString str =
//str can always be appended to safely
let newString = str + " new!"
newString
// embed business logic into types
emptyShoppingCart.remove // compile error!
// units of measure
let distance = 10<m> + 10<ft> // error!
Concurrency
14
F# has a number of built-in libraries to help when more than one thing at a time is
happening. Asynchronous programming is very easy, as is parallelism.
F# also has a built-in actor model, and excellent support for event handling and functional
reactive programming.
And of course, because data structures are immutable by default, sharing state and avoiding
locks is much easier.
// easy async logic with "async" keyword
let! result = async {something}
// easy parallelism
Async.Parallel [ for i in 0..40 ->
async { return fib(i) } ]
// message queues
MailboxProcessor.Start(fun inbox-> async{
let! msg = inbox.Receive()
printfn "message is: %s" msg
})
Completeness
Although it is a functional language at heart, F# does support other styles which are not
100% pure, which makes it much easier to interact with the non-pure world of web sites,
databases, other applications, and so on.
In particular, F# is designed as a hybrid functional/OO language, so it can do virtually
everything that C# can do.
Of course, F# is part of the .NET ecosystem, which gives you seamless access to all the
third party .NET libraries and tools. It runs on most platforms, including Linux and smart
phones (via Mono and the new .NET Core).
Finally, it is well integrated with Visual Studio (Windows) and Xamarin (Mac), which means
you get a great IDE with IntelliSense support, a debugger, and many plug-ins for unit tests,
source control, and other development tasks. Or on Linux, you can use the MonoDevelop
IDE instead.
15
16
17
The F# compiler is a free and open source tool which is available for Windows, Mac and
Linux (via Mono). Find out more about F# and how to install it at the F# Foundation.
You can use it with an IDE (Visual Studio, MonoDevelop), or with your favorite editor (VS
Code and Atom have especially good F# support using Ionide), or simply as a standalone
command line compiler.
If you don't want to install anything, you can try the .NET Fiddle site, which is an interactive
environment where you can explore F# in your web browser. You should be able to run most
of the code on this site there.
Shell scripts in F#
You can also use F# as a scripting language, rather than having to compile code into an
EXE. This is done by using the FSI program, which is not only a console but can also be
used to run scripts in the same way that you might use Python or Powershell.
This is very convenient when you want to quickly create some code without compiling it into
a full blown application. The F# build automation system "FAKE" is an example of how
useful this can be.
To see how you can do this yourself, here is a little example script that downloads a web
page to a local file. First create an FSX script file -- call it " ShellScriptExample.fsx " -- and
paste in the following code.
19
// ================================
// Description:
// downloads the given url and stores it as a file with a timestamp
//
// Example command line:
// fsi ShellScriptExample.fsx http://google.com google
// ================================
// "open" brings a .NET namespace into visibility
open System.Net
open System
// download the contents of a web page
let downloadUriToFile url targetfile =
let req = WebRequest.Create(Uri(url))
use resp = req.GetResponse()
use stream = resp.GetResponseStream()
use reader = new IO.StreamReader(stream)
let timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH-mm")
let path = sprintf "%s.%s.html" targetfile timestamp
use writer = new IO.StreamWriter(path)
writer.Write(reader.ReadToEnd())
printfn "finished downloading %s to %s" url path
// Running from FSI, the script name is first, and other args after
match fsi.CommandLineArgs with
| [| scriptName; url; targetfile |] ->
printfn "running script: %s" scriptName
downloadUriToFile url targetfile
| _ ->
printfn "USAGE: [url] [targetfile]"
Don't worry about how the code works right now. It's pretty crude anyway, and a better
example would add error handling, and so on.
To run this script, open a command window in the same directory and type:
fsi ShellScriptExample.fsx http://google.com google_homepage
As you play with the code on this site, you might want to experiment with creating some
scripts at the same time.
20
F# syntax in 60 seconds
F# syntax in 60 seconds
Here is a very quick overview on how to read F# code for newcomers unfamiliar with the
syntax.
It is obviously not very detailed but should be enough so that you can read and get the gist
of the upcoming examples in this series. Don't worry if you don't understand all of it, as I will
give more detailed explanations when we get to the actual code examples.
The two major differences between F# syntax and a standard C-like syntax are:
Curly braces are not used to delimit blocks of code. Instead, indentation is used (Python
is similar this way).
Whitespace is used to separate parameters rather than commas.
Some people find the F# syntax off-putting. If you are one of them, consider this quote:
"Optimising your notation to not confuse people in the first 10 minutes of seeing it but to
hinder readability ever after is a really bad mistake." (David MacIver, via a post about
Scala syntax).
Personally, I think that the F# syntax is very clear and straightforward when you get used to
it. In many ways, it is simpler than the C# syntax, with fewer keywords and special cases.
The example code below is a simple F# script that demonstrates most of the concepts that
you need on a regular basis.
I would encourage you to test this code interactively and play with it a bit! Either:
Type this into a F# script file (with .fsx extension) and send it to the interactive window.
See the "installing and using F#" page for details.
Alternatively, try running this code in the interactive window. Remember to always use
;; at the end to tell the interpreter that you are done entering and ready to evaluate.
21
F# syntax in 60 seconds
22
F# syntax in 60 seconds
23
F# syntax in 60 seconds
And with that, let's start by comparing some simple F# code with the equivalent C# code.
24
Learning F#
Functional languages are very different from standard imperative languages, and can be
quite tricky to get the hang of initially. This page offers some tips on how to learn F#
effectively.
25
Learning F#
play with the examples, and get comfortable with the ideas before you try to start doing
serious coding. If you don't understand how functions and types work, then you will have a
hard time being productive.
26
Learning F#
Troubleshooting
There are a number of extremely common errors that beginners make, and if you are
frustrated about getting your code to compile, please read the "troubleshooting F#" page.
27
Troubleshooting F#
As the saying goes, "if it compiles, it's correct", but it can be extremely frustrating just trying
to get the code to compile at all! So this page is devoted to helping you troubleshoot your F#
code.
I will first present some general advice on troubleshooting and some of the most common
errors that beginners make. After that, I will describe each of the common error messages in
detail, and give examples of how they can occur and how to correct them.
(Jump to the error numbers)
28
Troubleshooting F#
addTwoParams (1,2) // trying to pass a single tuple rather than two args
// error FS0001: This expression was expected to have type
// int but here has type 'a * 'b
The compiler treats (1,2) as a generic tuple, which it attempts to pass to " addTwoParams ".
Then it complains that the first parameter of addTwoParams is an int, and we're trying to pass
a tuple.
If you attempt to pass two arguments to a function expecting one tuple, you will get another
obscure error.
addTuple 1 2 // trying to pass two args rather than one tuple
// error FS0003: This value is not a function and cannot be applied
Troubleshooting F#
The exclamation point symbol is not the "NOT" operator. It is the deferencing operator for
mutable references. If you use it by mistake, you will get the following error:
let y = true
let z = !y
// => error FS0001: This expression was expected to have
// type 'a ref but here has type bool
The correct construction is to use the "not" keyword. Think SQL or VB syntax rather than C
syntax.
let y = true
let z = not y //correct
And for "not equal", use "<>", again like SQL or VB.
let z = 1 <> 2 //correct
Be sure to set your editor to convert tabs to spaces. And watch out if you are pasting code in
from elsewhere. If you do run into persistent problems with a bit of code, try removing the
whitespace and re-adding it.
Troubleshooting F#
If you are trying to create a function pointer or delegate, watch out that you don't accidentally
create a simple value that has already been evaluated.
If you want a parameterless function that you can reuse, you will need to explicitly pass a
unit parameter, or define it as a lambda.
let reader = new System.IO.StringReader("hello")
let nextLineFn = reader.ReadLine() //wrong
let nextLineFn() = reader.ReadLine() //correct
let nextLineFn = fun() -> reader.ReadLine() //correct
let r = new System.Random()
let randomFn = r.Next() //wrong
let randomFn() = r.Next() //correct
let randomFn = fun () -> r.Next() //correct
See the series "thinking functionally" for more discussion of parameterless functions.
F# compiler errors
31
Troubleshooting F#
32
Troubleshooting F#
Error message
Possible causes
This type (function type) does not match the type (simple
type). Note: function types have a arrow in them, like 'a
-> 'b .
E. Straightforward type
mismatch.
F. Inconsistent returns in
branches or matches.
G. Watch out for type
inference effects buried in
a function.
The type (type) does not match the type (other type)
K. Operator precedence
(especially functions and
pipes).
33
Troubleshooting F#
1 + 2.0 //wrong
// => error FS0001: The type 'float' does not match the type 'int'
This issue can also manifest itself in library functions and other places. For example, you
cannot do " average " on a list of ints.
[1..10] |> List.average // wrong
// => error FS0001: The type 'int' does not support any
// operators named 'DivideByInt'
34
Troubleshooting F#
This is particularly common for some .NET library functions that expect a unit parameter,
such as ReadLine above.
The fix is to pass the correct number of parameters. Check the type of the result value to
make sure that it is indeed a simple type. In the ReadLine case, the fix is to pass a ()
argument.
let line = reader.ReadLine() //correct
printfn "The line is %s" line //no compiler error
35
Troubleshooting F#
let g x =
match x with
| 1 -> "hello"
| _ -> 42
// error FS0001: This expression was expected to have type
// string but here has type int
Obviously, the straightforward fix is to make each branch return the same type.
let f x =
if x > 1 then "hello"
else "42"
let g x =
match x with
| 1 -> "hello"
| _ -> "42"
Remember that if an "else" branch is missing, it is assumed to return unit, so the "true"
branch must also return unit.
let f x =
if x > 1 then "hello"
// error FS0001: This expression was expected to have type
// unit but here has type string
If both branches cannot return the same type, you may need to create a new union type that
can contain both types.
type StringOrInt = | S of string | I of int // new union type
let f x =
if x > 1 then S "hello"
else I 42
36
Troubleshooting F#
The fix is to check the function signatures and drill down until you find the guilty party. Also,
use the most generic types possible, and avoid type annotations if possible.
One area where commas are used is when calling .NET library functions. These all take
tuples as arguments, so the comma form is correct. In fact, these calls look just the same as
they would from C#:
// correct
System.String.Compare("a","b")
// incorrect
System.String.Compare "a" "b"
37
Troubleshooting F#
let t1 = (0, 1)
let t2 = (0, "hello")
t1 = t2
// => error FS0001: Type mismatch. Expecting a int * int
// but given a int * string
// The type 'int' does not match the type 'string'
You can get the same issue when pattern matching tuples during binding:
let x,y = 1,2,3
// => error FS0001: Type mismatch. Expecting a 'a * 'b
// but given a 'a * 'b * 'c
// The tuples have differing lengths of 2 and 3
let f (x,y) = x + y
let z = (1,"hello")
let result = f z
// => error FS0001: Type mismatch. Expecting a int * int
// but given a int * string
// The type 'int' does not match the type 'string'
38
Troubleshooting F#
39
Troubleshooting F#
The reason is that " Bind " expects a tuple (wrapper,func) , not two parameters. (Check the
signature for bind in the F# documentation).
The fix is to change the bind function to accept a tuple as its (single) parameter.
type wrapBuilder() =
member this.Bind (wrapper:Wrapper<'a>, func:'a->Wrapper<'b>) =
match wrapper with
| Wrapped(innerThing) -> func innerThing
It can also occur when you do operator overloading, but the operators cannot be used as
prefix or infix.
40
Troubleshooting F#
let (!!) x y = x + y
(!!) 1 2 // ok
1 !! 2 // failed !! cannot be used as an infix operator
// error FS0003: This value is not a function and cannot be applied
The message tells you the problem: "runtime type tests are not allowed on some types".
The answer is to "box" the value which forces it into a reference type, and then you can type
check it:
let detectTypeBoxed v =
match box v with // used "box v"
| :? int -> printfn "this is an int"
| _ -> printfn "something else"
//test
detectTypeBoxed 1
detectTypeBoxed 3.14
41
Troubleshooting F#
Can also occur if you are missing one side of an infix operator:
|| true // error FS0010: Unexpected symbol '||'
false || true // OK
Can also occur if you attempt to send a namespace definition to F# interactive. The
interactive console does not allow namespaces.
namespace Customer // FS0010: Incomplete structured construct
// declare a type
type Person= {First:string; Last:string}
42
Troubleshooting F#
The easy fix is use ignore . But ask yourself why you are using a function and then
throwing away the answer ? it might be a bug.
let something =
2+2 |> ignore // ok
"hello"
This also occurs if you think you writing C# and you accidentally use semicolons to separate
expressions:
// wrong
let result = 2+2; "hello";
// fixed
let result = 2+2 |> ignore; "hello";
43
Troubleshooting F#
With this error, chances are you have confused the assignment operator " <- " for mutable
values, with the equality comparison operator " = ".
// '=' versus '<-'
let add() =
let mutable x = 1
x = x + 1 // warning FS0020
printfn "%d" x
F#'s type inference will cleverly figure out the generic types.
val id : 'a -> 'a
val compose : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c
val opt : 'a option
However in some cases, the F# compiler feels that the code is ambiguous, and, even though
it looks like it is guessing the type correctly, it needs you to be more specific:
let idMap = List.map id // error FS0030
let blankConcat = String.concat "" // error FS0030
Almost always this will be caused by trying to define a partially applied function, and almost
always, the easiest fix is to explicitly add the missing parameter:
44
Troubleshooting F#
45
Troubleshooting F#
type MyResource() =
interface System.IDisposable with
member this.Dispose() = printfn "disposed"
The reason is that when the compiler sees 'fib' in the body, it doesn't know about the function
because it hasn't finished compiling it yet!
The fix is to use the " rec " keyword.
let rec fib i =
match i with
| 1 -> 1
| 2 -> 1
| n -> fib(n-1) + fib(n-2)
46
Troubleshooting F#
Note that this only applies to " let " functions. Member functions do not need this, because
the scope rules are slightly different.
type FibHelper() =
member this.fib i =
match i with
| 1 -> 1
| 2 -> 1
| n -> fib(n-1) + fib(n-2)
If you try to use it the extension, you get the FS0039 error:
let i = 2
let result = i.IsEven
// FS0039: The field, constructor or
// member 'IsEven' is not defined
47
Troubleshooting F#
There a number of ways to fix this. One way is to use an explicit type annotation:
let streamReader filename = new System.IO.StreamReader(filename:string) // OK
You can sometimes use a named parameter to avoid the type annotation:
let streamReader filename = new System.IO.StreamReader(path=filename) // OK
Or you can try to create intermediate objects that help the type inference, again without
needing type annotations:
let streamReader filename =
let fileInfo = System.IO.FileInfo(filename)
new System.IO.StreamReader(fileInfo.FullName) // OK
48
Troubleshooting F#
The compiler does not know what type "x" is, and therefore does not know if " Length " is a
valid method.
There a number of ways to fix this. The crudest way is to provide an explicit type annotation:
let stringLength (x:string) = x.Length // OK
In some cases though, judicious rearrangement of the code can help. For example, the
example below looks like it should work. It's obvious to a human that the List.map function
is being applied to a list of strings, so why does x.Length cause an error?
List.map (fun x -> x.Length) ["hello"; "world"] // Error FS0072
The reason is that the F# compiler is currently a one-pass compiler, and so type information
present later in the program cannot be used if it hasn't been parsed yet.
Yes, you can always explicitly annotate:
List.map (fun x:string -> x.Length) ["hello"; "world"] // OK
But another, more elegant way that will often fix the problem is to rearrange things so the
known types come first, and the compiler can digest them before it moves to the next clause.
["hello"; "world"] |> List.map (fun x -> x.Length) // OK
It's good practice to avoid explicit type annotations, so this approach is best, if it is feasible.
49
Troubleshooting F#
50
So you're all excited about functional programming, and you've been learning F# in your
spare time, and you're annoying your co-workers by ranting about how great it is, and you're
itching to use it for serious stuff at work...
But then you hit a brick wall.
Your workplace has a "C# only" policy and won't let you use F#.
If you work in a typical enterprise environment, getting a new language approved will be a
long drawn out process, involving persuading your teammates, the QA guys, the ops guys,
your boss, your boss's boss, and the mysterious bloke down the hall who you've never
talked to. I would encourage you to start that process (a helpful link for your manager), but
still, you're impatient and thinking "what can I do now?"
On the other hand, perhaps you work in a flexible, easy going place, where you can do what
you like.
But you're conscientious, and don't want to be one of those people who re-write some
mission critical system in APL, and then vanish without trace, leaving your replacement
some mind-bendingly cryptic code to maintain. No, you want to make sure that you are not
doing anything that will affect your team's bus factor.
So in both these scenarios, you want to use F# at work, but you can't (or don't want to) use it
for core application code.
What can you do?
Well, don't worry! This series will suggest a number of ways you can get your hands dirty
with F# in a low-risk, incremental way, without affecting any mission critical code.
Twenty six low-risk ways to use F# at work. You can start right now -- no permission
needed.
Using F# for development and devops scripts. Twenty six low-risk ways to use F# at
work (part 2).
Using F# for testing. Twenty six low-risk ways to use F# at work (part 3).
Using F# for database related tasks. Twenty six low-risk ways to use F# at work (part 4).
Other interesting ways of using F# at work. Twenty six low-risk ways to use F# at work
(part 5).
51
Series contents
Here's a list of the twenty six ways so that you can go straight to any one that you find
particularly interesting.
Part 1 - Using F# to explore and develop interactively
1. Use F# to explore the .NET framework interactively
2. Use F# to test your own code interactively
3. Use F# to play with webservices interactively
52
Getting started
If you're using Visual Studio, you've already got F# installed, so you're ready to go! No need
to ask anyone's permission.
If you're on a Mac or Linux, you will have to a bit of work, alas (instructions for Mac and
Linux).
53
There are two ways to use F# interactively: (1) typing in the F# interactive window directly, or
(2) creating a F# script file (.FSX) and then evaluating code snippets.
To use the F# interactive window in Visual Studio:
1. Show the window with Menu > View > Other Windows > F# Interactive
2. Type an expression, and use double semicolon ( ;; ) to tell the interpreter you're
finished.
For example:
let x = 1
let y = 2
x + y;;
Personally, I prefer to create a script file ( File > New > File then pick "F# script") and type
code there, because you get auto-complete and intellisense.
To run a bit of code, just highlight and right click, or simply do Alt+Enter .
54
55
56
Is GetEnvironmentVariable case-sensitive?
This can be answered with a simple snippet:
Environment.GetEnvironmentVariable "ProgramFiles" =
Environment.GetEnvironmentVariable "PROGRAMFILES"
// answer => true
WARNING: in older versions of F#, opening a reference to your DLL will lock it so that you
can't compile it! In which case, before recompiling, be sure to reset the interactive session to
release the lock. In newer versions of F#, the DLL is shadow-copied, and there is no lock.
Once these libraries are in place, you can use the code below as a skeleton for a simple
WebAPI app.
// sets the current directory to be same as the script directory
System.IO.Directory.SetCurrentDirectory (__SOURCE_DIRECTORY__)
// assumes nuget install Microsoft.AspNet.WebApi.OwinSelfHost has been run
// so that assemblies are available under the current directory
58
#r @"Packages\Owin\lib\net40\Owin.dll"
#r @"Packages\Microsoft.Owin\lib\net40\Microsoft.Owin.dll"
#r @"Packages\Microsoft.Owin.Host.HttpListener\lib\net40\Microsoft.Owin.Host.HttpListe
ner.dll"
#r @"Packages\Microsoft.Owin.Hosting\lib\net40\Microsoft.Owin.Hosting.dll"
#r @"Packages\Microsoft.AspNet.WebApi.Owin\lib\net45\System.Web.Http.Owin.dll"
#r @"Packages\Microsoft.AspNet.WebApi.Core\lib\net45\System.Web.Http.dll"
#r @"Packages\Microsoft.AspNet.WebApi.Client\lib\net45\System.Net.Http.Formatting.dll"
#r @"Packages\Newtonsoft.Json\lib\net40\Newtonsoft.Json.dll"
#r "System.Net.Http.dll"
open System
open Owin
open Microsoft.Owin
open System.Web.Http
open System.Web.Http.Dispatcher
open System.Net.Http.Formatting
module OwinSelfhostSample =
/// a record to return
[<CLIMutable>]
type Greeting = { Text : string }
/// A simple Controller
type GreetingController() =
inherit ApiController()
// GET api/greeting
member this.Get() =
{Text="Hello!"}
/// Another Controller that parses URIs
type ValuesController() =
inherit ApiController()
// GET api/values
member this.Get() =
["value1";"value2"]
// GET api/values/5
member this.Get id =
sprintf "id is %i" id
// POST api/values
member this.Post ([<FromBody>]value:string) =
()
// PUT api/values/5
member this.Put(id:int, [<FromBody>]value:string) =
()
// DELETE api/values/5
59
member this.Delete(id:int) =
()
/// A helper class to store routes, etc.
type ApiRoute = { id : RouteParameter }
/// IMPORTANT: When running interactively, the controllers will not be found with
error:
/// "No type was found that matches the controller named 'XXX'."
/// The fix is to override the ControllerResolver to use the current assembly
type ControllerResolver() =
inherit DefaultHttpControllerTypeResolver()
override this.GetControllerTypes (assembliesResolver:IAssembliesResolver) =
let t = typeof<System.Web.Http.Controllers.IHttpController>
System.Reflection.Assembly.GetExecutingAssembly().GetTypes()
|> Array.filter t.IsAssignableFrom
:> Collections.Generic.ICollection<Type>
/// A class to manage the configuration
type MyHttpConfiguration() as this =
inherit HttpConfiguration()
let configureRoutes() =
this.Routes.MapHttpRoute(
name= "DefaultApi",
routeTemplate= "api/{controller}/{id}",
defaults= { id = RouteParameter.Optional }
) |> ignore
let configureJsonSerialization() =
let jsonSettings = this.Formatters.JsonFormatter.SerializerSettings
jsonSettings.Formatting <- Newtonsoft.Json.Formatting.Indented
jsonSettings.ContractResolver <-
Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
// Here is where the controllers are resolved
let configureServices() =
this.Services.Replace(
typeof<IHttpControllerTypeResolver>,
new ControllerResolver())
do configureRoutes()
do configureJsonSerialization()
do configureServices()
/// Create a startup class using the configuration
type Startup() =
// This code configures Web API. The Startup class is specified as a type
// parameter in the WebApp.Start method.
member this.Configuration (appBuilder:IAppBuilder) =
// Configure Web API for self-host.
60
61
This example is just to demonstrate that you can use the OWIN and WebApi libraries "outof-the-box".
For a more F# friendly web framework, have a look at Suave or WebSharper. There is a lot
more webby stuff at fsharp.org.
62
open System.Windows.Forms
open System.Drawing
let form = new Form(Width= 400, Height = 300, Visible = true, Text = "Hello World")
form.TopMost <- true
form.Click.Add (fun _ ->
form.Text <- sprintf "form clicked at %i" DateTime.Now.Ticks)
form.Show()
And here's the window after clicking, with the title bar changed:
63
That's better!
But the button is cut off, so let's change the flow direction:
panel.FlowDirection <- FlowDirection.TopDown
64
But now the yellow button is not the same width as the green button, which we can fix with
Dock :
As you can see, it is really easy to play around with layouts interactively this way. Once
you're happy with the layout logic, you can convert the code back to C# for your real
application.
This example is WinForms specific. For other UI frameworks the logic would be different, of
course.
So that's the first four suggestions. We're not done yet! The next post will cover using F# for
development and devops scripts.
65
Series contents
Here's a list of shortcuts to the twenty six ways:
Part 1 - Using F# to explore and develop interactively
1. Use F# to explore the .NET framework interactively
2. Use F# to test your own code interactively
3. Use F# to play with webservices interactively
4. Use F# to play with UI's interactively
Part 2 - Using F# for development and devops scripts
5. Use FAKE for build and CI scripts
6. An F# script to check that a website is responding
7. An F# script to convert an RSS feed into CSV
8. An F# script that uses WMI to check the stats of a process
9. Use F# for configuring and managing the cloud
Part 3 - Using F# for testing
10. Use F# to write unit tests with readable names
11. Use F# to run unit tests programmatically
12. Use F# to learn to write unit tests in other ways
13. Use FsCheck to write better unit tests
14. Use FsCheck to create random dummy data
15. Use F# to create mocks
16. Use F# to do automated browser testing
17. Use F# for Behaviour Driven Development
66
67
Debugging F# scripts
A great thing about using F# scripts is that you don't need to create a whole project, nor
launch Visual Studio.
But if you need to debug a script, and you're not in Visual Studio, what can you do? Here are
some tips:
First, you can just use tried and true printing to the console using printfn . I generally
wrap this in a simple log function so that I can turn logging on or off with a flag.
You can use the FsEye tool to inspect and watch variables in an interactive session.
Finally, you can still use the Visual Studio debugger. The trick is to attach the debugger
to the fsi.exe process, and then you can use Debugger.Break to halt at a certain point.
68
Let's start with FAKE, which is a cross platform build automation tool written in F#,
analogous to Ruby's Rake.
FAKE has built-in support for git, NuGet, unit tests, Octopus Deploy, Xamarin and more, and
makes it easy to develop complex scripts with dependencies.
You can even use it with TFS to avoid using XAML.
One reason to use FAKE rather than something like Rake is that you can standardize on
.NET code throughout your tool chain. In theory, you could use NAnt instead, but in practice,
no thanks, because XML. PSake is also a possibility, but more complicated than FAKE, I
think.
You can also use FAKE to remove dependencies on a particular build server. For example,
rather than using TeamCity's integration to run tests and other tasks, you might consider
doing them in FAKE instead, which means you can run full builds without having TeamCity
installed.
Here's an example of a very simple FAKE script, taken from a more detailed example on the
FAKE site.
// Include Fake lib
// Assumes NuGet has been used to fetch the FAKE libraries
#r "packages/FAKE/tools/FakeLib.dll"
open Fake
// Properties
let buildDir = "./build/"
// Targets
Target "Clean" (fun _ ->
CleanDir buildDir
)
Target "Default" (fun _ ->
trace "Hello World from FAKE"
)
// Dependencies
"Clean"
==> "Default"
// start build
RunTargetOrDefault "Default"
The syntax takes a little getting used to, but that effort is well spent.
Some further reading on FAKE:
69
Migrating to FAKE.
Hanselman on FAKE. Many of the comments are from people who are using FAKE
actively.
A NAnt user tries out FAKE.
70
Note that I'm using the Http utilities code in Fsharp.Data , which provides a nice wrapper
around HttpClient . More on HttpUtilities here.
71
Note that the type provider generates intellisense (shown below) to show you the available
properties based on the actual contents of the feed. That's very cool.
72
For more on the XML type provider, see the FSharp.Data pages.
73
Again, using a type provider means that you get intellisense (shown below). Very useful for
the hundreds of WMI options.
74
What's especially nice about using F# for this is that you can do it in micro scripts -- you
don't need any heavy tooling.
75
Summary
I hope you found these suggestions useful. Let me know in the comments if you apply them
in practice.
Next up: using F# for testing.
76
Series contents
Before moving on to the content of the post, here's the full list of the twenty six ways:
Part 1 - Using F# to explore and develop interactively
1. Use F# to explore the .NET framework interactively
2. Use F# to test your own code interactively
3. Use F# to play with webservices interactively
4. Use F# to play with UI's interactively
Part 2 - Using F# for development and devops scripts
5. Use FAKE for build and CI scripts
6. An F# script to check that a website is responding
7. An F# script to convert an RSS feed into CSV
8. An F# script that uses WMI to check the stats of a process
9. Use F# for configuring and managing the cloud
Part 3 - Using F# for testing
10. Use F# to write unit tests with readable names
11. Use F# to run unit tests programmatically
12. Use F# to learn to write unit tests in other ways
13. Use FsCheck to write better unit tests
14. Use FsCheck to create random dummy data
15. Use F# to create mocks
16. Use F# to do automated browser testing
17. Use F# for Behaviour Driven Development
Part 4. Using F# for database related tasks
77
78
[<TestFixture>]
type TestClass() =
[<Test>]
member this.When2IsAddedTo2Expect4() =
Assert.AreEqual(4, 2+2)
As you can see, there's a class with the TestFixture attribute, and a public void method
with the Test attribute. All very standard.
But there are some nice extras you get when you use F# rather than C#. First you can use
the double backtick syntax to create more readable names, and second, you can use let
bound functions in modules rather than classes, which simplifies the code.
[<Test>]
let ``When 2 is added to 2 expect 4``() =
Assert.AreEqual(4, 2+2)
The double backtick syntax makes the test results much easier to read. Here is the output of
the test with a standard class name:
TestClass.When2IsAddedTo2Expect4
Result: Success
So if you want to write test names that are accessible to non-programmers, give F# a go!
79
let add1 x = x + 1
// a simple test using any assertion framework:
// Fuchu's own, Nunit, FsUnit, etc
let ``Assert that add1 is x+1`` x _notUsed =
NUnit.Framework.Assert.AreEqual(x+1, add1 x)
// a single test case with one value
let simpleTest =
testCase "Test with 42" <|
``Assert that add1 is x+1`` 42
// a parameterized test case with one param
let parameterizedTest i =
testCase (sprintf "Test with %i" i) <|
``Assert that add1 is x+1`` i
You can run these tests directly in F# interactive using code like this: run simpleTest .
You can also combine these tests into one or more lists, or hierarchical lists of lists:
// create a hierarchy of tests
// mark it as the start point with the "Tests" attribute
[<Fuchu.Tests>]
let tests =
testList "Test group A" [
simpleTest
testList "Parameterized 1..10" ([1..10] |> List.map parameterizedTest)
testList "Parameterized 11..20" ([11..20] |> List.map parameterizedTest)
]
80
81
|> logger
member this.RunFinished(ex:Exception) =
ex.StackTrace
|> replaceNewline
|> sprintf "Exception occurred: %s"
|> logger
member this.SuiteFinished(result:TestResult) = ()
member this.SuiteStarted(testName:TestName) = ()
member this.TestFinished(result:TestResult)=
result.ResultState
|> sprintf "Result: %O"
|> logger
member this.TestOutput(testOutput:TestOutput) =
testOutput.Text
|> replaceNewline
|> logger
member this.TestStarted(testName:TestName) =
logger ""
testName.FullName
|> replaceNewline
|> logger
member this.UnhandledException(ex:Exception) =
ex.StackTrace
|> replaceNewline
|> sprintf "Unhandled exception occurred: %s"
|> logger
}
82
83
There are also a number of shortcut operators such as =? and >? that allow you to write
your tests even more simply -- no asserts anywhere!
[<Test>]
let ``2 + 2 is 4``() =
let result = 2 + 2
result =? 4
[<Test>]
let ``2 + 2 is bigger than 5``() =
let result = 2 + 2
result >? 5
But the problem with this approach is that it only tests a very specific example. There might
be some edge cases that we haven't thought of.
A much better approach is to find something that must be true for all cases. Then we can
create a test that checks that this something (a "property") is true for all cases, or at least a
large random subset.
For example, in the Roman numeral example, we can say that one property is "all Roman
numerals have at most one 'V' character" or "all Roman numerals have at most three 'X'
characters". We can then construct tests that check this property is indeed true.
This is where FsCheck can help. FsCheck is a framework designed for exactly this kind of
property-based testing. It's written in F# but it works equally well for testing C# code.
So, let's see how we'd use FsCheck for our Roman numerals.
First, we define some properties that we expect to hold for all Roman numerals.
84
Here are the results of the test. You can see that 100 random numbers have been tested,
not just one.
Test that roman numerals have no more than one V
Ok, passed 100 tests.
Test that roman numerals have no more than three Xs
Ok, passed 100 tests.
If we changed the test to be "Test that roman numerals have no more than TWO Xs", then
the test result is false, and looks like this:
85
In other words, after generating 33 different inputs, FsCheck has correctly found a number
(30) that does not meet the required property. Very nice!
86
87
let generateName() =
FsCheck.Gen.elements possibleNames
// generate a random EmailAddress by combining random users and domains
let generateEmail() =
let userGen = FsCheck.Gen.elements ["a"; "b"; "c"; "d"; "e"; "f"]
let domainGen = FsCheck.Gen.elements ["gmail.com"; "example.com"; "outlook.com"]
let makeEmail u d = sprintf "%s@%s" u d |> EmailAddress
FsCheck.Gen.map2 makeEmail userGen domainGen
// generate a random PhoneNumber
let generatePhone() =
let areaGen = FsCheck.Gen.choose(100,999)
let n1Gen = FsCheck.Gen.choose(1,999)
let n2Gen = FsCheck.Gen.choose(1,9999)
let makeNumber area n1 n2 = sprintf "(%03i)%03i-%04i" area n1 n2 |> PhoneNumber
FsCheck.Gen.map3 makeNumber areaGen n1Gen n2Gen
// generate a random birthdate
let generateDate() =
let minDate = DateTime(1920,1,1).ToOADate() |> int
let maxDate = DateTime(2014,1,1).ToOADate() |> int
let oaDateGen = FsCheck.Gen.choose(minDate,maxDate)
let makeDate oaDate = float oaDate |> DateTime.FromOADate
FsCheck.Gen.map makeDate oaDateGen
// a function to create a customer
let createCustomer name email phone birthdate =
{name=name; email=email; phone=phone; birthdate=birthdate}
// use applicatives to create a customer generator
let generateCustomer =
createCustomer
<!> generateName()
<*> generateEmail()
<*> generatePhone()
<*> generateDate()
[<Test>]
let printRandomCustomers() =
let size = 0
let count = 10
let data = FsCheck.Gen.sample size count generateCustomer
// print it
data |> List.iter (printfn "%A")
88
89
// Foq Method
let mock =
Mock<IFoo>()
.Setup(fun foo -> <@ foo.DoSomething("ping") @>).Returns(true)
.Create()
// Foq Matching Arguments
mock.Setup(fun foo -> <@ foo.DoSomething(any()) @>).Returns(true)
// Foq Property
mock.Setup(fun foo -> <@ foo.Name @>).Returns("bar")
90
If you are using BDD already with .NET, you're probably using SpecFlow or similar.
91
You should consider using TickSpec instead because, as with all things F#, the syntax is
much more lightweight.
For example, here's the full implementation of the scenario above.
type StockItem = { Count : int }
let mutable stockItem = { Count = 0 }
let [<Given>] ``a customer buys a black jumper`` () =
()
let [<Given>] ``I have (.*) black jumpers left in stock`` (n:int) =
stockItem <- { stockItem with Count = n }
let [<When>] ``they return the jumper for a refund`` () =
stockItem <- { stockItem with Count = stockItem.Count + 1 }
let [<Then>] ``I should have (.*) black jumpers in stock`` (n:int) =
let passed = (stockItem.Count = n)
Assert.True(passed)
The C# equivalent has a lot more clutter, and the lack of double backtick syntax really hurts:
[Given(@"a customer buys a black jumper")]
public void GivenACustomerBuysABlackJumper()
{
// code
}
[Given(@"I have (.*) black jumpers left in stock")]
public void GivenIHaveNBlackJumpersLeftInStock(int n)
{
// code
}
Summary of testing in F#
You can of course combine all the test techniques we've seen so far (as this slide deck
demonstrates):
Unit tests (FsUnit, Unquote) and property-based tests (FsCheck).
Automated acceptance tests (or at least a smoke test) written in BDD (TickSpec) driven
by browser automation (Canopy).
92
93
Series contents
Before moving on to the content of the post, here's the full list of the twenty six ways:
Part 1 - Using F# to explore and develop interactively
1. Use F# to explore the .NET framework interactively
2. Use F# to test your own code interactively
3. Use F# to play with webservices interactively
4. Use F# to play with UI's interactively
Part 2 - Using F# for development and devops scripts
5. Use FAKE for build and CI scripts
6. An F# script to check that a website is responding
7. An F# script to convert an RSS feed into CSV
8. An F# script that uses WMI to check the stats of a process
9. Use F# for configuring and managing the cloud
Part 3 - Using F# for testing
10. Use F# to write unit tests with readable names
11. Use F# to run unit tests programmatically
12. Use F# to learn to write unit tests in other ways
13. Use FsCheck to write better unit tests
14. Use FsCheck to create random dummy data
15. Use F# to create mocks
16. Use F# to do automated browser testing
17. Use F# for Behaviour Driven Development
Part 4. Using F# for database related tasks
94
95
Getting set up
The code for this section is available on github. In there, there are some SQL scripts to
create the sample database, tables and stored procs that I'll use in these examples.
To run the examples, then, you'll need SQL Express or SQL Server running locally or
somewhere accessible, with the relevant setup scripts having been run.
96
[<Literal>]
let connectionString = "Data Source=localhost; Initial Catalog=SqlInFsharp; Integrated
Security=True;"
type Sql = SqlDataConnection<connectionString>
let db = Sql.GetDataContext()
// find the number of customers with a gmail domain
query {
for c in db.Customer do
where (c.Email.EndsWith("gmail.com"))
select c
count
}
If you want to see what SQL code is generated, you can turn logging on, of course:
// optional, turn logging on
db.DataContext.Log <- Console.Out
You can also do more complicated things, such as using subqueries. Here's an example
from MSDN:
Note that, as befitting a functional approach, queries are nice and composable.
// Find students who have signed up at least one course.
query {
for student in db.Student do
where (query { for courseSelection in db.CourseSelection do
exists (courseSelection.StudentID = student.StudentID) })
select student
}
And if the SQL engine doesn't support certain functions such as regexes, and assuming the
size of the data is not too large, you can just stream the data out and do the processing in
F#.
97
// find the most popular domain for people born in each decade
let getDomain email =
Regex.Match(email,".*@(.*)").Groups.[1].Value
let getDecade (birthdate:Nullable<DateTime>) =
if birthdate.HasValue then
birthdate.Value.Year / 10 * 10 |> Some
else
None
let topDomain list =
list
|> Seq.distinct
|> Seq.head
|> snd
db.Customer
|> Seq.map (fun c -> getDecade c.Birthdate, getDomain c.Email)
|> Seq.groupBy fst
|> Seq.sortBy fst
|> Seq.map (fun (decade, group) -> (decade,topDomain group))
|> Seq.iter (printfn "%A")
As you can see from the code above, the nice thing about doing the processing in F# is that
you can define helper functions separately and connect them together easily.
98
module DbLib
[<Literal>]
let connectionString = "Data Source=localhost; Initial Catalog=SqlInFsharp;Integrated
Security=True;"
type Sql = SqlDataConnection<connectionString>
let removeExistingData (db:DbContext) =
let truncateTable name =
sprintf "TRUNCATE TABLE %s" name
|> db.DataContext.ExecuteCommand
|> ignore
["Customer"; "CustomerImport"]
|> List.iter truncateTable
let insertReferenceData (db:DbContext) =
[ "US","United States";
"GB","United Kingdom" ]
|> List.iter (fun (code,name) ->
let c = new Sql.ServiceTypes.Country()
c.IsoCode <- code; c.CountryName <- name
db.Country.InsertOnSubmit c
)
db.DataContext.SubmitChanges()
// removes all data and restores db to known starting point
let resetDatabase() =
use db = Sql.GetDataContext()
removeExistingData db
insertReferenceData db
Now I can write a unit test, using NUnit say, just like any other unit test.
Assume that we have Customer table, and a sproc called up_Customer_Upsert that either
inserts a new customer or updates an existing one, depending on whether the passed in
customer id is null or not.
Here's what a test looks like:
99
[<Test>]
let ``When upsert customer called with null id, expect customer created with new id``(
) =
DbLib.resetDatabase()
use db = DbLib.Sql.GetDataContext()
// create customer
let newId = db.Up_Customer_Upsert(Nullable(),"Alice","x@example.com",Nullable())
// check new id
Assert.Greater(newId,0)
// check one customer exists
let customerCount = db.Customer |> Seq.length
Assert.AreEqual(1,customerCount)
Note that, because the setup is expensive, I do multiple asserts in the test. This could be
refactored if you find this too ugly!
Here's one that tests that updates work:
[<Test>]
let ``When upsert customer called with existing id, expect customer updated``() =
DbLib.resetDatabase()
use db = DbLib.Sql.GetDataContext()
// create customer
let custId = db.Up_Customer_Upsert(Nullable(),"Alice","x@example.com",Nullable())
// update customer
let newId = db.Up_Customer_Upsert(Nullable custId,"Bob","y@example.com",Nullable()
)
// check id hasnt changed
Assert.AreEqual(custId,newId)
// check still only one customer
let customerCount = db.Customer |> Seq.length
Assert.AreEqual(1,customerCount)
// check customer columns are updated
let customer = db.Customer |> Seq.head
Assert.AreEqual("Bob",customer.Name)
100
[<Test>]
let ``When upsert customer called with blank name, expect validation error``() =
DbLib.resetDatabase()
use db = DbLib.Sql.GetDataContext()
try
// try to create customer will a blank name
db.Up_Customer_Upsert(Nullable(),"","x@example.com",Nullable()) |> ignore
Assert.Fail("expecting a SqlException")
with
| :? System.Data.SqlClient.SqlException as ex ->
Assert.That(ex.Message,Is.StringContaining("@Name"))
Assert.That(ex.Message,Is.StringContaining("blank"))
Using the same code as before, we can then generate random instances of
CustomerImport .
101
[<Literal>]
let connectionString = "Data Source=localhost; Initial Catalog=SqlInFsharp; Integrated
Security=True;"
type Sql = SqlDataConnection<connectionString>
// a list of names to sample
let possibleFirstNames =
["Merissa";"Kenneth";"Zora";"Oren"]
let possibleLastNames =
["Applewhite";"Feliz";"Abdulla";"Strunk"]
// generate a random name by picking from the list at random
let generateFirstName() =
FsCheck.Gen.elements possibleFirstNames
let generateLastName() =
FsCheck.Gen.elements possibleLastNames
// generate a random email address by combining random users and domains
let generateEmail() =
let userGen = FsCheck.Gen.elements ["a"; "b"; "c"; "d"; "e"; "f"]
let domainGen = FsCheck.Gen.elements ["gmail.com"; "example.com"; "outlook.com"]
let makeEmail u d = sprintf "%s@%s" u d
FsCheck.Gen.map2 makeEmail userGen domainGen
So far so good.
Now we get to the age column, which is nullable. This means we can't generate random
int s, but instead we have to generate random Nullable<int> s. This is where type
checking is really useful -- the compiler has forced us to take that into account. So to make
sure we cover all the bases, we'll generate a null value one time out of twenty.
// Generate a random nullable age.
// Note that because age is nullable,
// the compiler forces us to take that into account
let generateAge() =
let nonNullAgeGenerator =
FsCheck.Gen.choose(1,99)
|> FsCheck.Gen.map (fun age -> Nullable age)
let nullAgeGenerator =
FsCheck.Gen.constant (Nullable())
// 19 out of 20 times choose a non null age
FsCheck.Gen.frequency [
(19,nonNullAgeGenerator)
(1,nullAgeGenerator)
]
102
Putting it altogether...
// a function to create a customer
let createCustomerImport first last email age =
let c = new Sql.ServiceTypes.CustomerImport()
c.FirstName <- first
c.LastName <- last
c.EmailAddress <- email
c.Age <- age
c //return new record
// use applicatives to create a customer generator
let generateCustomerImport =
createCustomerImport
<!> generateFirstName()
<*> generateLastName()
<*> generateEmail()
<*> generateAge()
Once we have a random generator, we can fetch as many records as we like, and insert
them using the type provider.
In the code below, we'll generate 10,000 records, hitting the database in batches of 1,000
records.
let insertAll() =
use db = Sql.GetDataContext()
// optional, turn logging on or off
// db.DataContext.Log <- Console.Out
// db.DataContext.Log <- null
let insertOne counter customer =
db.CustomerImport.InsertOnSubmit customer
// do in batches of 1000
if counter % 1000 = 0 then
db.DataContext.SubmitChanges()
// generate the records
let count = 10000
let generator = FsCheck.Gen.sample 0 count generateCustomerImport
// insert the records
generator |> List.iteri insertOne
db.DataContext.SubmitChanges() // commit any remaining
103
#time
insertAll()
#time
It's not as fast as using BCP, but it is plenty adequate for testing. For example, it only takes a
few seconds to create the 10,000 records above.
I want to stress that this is a single standalone script, not a heavy binary, so it is really easy
to tweak and run on demand.
And of course you get all the goodness of a scripted approach, such as being able to store it
in source control, track changes, etc.
But the system we're importing from has a different format, like this:
CREATE TABLE dbo.CustomerImport (
CustomerId int NOT NULL IDENTITY(1,1)
,FirstName varchar(50) NOT NULL
,LastName varchar(50) NOT NULL
,EmailAddress varchar(50) NOT NULL
,Age int NULL
)
104
With this transform in place, the rest of the code is easy, we just just read from the source
and write to the target.
105
let transferAll() =
use sourceDb = SourceSql.GetDataContext()
use targetDb = TargetSql.GetDataContext()
let insertOne counter customer =
targetDb.Customer.InsertOnSubmit customer
// do in batches of 1000
if counter % 1000 = 0 then
targetDb.DataContext.SubmitChanges()
printfn "...%i records transferred" counter
// get the sequence of source records
sourceDb.CustomerImport
// transform to a target record
|> Seq.map makeTargetCustomer
// and insert
|> Seq.iteri insertOne
targetDb.DataContext.SubmitChanges() // commit any remaining
printfn "Done"
Because these are sequence operations, only one record at a time is in memory (excepting
the LINQ submit buffer), so even large data sets can be processed.
To see it in use, first insert a number of records using the dummy data script just discussed,
and then run the transfer as follows:
#time
transferAll()
#time
106
Alas, there are often subtle differences between dev, test and production environments:
connection strings, authorization, alerts, log configuration, etc.
That naturally leads to the problem of trying to keep three different copies of a script around,
which in turn makes you think: why not have one script and parameterize it for the
environment?
But now you are dealing with lots of ugly SQL code! The scripts that create SQL agent jobs
are typically hundreds of lines long and were not really designed to be maintained by hand.
F# to the rescue!
In F#, it's really easy to create some simple record types that store all the data you need to
generate and configure a job.
For example, in the script below:
I created a union type called Step that could store a Package , Executable ,
Powershell and so on.
Each of these step types in turn have their own specific properties, so that a Package
has a name and variables, and so on.
A JobInfo consists of a name plus a list of Step s.
An agent script is generated from a JobInfo plus a set of global properties associated
with an environment, such as the databases, shared folder locations, etc.
let thisDir = __SOURCE_DIRECTORY__
System.IO.Directory.SetCurrentDirectory (thisDir)
#load @"..\..\SqlAgentLibrary.Lib.fsx"
module MySqlAgentJob =
open SqlAgentLibrary.Lib.SqlAgentLibrary
let PackageFolder = @"\shared\etl\MyJob"
let step1 = Package {
Name = "An SSIS package"
Package = "AnSsisPackage.dtsx"
Variables =
[
"EtlServer", "EtlServer"
"EtlDatabase", "EtlDatabase"
"SsisLogServer", "SsisLogServer"
"SsisLogDatabase", "SsisLogDatabase"
]
}
let step2 = Package {
107
108
let generateJob() =
MySqlAgentJob.generate globals
DevEnvironment.generateJob()
I can't share the actual F# code, but I think you get the idea. It's quite simple to create.
Once we have these .FSX files, we can generate the real SQL Agent scripts en-masse and
then deploy them to the appropriate servers.
Below is an example of a SQL Agent script that might be generated automatically from the
.FSX file.
As you can see, it is a nicely laid out and formatted T-SQL script. The idea is that a DBA can
review it and be confident that no magic is happening, and thus be willing to accept it as
input.
On the other hand, it would be risky to maintain scripts like. Editing the SQL code directly
could be risky. Better to use type-checked (and more concise) F# code than untyped T-SQL!
USE [msdb]
GO
-- =====================================================
-- Script that deletes and recreates the SQL Agent job 'My SqlAgent Job'
--
-- The job steps are:
-- 1) An SSIS package
-- {Continue on error=false}
-- 2) Another SSIS package
-- {Continue on error=false}
-- =====================================================
-- =====================================================
-- Environment is DEV
--
-- The other global variables are:
-- Database = mydatabase
-- EtlDatabase = etl_config
-- EtlServer = localhost
-- JobName = My SqlAgent Job
-- JobServer = (local)
-- PackageFolder = \\shared\etl\MyJob\
-- Server = localhost
-- SetEndFlag = 0
-- SetStartFlag = 2
-- SsisLogDatabase = etl_config
109
-- SsisLogServer = localhost
-- =====================================================
-- =====================================================
-- Create job
-- =====================================================
-- ---------------------------------------------- Delete Job if it exists
-- --------------------------------------------IF EXISTS (SELECT job_id FROM msdb.dbo.sysjobs_view WHERE name = 'My SqlAgent Job')
BEGIN
PRINT 'Deleting job "My SqlAgent Job"'
EXEC msdb.dbo.sp_delete_job @job_name='My SqlAgent Job', @delete_unused_schedule=0
END
-- ---------------------------------------------- Create Job
-- --------------------------------------------BEGIN TRANSACTION
DECLARE @ReturnCode INT
SELECT @ReturnCode = 0
-- ---------------------------------------------- Create Category if needed
-- --------------------------------------------IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name='ETL' AND category_c
lass=1)
BEGIN
PRINT 'Creating category "ETL"'
EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name='
ETL'
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
END
-- ---------------------------------------------- Create Job
-- --------------------------------------------DECLARE @jobId BINARY(16)
PRINT 'Creating job "My SqlAgent Job"'
EXEC @ReturnCode = msdb.dbo.sp_add_job @job_name='My SqlAgent Job',
@enabled=1,
@category_name='ETL',
@owner_login_name=N'sa',
@description='Copy data from one place to another',
@job_id = @jobId OUTPUT
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
110
111
PRINT 'Done!'
COMMIT TRANSACTION
GOTO EndSave
QuitWithRollback:
IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION
EndSave:
GO
Summary
I hope that this set of suggestions has thrown a new light on what F# can be used for.
In my opinion, the combination of concise syntax, lightweight scripting (no binaries) and SQL
type providers makes F# incredibly useful for database related tasks.
Please leave a comment and let me know what you think.
112
Series contents
Before moving on to the content of the post, here's the full list of the twenty six ways:
Part 1 - Using F# to explore and develop interactively
1. Use F# to explore the .NET framework interactively
2. Use F# to test your own code interactively
3. Use F# to play with webservices interactively
4. Use F# to play with UI's interactively
Part 2 - Using F# for development and devops scripts
5. Use FAKE for build and CI scripts
6. An F# script to check that a website is responding
7. An F# script to convert an RSS feed into CSV
8. An F# script that uses WMI to check the stats of a process
9. Use F# for configuring and managing the cloud
Part 3 - Using F# for testing
10. Use F# to write unit tests with readable names
11. Use F# to run unit tests programmatically
12. Use F# to learn to write unit tests in other ways
13. Use FsCheck to write better unit tests
14. Use FsCheck to create random dummy data
15. Use F# to create mocks
16. Use F# to do automated browser testing
17. Use F# for Behaviour Driven Development
Part 4. Using F# for database related tasks
18. Use F# to replace LINQpad
19. Use F# to unit test stored procedures
20. Use FsCheck to generate random database records
113
114
For more complicating parsing tasks, I highly recommend using FParsec, which is perfectly
suited for this kind of thing. For example, it has been used for parsing search queries for
FogCreek, CSV files, chess notation, and a custom DSL for load testing scenarios.
The code to generate the diagram itself was short, only about 60 lines, which you can see
here.
As an alternative to GraphViz, you could also consider using FSGraph.
For more mathematical or data-centric visualizations, there are a number of good libraries:
FSharp.Charting for desktop visualizations that is well integrated with F# scripting.
FsPlot for interactive visualizations in HTML.
VegaHub, an F# library for working with Vega
F# for Visualization
And finally, there's the 800 lb gorilla -- Excel.
115
Using the built-in capabilities of Excel is great, if it is available. And F# scripting plays well
with Excel.
You can chart in Excel, plot functions in Excel, and for even more power and integration, you
have the FCell and Excel-DNA projects.
Freebase
The code for this section is available on github.
Freebase is a large collaborative knowledge base and online collection of structured data
harvested from many sources.
To get started, just link in the type provider DLL as we have seen before.
The site is throttled, so you'll probably need an API key if you're using it a lot (api details
here)
116
Once the type provider is loaded, you can start asking questions, such as...
"Who are the US presidents?"
data.Society.Government.``US Presidents``
|> Seq.map (fun p -> p.``President number`` |> Seq.head, p.Name)
|> Seq.sortBy fst
|> Seq.iter (fun (n,name) -> printfn "%s was number %i" name n )
Result:
George Washington was number 1
John Adams was number 2
Thomas Jefferson was number 3
James Madison was number 4
James Monroe was number 5
John Quincy Adams was number 6
...
Ronald Reagan was number 40
George H. W. Bush was number 41
Bill Clinton was number 42
George W. Bush was number 43
Barack Obama was number 44
117
118
World Bank
The code for this section is available on github.
On the other extreme from Freebase is the World Bank Open Data, which has lots of
detailed economic and social information from around the world.
The setup is identical to Freebase, but no API key is needed.
// sets the current directory to be same as the script directory
System.IO.Directory.SetCurrentDirectory (__SOURCE_DIRECTORY__)
// Requires FSharp.Data under script directory
// nuget install FSharp.Data -o Packages -ExcludeVersion
#r @"Packages\FSharp.Data\lib\net40\FSharp.Data.dll"
open FSharp.Data
let data = WorldBankData.GetDataContext()
With the type provider set up, we can do a serious query, such as:
"How do malnutrition rates compare between low income and high income countries?"
119
120
European Union -- Maternal mortality ratio (modeled estimate, per 100,000 live births)
2010 9.0
United Kingdom -- Maternal mortality ratio (modeled estimate, per 100,000 live births)
2010 12.0
United States -- Maternal mortality ratio (modeled estimate, per 100,000 live births)
2010 21.0
Series summary
Phew! That was a long list of examples and a lot of code to look at. If you've made it to the
end, congratulations!
121
I hope that this has given you some new insights into the value of F#. It's not just a math-y or
financial language -- it's a practical one too. And it can help you with all sorts of things in
your development, testing, and data management workflows.
Finally, as I have stressed throughout this series, all these uses are safe, low risk and
incremental. What's the worst that can happen?
So go on, persuade your team mates and boss to give F# a try, and let me know how it
goes.
Postscript
After I posted this, Simon Cousins tweeted that I missed one -- I can't resist adding it.
@ScottWlaschin 27: balance the generation schedule for the uk power station fleet.
seriously, the alternative to #fsharp was way too risky
Simon Cousins (@simontcousins) April 25, 2014
You can read more about Simon's real-world of use of F# (for power generation) on his blog.
There are more testimonials to F# at fsharp.org.
122
This series of posts will give you a guided tour through the main features of F# and then
show you ways that F# can help you in your day-to-day development.
Introduction to the 'Why use F#' series. An overview of the benefits of F#.
F# syntax in 60 seconds. A very quick overview on how to read F# code.
Comparing F# with C#: A simple sum. In which we attempt to sum the squares from 1 to
N without using a loop.
Comparing F# with C#: Sorting. In which we see that F# is more declarative than C#,
and we are introduced to pattern matching..
Comparing F# with C#: Downloading a web page. In which we see that F# excels at
callbacks, and we are introduced to the 'use' keyword.
Four Key Concepts. The concepts that differentiate F# from a standard imperative
language.
Conciseness. Why is conciseness important?.
Type inference. How to avoid getting distracted by complex type syntax.
Low overhead type definitions. No penalty for making new types.
Using functions to extract boilerplate code. The functional approach to the DRY
principle.
Using functions as building blocks. Function composition and mini-languages make
code more readable.
Pattern matching for conciseness. Pattern matching can match and bind in a single
step.
Convenience. Features that reduce programming drudgery and boilerplate code.
Out-of-the-box behavior for types. Immutability and built-in equality with no coding.
Functions as interfaces. OO design patterns can be trivial when functions are used.
Partial Application. How to fix some of a function's parameters.
Active patterns. Dynamic patterns for powerful matching.
Correctness. How to write 'compile time unit tests'.
Immutability. Making your code predictable.
Exhaustive pattern matching. A powerful technique to ensure correctness.
Using the type system to ensure correct code. In F# the type system is your friend, not
your enemy.
Worked example: Designing for correctness. How to make illegal states
unrepresentable.
Concurrency. The next major revolution in how we write software?.
Asynchronous programming. Encapsulating a background task with the Async class.
Messages and Agents. Making it easier to think about concurrency.
Functional Reactive Programming. Turning events into streams.
Completeness. F# is part of the whole .NET ecosystem.
Seamless interoperation with .NET libraries. Some convenient features for working with
.NET libraries.
123
124
125
In the rest of this series of posts, I will try to demonstrate each of these F# benefits, using
standalone snippets of F# code (and often with C# code for comparison). I'll briefly cover all
the major features of F#, including pattern matching, function composition, and concurrent
programming. By the time you have finished this series, I hope that you will have been
impressed with the power and elegance of F#, and you will be encouraged to use it for your
next project!
126
The mysterious looking |> is called the pipe operator. It just pipes the output of one
expression into the input of the next. So the code for sumOfSquares reads as:
1. Create a list of 1 to n (square brackets construct a list).
2. Pipe the list into the library function called List.map , transforming the input list into an
output list using the "square" function we just defined.
3. Pipe the resulting list of squares into the library function called List.sum . Can you
guess what it does?
4. There is no explicit "return" statement. The output of List.sum is the overall result of
the function.
Next, here's a C# implementation using the classic (non-functional) style of a C-based
language. (A more functional version using LINQ is discussed later.)
127
Less code
The most obvious difference is that there is a lot more C# code. 13 C# lines compared with 3
F# lines (ignoring comments). The C# code has lots of "noise", things like curly braces,
semicolons, etc. And in C# the functions cannot stand alone, but need to be added to some
class ("SumOfSquaresHelper"). F# uses whitespace instead of parentheses, needs no line
terminator, and the functions can stand alone.
In F# it is common for entire functions to be written on one line, as the "square" function is.
The sumOfSquares function could also have been written on one line. In C# this is normally
frowned upon as bad practice.
When a function does have multiple lines, F# uses indentation to indicate a block of code,
which eliminates the need for braces. (If you have ever used Python, this is the same idea).
So the sumOfSquares function could also have been written this way:
let sumOfSquares n =
[1..n]
|> List.map square
|> List.sum
128
The only drawback is that you have to indent your code carefully. Personally, I think it is
worth the trade-off.
No type declarations
The next difference is that the C# code has to explicitly declare all the types used. For
example, the int i parameter and int SumOfSquares return type. Yes, C# does allow you
to use the "var" keyword in many places, but not for parameters and return types of
functions.
In the F# code we didn't declare any types at all. This is an important point: F# looks like an
untyped language, but it is actually just as type-safe as C#, in fact, even more so! F# uses a
technique called "type inference" to infer the types you are using from their context. It works
amazingly very well most of the time, and reduces the code complexity immensely.
In this case, the type inference algorithm notes that we started with a list of integers. That in
turn implies that the square function and the sum function must be taking ints as well, and
that the final value must be an int. You can see what the inferred types are by looking at the
result of the compilation in the interactive window. You'll see something like:
val square : int -> int
which means that the "square" function takes an int and returns an int.
If the original list had used floats instead, the type inference system would have deduced
that the square function used floats instead. Try it and see:
// define the square function
let squareF x = x * x
// define the sumOfSquares function
let sumOfSquaresF n =
[1.0 .. n] |> List.map squareF |> List.sum // "1.0" is a float
sumOfSquaresF 100.0
The type checking is very strict! If you try using a list of floats ( [1.0..n] ) in the original
sumOfSquares example, or a list of ints ( [1 ..n] ) in the sumOfSquaresF example, you will
Interactive development
129
Finally, F# has an interactive window where you can test the code immediately and play
around with it. In C# there is no easy way to do this.
For example, I can write my square function and immediately test it:
// define the square function
let square x = x * x
// test
let s2 = square 2
let s3 = square 3
let s4 = square 4
When I am satisfied that it works, I can move on to the next bit of code.
This kind of interactivity encourages an incremental approach to coding that can become
addictive!
Furthermore, many people claim that designing code interactively enforces good design
practices such as decoupling and explicit dependencies, and therefore, code that is suitable
for interactive evaluation will also be code that is easy to test. Conversely, code that cannot
be tested interactively will probably be hard to test as well.
However, in addition to the noise of the curly braces and periods and semicolons, the C#
version needs to declare the parameter and return types, unlike the F# version.
130
Many C# developers may find this a trivial example, but still resort back to loops when the
logic becomes more complicated. In F# though, you will almost never see explicit loops like
this. See for example, this post on eliminating boilerplate from more complicated loops.
131
Note that this is a simplified algorithm and is not optimized (and it does not sort in place, like
a true quicksort); we want to focus on clarity for now.
Here is the code in F#:
let rec quicksort list =
match list with
| [] -> // If the list is empty
[] // return an empty list
| firstElem::otherElements -> // If the list is not empty
let smallerElements = // extract the smaller ones
otherElements
|> List.filter (fun e -> e < firstElem)
|> quicksort // and sort them
let largerElements = // extract the large ones
otherElements
|> List.filter (fun e -> e >= firstElem)
|> quicksort // and sort them
// Combine the 3 parts into a new list and return it
List.concat [smallerElements; [firstElem]; largerElements]
//test
printfn "%A" (quicksort [1;5;23;18;9;1;3])
132
Again note that this is not an optimized implementation, but is designed to mirror the
algorithm closely.
Let's go through this code:
There are no type declarations anywhere. This function will work on any list that has
comparable items (which is almost all F# types, because they automatically have a
default comparison function).
The whole function is recursive -- this is signaled to the compiler using the rec
keyword in " let rec quicksort list = ".
The match..with is sort of like a switch/case statement. Each branch to test is signaled
with a vertical bar, like so:
match x with
| caseA -> something
| caseB -> somethingElse
The " match " with [] matches an empty list, and returns an empty list.
The " match " with firstElem::otherElements does two things.
First, it only matches a non-empty list.
Second, it creates two new values automatically. One for the first element called
" firstElem ", and one for the rest of the list, called " otherElements ". In C# terms,
this is like having a "switch" statement that not only branches, but does variable
declaration and assignment at the same time.
The -> is sort of like a lambda ( => ) in C#. The equivalent C# lambda would look
something like (firstElem, otherElements) => do something .
The " smallerElements " section takes the rest of the list, filters it against the first
element using an inline lambda expression with the " < " operator and then pipes the
result into the quicksort function recursively.
The " largerElements " line does the same thing, except using the " >= " operator
Finally the resulting list is constructed using the list concatenation function
" List.concat ". For this to work, the first element needs to be put into a list, which is
what the square brackets are for.
Again note there is no "return" keyword; the last value will be returned. In the " [] "
branch the return value is the empty list, and in the main branch, it is the newly
constructed list.
For comparison here is an old-style C# implementation (without using LINQ).
133
Comparing the two sets of code, again we can see that the F# code is much more compact,
with less noise and no need for type declarations.
Furthermore, the F# code reads almost exactly like the actual algorithm, unlike the C# code.
This is another key advantage of F# -- the code is generally more declarative ("what to do")
and less imperative ("how to do it") than C#, and is therefore much more self-documenting.
A functional implementation in C#
134
This is much cleaner, and reads almost the same as the F# version. But unfortunately there
is no way of avoiding the extra noise in the function signature.
Correctness
Finally, a beneficial side-effect of this compactness is that F# code often works the first time,
while the C# code may require more debugging.
Indeed, when coding these samples, the old-style C# code was incorrect initially, and
required some debugging to get it right. Particularly tricky areas were the for loop (starting
at 1 not zero) and the CompareTo comparison (which I got the wrong way round), and it
135
would also be very easy to accidentally modify the inbound list. The functional style in the
second C# example is not only cleaner but was easier to code correctly.
But even the functional C# version has drawbacks compared to the F# version. For
example, because F# uses pattern matching, it is not possible to branch to the "non-empty
list" case with an empty list. On the other hand, in the C# code, if we forgot the test:
if (values == null || !values.Any()) ...
would fail with an exception. The compiler cannot enforce this for you. In your own code,
how often have you used FirstOrDefault rather than First because you are writing
"defensive" code. Here is an example of a code pattern that is very common in C# but is rare
in F#:
var item = values.FirstOrDefault(); // instead of .First()
if (item != null)
{
// do something if item is valid
}
The one-step "pattern match and branch" in F# allows you to avoid this in many cases.
Postscript
The example implementation in F# above is actually pretty verbose by F# standards!
For fun, here is what a more typically concise version would look like:
let rec quicksort2 = function
| [] -> []
| first::rest ->
let smaller,larger = List.partition ((>=) first) rest
List.concat [quicksort2 smaller; [first]; quicksort2 larger]
// test code
printfn "%A" (quicksort2 [1;5;23;18;9;1;3])
Not bad for 4 lines of code, and when you get used to the syntax, still quite readable.
136
137
138
class WebPageDownloader
{
public TResult FetchUrl<TResult>(
string url,
Func<string, StreamReader, TResult> callback)
{
var req = WebRequest.Create(url);
using (var resp = req.GetResponse())
{
using (var stream = resp.GetResponseStream())
{
using (var reader = new StreamReader(stream))
{
return callback(url, reader);
}
}
}
}
}
139
A very useful feature of F# is that you can "bake in" parameters in a function so that they
don't have to be passed in every time. This is why the url parameter was placed last
rather than first, as in the C# version. The callback can be setup once, while the url varies
from call to call.
// build a function with the callback "baked in"
let fetchUrl2 = fetchUrl myCallback
// test
let google = fetchUrl2 "http://www.google.com"
let bbc = fetchUrl2 "http://news.bbc.co.uk"
// test with a list of sites
let sites = ["http://www.bing.com";
"http://www.google.com";
"http://www.yahoo.com"]
// process each site in the list
sites |> List.map fetchUrl2
The last line (using List.map ) shows how the new function can be easily used in
conjunction with list processing functions to download a whole list at once.
Here is the equivalent C# test code:
140
[Test]
public void TestFetchUrlWithCallback()
{
Func<string, StreamReader, string> myCallback = (url, reader) =>
{
var html = reader.ReadToEnd();
var html1000 = html.Substring(0, 1000);
Console.WriteLine(
"Downloaded {0}. First 1000 is {1}", url,
html1000);
return html;
};
var downloader = new WebPageDownloader();
var google = downloader.FetchUrl("http://www.google.com",
myCallback);
// test with a list of sites
var sites = new List<string> {
"http://www.bing.com",
"http://www.google.com",
"http://www.yahoo.com"};
// process each site in the list
sites.ForEach(site => downloader.FetchUrl(site, myCallback));
}
Again, the code is a bit noisier than the F# code, with many explicit type references. More
importantly, the C# code doesn't easily allow you to bake in some of the parameters in a
function, so the callback must be explicitly referenced every time.
141
142
As you might expect from the term "functional programming", functions are everywhere in
F#.
Of course, functions are first class entities, and can be passed around like any other value:
let square x = x * x
// functions as values
let squareclone = square
let result = [1..10] |> List.map squareclone
// functions taking other functions as parameters
let execFunction aFunc aParam = aFunc aParam
let result2 = execFunction square 12
But C# has first-class functions too, so what's so special about functional programming?
The short answer is that the function-oriented nature of F# infiltrates every part of the
language and type system in a way that it does not in C#, so that things that are awkward or
clumsy in C# are very elegant in F#.
It's hard to explain this in a few paragraphs, but here are some of the benefits that we will
see demonstrated over this series of posts:
Building with composition. Composition is the 'glue' that allows us build larger
systems from smaller ones. This is not an optional technique, but is at the very heart of
the functional style. Almost every line of code is a composable expression (see below).
Composition is used to build basic functions, and then functions that use those
functions, and so on. And the composition principle doesn't just apply to functions, but
also to types (the product and sum types discussed below).
Factoring and refactoring. The ability to factor a problem into parts depends how
easily the parts can be glued back together. Methods and classes that might seem to be
indivisible in an imperative language can often be broken down into surprisingly small
pieces in a functional design. These fine-grained components typically consist of (a) a
few very general functions that take other functions as parameters, and (b) other helper
functions that specialize the general case for a particular data structure or application.
Once factored out, the generalized functions allow many additional operations to be
programmed very easily without having to write new code. You can see a good example
of a general function like this (the fold function) in the post on extracting duplicate code
from loops.
Good design. Many of the principles of good design, such as "separation of concerns",
"single responsibility principle", "program to an interface, not an implementation", arise
naturally as a result of a functional approach. And functional code tends to be high level
and declarative in general.
143
The following posts in this series will have examples of how functions can make code more
concise and convenient, and then for a deeper understanding, there is a whole series on
thinking functionally.
F# works in the same way -- every function definition is a single expression, not a set of
statements.
And it might not be obvious, but code built from expressions is both safer and more compact
than using statements. To see this, let's compare some statement-based code in C# with the
equivalent expression-based code.
First, the statement-based code. Statements don't return values, so you have to use
temporary variables that are assigned to from within statement bodies.
// statement-based code in C#
int result;
if (aBool)
{
result = 42;
}
Console.WriteLine("result={0}", result);
Because the if-then block is a statement, the result variable must be defined outside
the statement but assigned to from inside the statement, which leads to issues such as:
What initial value should result be set to?
What if I forget to assign to the result variable?
What is the value of the result variable in the "else" case?
For comparison, here is the same code, rewritten in an expression-oriented style:
144
// expression-based code in C#
int result = (aBool) ? 42 : 0;
Console.WriteLine("result={0}", result);
Algebraic Types
The type system in F# is based on the concept of algebraic types. That is, new compound
types are built by combining existing types in two different ways:
First, a combination of values, each picked from a set of types. These are called
"product" types.
Of, alternately, as a disjoint union representing a choice between a set of types. These
are called "sum" types.
For example, given existing types int and bool , we can create a new product type that
must have one of each:
//declare it
type IntAndBool = {intPart: int; boolPart: bool}
//use it
let x = {intPart=1; boolPart=false}
Alternatively, we can create a new union/sum type that has a choice between each type:
145
//declare it
type IntOrBool =
| IntChoice of int
| BoolChoice of bool
//use it
let y = IntChoice 42
let z = BoolChoice true
These "choice" types are not available in C#, but are incredibly useful for modeling many
real-world cases, such as states in a state machine (which is a surprisingly common theme
in many domains).
And by combining "product" and "sum" types in this way, it is easy to create a rich set of
types that accurately models any business domain. For examples of this in action, see the
posts on low overhead type definitions and using the type system to ensure correct code.
146
Finally, loops are generally done using recursion, and typically look something like this:
match aList with
| [] ->
// Empty case
| first::rest ->
// Case with at least one element.
// Process first element, and then call
// recursively with the rest of the list
Although the match expression seems unnecessarily complicated at first, you'll see that in
practice it is both elegant and powerful.
For the benefits of pattern matching, see the post on exhaustive pattern matching, and for a
worked example that uses pattern matching heavily, see the roman numerals example.
147
148
Conciseness
Conciseness
After having seen some simple code, we will now move on to demonstrating the major
themes (conciseness, convenience, correctness, concurrency and completeness), filtered
through the concepts of types, functions and pattern matching.
With the next few posts, we'll examine the features of F# that aid conciseness and
readability.
An important goal for most mainstream programming languages is a good balance of
readability and conciseness. Too much conciseness can result in hard-to-understand or
obfuscated code (APL anyone?), while too much verbosity can easily swamp the underlying
meaning. Ideally, we want a high signal-to-noise ratio, where every word and character in
the code contributes to the meaning of the code, and there is minimal boilerplate.
Why is conciseness important? Here are a few reasons:
A concise language tends to be more declarative, saying what the code should do
rather than how to do it. That is, declarative code is more focused on the high-level logic
rather than the nuts and bolts of the implementation.
It is easier to reason about correctness if there are fewer lines of code to reason
about!
And of course, you can see more code on a screen at a time. This might seem trivial,
but the more you can see, the more you can grasp as well.
As you have seen, compared with C#, F# is generally much more concise. This is due to
features such as:
Type inference and low overhead type definitions. One of the major reasons for F#'s
conciseness and readability is its type system. F# makes it very easy to create new
types as you need them. They don't cause visual clutter either in their definition or in
use, and the type inference system means that you can use them freely without getting
distracted by complex type syntax.
Using functions to extract boilerplate code. The DRY principle ("don't repeat
yourself") is a core principle of good design in functional languages as well as objectoriented languages. In F# it is extremely easy to extract repetitive code into common
utility functions, which allows you to focus on the important stuff.
Composing complex code from simple functions and creating mini-languages.
The functional approach makes it easy to create a set of basic operations and then
combine these building blocks in various ways to build up more complex behaviors. In
this way, even the most complex code is still very concise and readable.
149
Conciseness
Pattern matching. We've seen pattern matching as a glorified switch statement, but in
fact it is much more general, as it can compare expressions in a number of ways,
matching on values, conditions, and types, and then assign or extract values, all at the
same time.
150
Type inference
Type inference
As you have already seen, F# uses a technique called "type inference" to greatly reduce the
number of type annotations that need to be explicitly specified in normal code. And even
when types do need to be specified, the syntax is less longwinded compared to C#.
To see this, here are some C# methods that wrap two standard LINQ functions. The
implementations are trivial, but the method signatures are extremely complex:
public IEnumerable<TSource> Where<TSource>(
IEnumerable<TSource> source,
Func<TSource, bool> predicate
)
{
//use the standard LINQ implementation
return source.Where(predicate);
}
public IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(
IEnumerable<TSource> source,
Func<TSource, TKey> keySelector
)
{
//use the standard LINQ implementation
return source.GroupBy(keySelector);
}
And here are the exact F# equivalents, showing that no type annotations are needed at all!
let Where source predicate =
//use the standard F# implementation
Seq.filter predicate source
let GroupBy source keySelector =
//use the standard F# implementation
Seq.groupBy keySelector source
You might notice that the standard F# implementations for "filter" and "groupBy" have
the parameters in exactly the opposite order from the LINQ implementations used in C#.
The "source" parameter is placed last, rather than first. There is a reason for this, which
will be explained in the thinking functionally series.
151
Type inference
The type inference algorithm is excellent at gathering information from many sources to
determine the types. In the following example, it correctly deduces that the list value is a
list of strings.
let i = 1
let s = "hello"
let tuple = s,i // pack into tuple
let s2,i2 = tuple // unpack
let list = [s2] // type is string list
And in this example, it correctly deduces that the sumLengths function takes a list of strings
and returns an int.
let sumLengths strList =
strList |> List.map String.length |> List.sum
// function type is: string list -> int
152
153
154
| _ -> false
override this.GetHashCode() = hash this.CustomerAccountId
This code fragment contains 17 type definitions in just a few lines, but with minimal
complexity. How many lines of C# code would you need to do the same thing?
Obviously, this is a simplified version with just the basic types ? in a real system, constraints
and other methods would be added. But note how easy it is to create lots of DDD value
objects, especially wrapper types for strings, such as " ZipCode " and " Email ". By using
these wrapper types, we can enforce certain constraints at creation time, and also ensure
that these types don't get confused with unconstrained strings in normal code. The only
"entity" type is the CustomerAccount , which is clearly indicated as having special treatment
for equality and comparison.
For a more in-depth discussion, see the series called "Domain driven design in F#".
155
156
What do all these implementations have in common? The looping logic! As programmers,
we are told to remember the DRY principle ("don't repeat yourself"), yet here we have
repeated almost exactly the same loop logic each time. Let's see if we can extract just the
differences between these three methods:
157
Function
Initial
value
Product
product=1
SumOfOdds
sum=0
AlternatingSum
int sum =
0
bool
isNeg =
true
Is there a way to strip the duplicate code and focus on the just the setup and inner loop
logic? Yes there is. Here are the same three functions in F#:
let product n =
let initialValue = 1
let action productSoFar x = productSoFar * x
[1..n] |> List.fold action initialValue
//test
product 10
let sumOfOdds n =
let initialValue = 0
let action sumSoFar x = if x%2=0 then sumSoFar else sumSoFar+x
[1..n] |> List.fold action initialValue
//test
sumOfOdds 10
let alternatingSum n =
let initialValue = (true,0)
let action (isNeg,sumSoFar) x = if isNeg then (false,sumSoFar-x)
else (true ,sumSoFar+x)
[1..n] |> List.fold action initialValue |> snd
//test
alternatingSum 100
158
The action function always has two parameters: a running total (or state) and the list element
to act on (called "x" in the above examples).
In the last function, alternatingSum , you will notice that it used a tuple (pair of values) for
the initial value and the result of the action. This is because both the running total and the
isNeg flag must be passed to the next iteration of the loop -- there are no "global" values
that can be used. The final result of the fold is also a tuple so we have to use the "snd"
(second) function to extract the final total that we want.
By using List.fold and avoiding any loop logic at all, the F# code gains a number of
benefits:
The key program logic is emphasized and made explicit. The important differences
between the functions become very clear, while the commonalities are pushed to the
background.
The boilerplate loop code has been eliminated, and as a result the code is more
condensed than the C# version (4-5 lines of F# code vs. at least 9 lines of C# code)
There can never be a error in the loop logic (such as off-by-one) because that logic
is not exposed to us.
By the way, the sum of squares example could also be written using fold as well:
let sumOfSquaresWithFold n =
let initialValue = 0
let action sumSoFar x = sumSoFar + (x*x)
[1..n] |> List.fold action initialValue
//test
sumOfSquaresWithFold 100
"Fold" in C#
Can you use the "fold" approach in C#? Yes. LINQ does have an equivalent to fold , called
Aggregate . And here is the C# code rewritten to use it:
159
Well, in some sense these implementations are simpler and safer than the original C#
versions, but all the extra noise from the generic types makes this approach much less
elegant than the equivalent code in F#. You can see why most C# programmers prefer to
stick with explicit loops.
160
Doing this in LINQ seems hard to do efficiently (that is, in one pass), and has come up as a
Stack Overflow question. Jon Skeet event wrote an article about it.
Again, fold to the rescue!
And here's the C# code using Aggregate :
public class NameAndSize
{
public string Name;
public int Size;
}
public static NameAndSize MaxNameAndSize(IList<NameAndSize> list)
{
if (!list.Any())
{
return default(NameAndSize);
}
var initialValue = list[0];
Func<NameAndSize, NameAndSize, NameAndSize> action =
(maxSoFar, x) => x.Size > maxSoFar.Size ? x : maxSoFar;
return list.Aggregate(initialValue, action);
}
161
Note that this C# version returns null for an empty list. That seems dangerous -- so what
should happen instead? Throwing an exception? That doesn't seem right either.
Here's the F# code using fold:
type NameAndSize= {Name:string;Size:int}
let maxNameAndSize list =
let innerMaxNameAndSize initialValue rest =
let action maxSoFar x = if maxSoFar.Size < x.Size then x else maxSoFar
rest |> List.fold action initialValue
// handle empty lists
match list with
| [] ->
None
| first::rest ->
let max = innerMaxNameAndSize first rest
Some max
Actually, I didn't need to write this at all, because F# already has a maxBy function!
// use the built in function
list |> List.maxBy (fun item -> item.Size)
[] |> List.maxBy (fun item -> item.Size)
162
But as you can see, it doesn't handle empty lists well. Here's a version that wraps the
maxBy safely.
163
The " >> " operator is the composition operator. It means: do the first function, and then do
the second.
Note how concise this way of combining functions is. There are no parameters, types or
other irrelevant noise.
To be sure, the examples could also have been written less concisely and more explicitly as:
let add2ThenMult3 x = mult3 (add2 x)
let mult3ThenSquare x = square (mult3 x)
164
And in the explicit style, the functions are written back-to-front from the order they are
applied. In my example of add2ThenMult3 I want to add 2 first, and then multiply. The
add2 >> mult3 syntax makes this visually clearer than mult3(add2 x) .
Our new function, mult3ThenSquareLogged , has an ugly name, but it is easy to use and nicely
hides the complexity of the functions that went into it. You can see that if you define your
building block functions well, this composition of functions can be a powerful way to get new
functionality.
But wait, there's more! Functions are first class entities in F#, and can be acted on by any
other F# code. Here is an example of using the composition operator to collapse a list of
functions into a single operation.
165
let listOfFunctions = [
mult3;
square;
add2;
logMsgN "result=";
]
// compose all functions in the list into a single one
let allFunctions = List.reduce (>>) listOfFunctions
//test
allFunctions 5
Mini languages
Domain-specific languages (DSLs) are well recognized as a technique to create more
readable and concise code. The functional approach is very well suited for this.
If you need to, you can go the route of having a full "external" DSL with its own lexer, parser,
and so on, and there are various toolsets for F# that make this quite straightforward.
But in many cases, it is easier to stay within the syntax of F#, and just design a set of "verbs"
and "nouns" that encapsulate the behavior we want.
The ability to create new types concisely and then match against them makes it very easy to
set up fluent interfaces quickly. For example, here is a little function that calculates dates
using a simple vocabulary. Note that two new enum-style types are defined just for this one
function.
166
The example above only has one "verb", using lots of types for the "nouns".
The following example demonstrates how you might build the functional equivalent of a
fluent interface with many "verbs".
Say that we are creating a drawing program with various shapes. Each shape has a color,
size, label and action to be performed when clicked, and we want a fluent interface to
configure each shape.
Here is an example of what a simple method chain for a fluent interface in C# might look
like:
FluentShape.Default
.SetColor("red")
.SetLabel("box")
.OnClick( s => Console.Write("clicked") );
Now the concept of "fluent interfaces" and "method chaining" is really only relevant for
object-oriented design. In a functional language like F#, the nearest equivalent would be the
use of the pipeline operator to chain a set of functions together.
Let's start with the underlying Shape type:
167
For "method chaining" to work, every function should return an object that can be used next
in the chain. So you will see that the " display " function returns the shape, rather than
nothing.
Next we create some helper functions which we expose as the "mini-language", and will be
used as building blocks by the users of the language.
let setLabel label shape =
{shape with FluentShape.label = label}
let setColor color shape =
{shape with FluentShape.color = color}
//add a click action to what is already there
let appendClickAction action shape =
{shape with FluentShape.onClick = shape.onClick >> action}
Notice that appendClickAction takes a function as a parameter and composes it with the
existing click action. As you start getting deeper into the functional approach to reuse, you
start seeing many more "higher order functions" like this, that is, functions that act on other
functions. Combining functions like this is one of the keys to understanding the functional
way of programming.
Now as a user of this "mini-language", I can compose the base helper functions into more
complex functions of my own, creating my own function library. (In C# this kind of thing might
be done using extension methods.)
168
I can then combine these functions together to create objects with the desired behavior.
//setup some test values
let redBox = defaultShape |> setRedBox
let blueBox = defaultShape |> setBlueBox
// create a shape that changes color when clicked
redBox
|> display
|> changeColorOnClick "green"
|> click
|> display // new version after the click
// create a shape that changes label and color when clicked
blueBox
|> display
|> appendClickAction (setLabel "box2" >> setColor "green")
|> click
|> display // new version after the click
In the second case, I actually pass two functions to appendClickAction , but I compose them
into one first. This kind of thing is trivial to do with a well structured functional library, but it is
quite hard to do in C# without having lambdas within lambdas.
Here is a more complex example. We will create a function " showRainbow " that, for each
color in the rainbow, sets the color and displays the shape.
let rainbow =
["red";"orange";"yellow";"green";"blue";"indigo";"violet"]
let showRainbow =
let setColorAndDisplay color = setColor color >> display
rainbow
|> List.map setColorAndDisplay
|> List.reduce (>>)
// test the showRainbow function
defaultShape |> showRainbow
169
Notice that the functions are getting more complex, but the amount of code is still quite
small. One reason for this is that the function parameters can often be ignored when doing
function composition, which reduces visual clutter. For example, the " showRainbow " function
does take a shape as a parameter, but it is not explicitly shown! This elision of parameters is
called "point-free" style and will be discussed further in the "thinking functionally" series
170
You can also bind values to the inside of complex structures such as records. In the
following example, we will create an " Address " type, and then a " Customer " type which
contains an address. Next, we will create a customer value, and then match various
properties against it.
171
In the last example, note how we could reach right into the Address substructure and pull
out the street as well as the customer name.
This ability to process a nested structure, extract only the fields you want, and assign them
to values, all in a single step, is very useful. It removes quite a bit of coding drudgery, and is
another factor in the conciseness of typical F# code.
172
Convenience
Convenience
In the next set of posts, we will explore a few more features of F# that I have grouped under
the theme of "convenience". These features do not necessarily result in more concise code,
but they do remove much of the drudgery and boilerplate code that would be needed in C#.
Useful "out-of-the-box" behavior for types. Most types that you create will
immediately have some useful behavior, such as immutability and built-in equality ?
functionality that has to be explicitly coded for in C#.
All functions are "interfaces", meaning that many of the roles that interfaces play in
object-oriented design are implicit in the way that functions work. And similarly, many
object-oriented design patterns are unnecessary or trivial within a functional paradigm.
Partial application. Complicated functions with many parameters can have some of the
parameters fixed or "baked in" and yet leave other parameters open.
Active patterns. Active patterns are a special kind of pattern where the pattern can be
matched or detected dynamically, rather than statically. They are great for simplifying
frequently used parsing and grouping behaviors.
173
174
175
Note that the Ace of Hearts is automatically greater than the Two of Hearts, because the
"Ace" rank value comes after the "Two" rank value.
But also note that the Two of Hearts is automatically greater than the Ace of Spades,
because the Suit part is compared first, and the "Heart" suit value comes after the "Spade"
value.
176
And as a side benefit, you get min and max for free too!
List.max hand |> printfn "high card is %A"
List.min hand |> printfn "low card is %A"
177
Functions as interfaces
Functions as interfaces
An important aspect of functional programming is that, in a sense, all functions are
"interfaces", meaning that many of the roles that interfaces play in object-oriented design are
implicit in the way that functions work.
In fact, one of the critical design maxims, "program to an interface, not an implementation",
is something you get for free in F#.
To see how this works, let's compare the same design pattern in C# and F#. For example, in
C# we might want to use the "decorator pattern" to enhance some core code.
Let's say that we have a calculator interface:
interface ICalculator
{
int Calculate(int input);
}
And then if we want to add logging, we can wrap the core calculator implementation inside a
logging wrapper.
178
Functions as interfaces
So far, so straightforward. But note that, for this to work, we must have defined an interface
for the classes. If there had been no ICalculator interface, it would be necessary to retrofit
the existing code.
And here is where F# shines. In F#, you can do the same thing without having to define the
interface first. Any function can be transparently swapped for any other function as long as
the signatures are the same.
Here is the equivalent F# code.
let addingCalculator input = input + 1
let loggingCalculator innerCalculator input =
printfn "input is %A" input
let result = innerCalculator input
printfn "result is %A" result
result
Generic wrappers
Even nicer is that by default, the F# logging code can be made completely generic so that it
will work for any function at all. Here are some examples:
179
Functions as interfaces
The new "wrapped" functions can be used anywhere the original functions could be used ?
no one can tell the difference!
// test
add1WithLogging 3
times2WithLogging 3
[1..5] |> List.map add1WithLogging
Exactly the same generic wrapper approach can be used for other things. For example, here
is a generic wrapper for timing a function.
let genericTimer anyFunc input =
let stopwatch = System.Diagnostics.Stopwatch()
stopwatch.Start()
let result = anyFunc input //evaluate the function
printfn "elapsed ms is %A" stopwatch.ElapsedMilliseconds
result
let add1WithTimer = genericTimer add1WithLogging
// test
add1WithTimer 3
The ability to do this kind of generic wrapping is one of the great conveniences of the
function-oriented approach. You can take any function and create a similar function based
on it. As long as the new function has exactly the same inputs and outputs as the original
function, the new can be substituted for the original anywhere. Some more examples:
It is easy to write a generic caching wrapper for a slow function, so that the value is only
calculated once.
It is also easy to write a generic "lazy" wrapper for a function, so that the inner function
is only called when a result is needed
180
Functions as interfaces
Note that again, we do not have to define any kind of INoiseMakingStrategy interface first.
Any function with the right signature will work. As a consequence, in the functional model,
the standard .NET "strategy" interfaces such as IComparer , IFormatProvider , and
IServiceProvider become irrelevant.
181
Partial Application
Partial Application
A particularly convenient feature of F# is that complicated functions with many parameters
can have some of the parameters fixed or "baked in" and yet leave other parameters open.
In this post, we'll take a quick look at how this might be used in practice.
Let's start with a very simple example of how this works. We'll start with a trivial function:
// define a adding function
let add x y = x + y
// normal use
let z = add 1 2
But we can do something strange as well ? we can call the function with only one parameter!
let add42 = add 42
The result is a new function that has the "42" baked in, and now takes only one parameter
instead of two! This technique is called "partial application", and it means that, for any
function, you can "fix" some of the parameters and leave other ones open to be filled in later.
// use the new function
add42 2
add42 3
With that under our belt, let's revisit the generic logger that we saw earlier:
let genericLogger anyFunc input =
printfn "input is %A" input //log the input
let result = anyFunc input //evaluate the function
printfn "result is %A" result //log the result
result //return the result
Unfortunately, I have hard-coded the logging operations. Ideally, I'd like to make this more
generic so that I can choose how logging is done.
Of course, F# being a functional programming language, we will do this by passing functions
around.
182
Partial Application
In this case we would pass "before" and "after" callback functions to the library function, like
this:
let genericLogger before after anyFunc input =
before input //callback for custom behavior
let result = anyFunc input //evaluate the function
after result //callback for custom behavior
result //return the result
You can see that the logging function now has four parameters. The "before" and "after"
actions are passed in as explicit parameters as well as the function and its input. To use this
in practice, we just define the functions and pass them in to the library function along with
the final int parameter:
let add1 input = input + 1
// reuse case 1
genericLogger
(fun x -> printf "before=%i. " x) // function to call before
(fun x -> printfn " after=%i." x) // function to call after
add1 // main function
2 // parameter
// reuse case 2
genericLogger
(fun x -> printf "started with=%i " x) // different callback
(fun x -> printfn " ended with=%i" x)
add1 // main function
2 // parameter
This is a lot more flexible. I don't have to create a new function every time I want to change
the behavior -- I can define the behavior on the fly.
But you might be thinking that this is a bit ugly. A library function might expose a number of
callback functions and it would be inconvenient to have to pass the same functions in over
and over.
Luckily, we know the solution for this. We can use partial application to fix some of the
parameters. So in this case, let's define a new function which fixes the before and after
functions, as well as the add1 function, but leaves the final parameter open.
183
Partial Application
The new "wrapper" function is called with just an int now, so the code is much cleaner. As in
the earlier example, it can be used anywhere the original add1 function could be used
without any changes.
add1WithConsoleLogging 2
add1WithConsoleLogging 3
add1WithConsoleLogging 4
[1..5] |> List.map add1WithConsoleLogging
184
Partial Application
In C#, this style of programming is required when using the LINQ libraries, but many
developers have not embraced it fully to make their own code more generic and adaptable.
And it's not helped by the ugly Action<> and Func<> type declarations that are required.
But it can certainly make the code much more reusable.
185
Active patterns
Active patterns
F# has a special type of pattern matching called "active patterns" where the pattern can be
parsed or detected dynamically. As with normal patterns, the matching and output are
combined into a single step from the caller's point of view.
Here is an example of using active patterns to parse a string into an int or bool.
// create an active pattern
let (|Int|_|) str =
match System.Int32.TryParse(str) with
| (true,int) -> Some(int)
| _ -> None
// create an active pattern
let (|Bool|_|) str =
match System.Boolean.TryParse(str) with
| (true,bool) -> Some(bool)
| _ -> None
You don't need to worry about the complex syntax used to define the active pattern right
now ? this is just an example so that you can see how they are used.
Once these patterns have been set up, they can be used as part of a normal " match..with "
expression.
// create a function to call the patterns
let testParse str =
match str with
| Int i -> printfn "The value is an int '%i'" i
| Bool b -> printfn "The value is a bool '%b'" b
| _ -> printfn "The value '%s' is something else" str
// test
testParse "12"
testParse "true"
testParse "abc"
You can see that from the caller's point of view, the matching with an Int or Bool is
transparent, even though there is parsing going on behind the scenes.
186
Active patterns
A similar example is to use active patterns with regular expressions in order to both match
on a regex pattern and return the matched value in a single step.
// create an active pattern
open System.Text.RegularExpressions
let (|FirstRegexGroup|_|) pattern input =
let m = Regex.Match(input,pattern)
if (m.Success) then Some m.Groups.[1].Value else None
Again, once this pattern has been set up, it can be used transparently as part of a normal
match expression.
// create a function to call the pattern
let testRegex str =
match str with
| FirstRegexGroup "http://(.*?)/(.*)" host ->
printfn "The value is a url and the host is %s" host
| FirstRegexGroup ".*?@(.*)" host ->
printfn "The value is an email and the host is %s" host
| _ -> printfn "The value '%s' is something else" str
// test
testRegex "http://google.com/test"
testRegex "alice@hotmail.com"
And for fun, here's one more: the well-known FizzBuzz challenge written using active
patterns.
// setup the active patterns
let (|MultOf3|_|) i = if i % 3 = 0 then Some MultOf3 else None
let (|MultOf5|_|) i = if i % 5 = 0 then Some MultOf5 else None
// the main function
let fizzBuzz i =
match i with
| MultOf3 & MultOf5 -> printf "FizzBuzz, "
| MultOf3 -> printf "Fizz, "
| MultOf5 -> printf "Buzz, "
| _ -> printf "%i, " i
// test
[1..20] |> List.iter fizzBuzz
187
Correctness
Correctness
As a programmer, you are constantly judging the code that you and others write. In an ideal
world, you should be able to look at a piece of code and easily understand exactly what it
does; and of course, being concise, clear and readable is a major factor in this.
But more importantly, you have to be able to convince yourself that the code does what it is
supposed to do. As you program, you are constantly reasoning about code correctness, and
the little compiler in your brain is checking the code for errors and possible mistakes.
So how can a programming language help you with this?
A modern imperative language like C# provides many ways that you are already familiar
with: type checking, scoping and naming rules, access modifiers and so on. And, in recent
versions, static code analysis and code contracts.
All these techniques mean that the compiler can take on a lot of the burden of checking for
correctness. If you make a mistake, the compiler will warn you.
But F# has some additional features that can have a huge impact on ensuring correctness.
The next few posts will be devoted to four of them:
Immutability, which enables code to behave much more predictably.
Exhaustive pattern matching, which traps many common errors at compile time.
A strict type system, which is your friend, not your enemy. You can use the static type
checking almost as an instant "compile time unit test".
An expressive type system that can help you "make illegal states unrepresentable"* .
We'll see how to design a real-world example that demonstrates this.
* Thanks to Yaron Minsky at Jane Street for this phrase.
188
Immutability
Immutability
To see why immutability is important, let's start with a small example.
Here's some simple C# code that processes a list of numbers.
public List<int> MakeList()
{
return new List<int> {1,2,3,4,5,6,7,8,9,10};
}
public List<int> OddNumbers(List<int> list)
{
// some code
}
public List<int> EvenNumbers(List<int> list)
{
// some code
}
Everything works great, and the test passes, but I notice that I am creating the list twice ?
surely I should refactor this out? So I do the refactoring, and here's the new improved
version:
public void RefactoredTest()
{
var list = MakeList();
var odds = OddNumbers(list);
var evens = EvenNumbers(list);
// assert odds = 1,3,5,7,9 -- OK!
// assert evens = 2,4,6,8,10 -- FAIL!
}
189
Immutability
But now the test suddenly fails! Why would a refactoring break the test? Can you tell just by
looking at the code?
The answer is, of course, that the list is mutable, and it is probable that the OddNumbers
function is making destructive changes to the list as part of its filtering logic. Of course, in
order to be sure, we would have to examine the code inside the OddNumbers function.
In other words, when I call the OddNumbers function, I am unintentionally creating
undesirable side effects.
Is there a way to ensure that this cannot happen? Yes -- if the functions had used
IEnumerable instead:
In this case we can be confident that calling the OddNumbers function could not possibly
have any effect on the list, and EvenNumbers would work correctly. What's more, we can
know this just by looking at the signatures, without having to examine the internals of the
functions. And if you try to make one of the functions misbehave by assigning to the list then
you will get an error straight away, at compile time.
So IEnumerable can help in this case, but what if I had used a type such as
IEnumerable<Person> instead of IEnumerable<int> ? Could I still be as confident that the
190
Immutability
don't have to worry about using an object as a key in a hashtable and having its hash code
change).
In fact, immutability is a good idea for the same reasons that global variables are a bad idea:
data should be kept as local as possible and side-effects should be avoided.
Second, immutability is easier to work with. If data is immutable, many common tasks
become much easier. Code is easier to write and easier to maintain. Fewer unit tests are
needed (you only have to check that a function works in isolation), and mocking is much
easier. Concurrency is much simpler, as you don't have to worry about using locks to avoid
update conflicts (because there are no updates).
Finally, using immutability by default means that you start thinking differently about
programming. You tend to think about transforming the data rather than mutating it in
place.
SQL queries and LINQ queries are good examples of this "transformational" approach. In
both cases, you always transform the original data through various functions (selects, filters,
sorts) rather than modifying the original data.
When a program is designed using a transformation approach, the result tends to be more
elegant, more modular, and more scalable. And as it happens, the transformation approach
is also a perfect fit with a function-oriented paradigm.
Because of this, F# has a number of tricks to make life easier and to optimize the underlying
code.
First, since you can't modify a data structure, you must copy it when you want to change it.
F# makes it easy to copy another data structure with only the changes you want:
let alice = {john with FirstName="Alice"}
191
Immutability
And complex data structures are implemented as linked lists or similar, so that common
parts of the structure are shared.
// create an immutable list
let list1 = [1;2;3;4]
// prepend to make a new list
let list2 = 0::list1
// get the last 4 of the second list
let list3 = list2.Tail
// the two lists are the identical object in memory!
System.Object.ReferenceEquals(list1,list3)
This technique ensures that, while you might appear to have hundreds of copies of a list in
your code, they are all sharing the same memory behind the scenes.
Mutable data
F# is not dogmatic about immutability; it does support mutable data with the mutable
keyword. But turning on mutability is an explicit decision, a deviation from the default, and it
is generally only needed for special cases such as optimization, caching, etc, or when
dealing with the .NET libraries.
In practice, a serious application is bound to have some mutable state if it deals with messy
world of user interfaces, databases, networks and so on. But F# encourages the
minimization of such mutable state. You can generally still design your core business logic to
use immutable data, with all the corresponding benefits.
192
This code will compile, but there is an obvious bug! The compiler couldn't see it ? can you?
If you can, and you fixed it, would it stay fixed if I added another State to the list?
Here's the F# equivalent:
type State = New | Draft | Published | Inactive | Discontinued
let handleState state =
match state with
| Inactive -> () // code for Inactive
| Draft -> () // code for Draft
| New -> () // code for New
| Discontinued -> () // code for Discontinued
Now try running this code. What does the compiler tell you?
The fact that exhaustive matching is always done means that certain common errors will be
detected by the compiler immediately:
A missing case (often caused when a new choice has been added due to changed
requirements or refactoring).
An impossible case (when an existing choice has been removed).
193
A redundant case that could never be reached (the case has been subsumed in a
previous case -- this can sometimes be non-obvious).
Now let's look at some real examples of how exhaustive matching can help you write correct
code.
Unfortunately, this test is not required by the compiler. All it takes is for one piece of code to
forget to do this, and the program can crash. Over the years, a huge amount of
programming effort has been devoted to handling nulls ? the invention of nulls has even
been called a billion dollar mistake!
In pure F#, nulls cannot exist accidentally. A string or object must always be assigned to
something at creation, and is immutable thereafter.
However, there are many situations where the design intent is to distinguish between valid
and invalid values, and you require the caller to handle both cases.
In C#, this can be managed in certain situations by using nullable value types (such as
Nullable<int> ) to make the design decision clear. When a nullable is encountered the
compiler will force you to be aware of it. You can then test the validity of the value before
using it. But nullables do not work for standard classes (i.e. reference types), and it is easy
to accidentally bypass the tests too and just call Value directly.
In F# there is a similar but more powerful concept to convey the design intent: the generic
wrapper type called Option , with two choices: Some or None . The Some choice wraps a
valid value, and None represents a missing value.
Here's an example where Some is returned if a file exists, but a missing file returns None .
194
If we want to do anything with these values, we must always handle both possible cases.
match goodFileInfo with
| Some fileInfo ->
printfn "the file %s exists" fileInfo.FullName
| None ->
printfn "the file doesn't exist"
match badFileInfo with
| Some fileInfo ->
printfn "the file %s exists" fileInfo.FullName
| None ->
printfn "the file doesn't exist"
We have no choice about this. Not handling a case is a compile-time error, not a run-time
error. By avoiding nulls and by using Option types in this way, F# completely eliminates a
large class of null reference exceptions.
Caveat: F# does allow you to access the value without testing, just like C#, but that is
considered extremely bad practice.
195
It compiles correctly, but it actually has a couple of issues. Can you find them quickly? If
you're lucky, your unit tests will find them for you, assuming you have thought of all the edge
cases.
Now let's try the same thing in F#:
let rec movingAverages list =
match list with
// if input is empty, return an empty list
| [] -> []
// otherwise process pairs of items from the input
| x::y::rest ->
let avg = (x+y)/2.0
//build the result by recursing the rest of the list
avg :: movingAverages (y::rest)
This code also has a bug. But unlike C#, this code will not even compile until I fix it. The
compiler will tell me that I haven't handled the case when I have a single item in my list. Not
only has it found a bug, it has revealed a gap in the requirements: what should happen when
there is only one item?
Here's the fixed up version:
let rec movingAverages list =
match list with
// if input is empty, return an empty list
| [] -> []
// otherwise process pairs of items from the input
| x::y::rest ->
let avg = (x+y)/2.0
//build the result by recursing the rest of the list
avg :: movingAverages (y::rest)
// for one item, return an empty list
| [_] -> []
// test
movingAverages [1.0]
movingAverages [1.0; 2.0]
movingAverages [1.0; 2.0; 3.0]
196
197
The code demonstrates how performActionOnFile returns a Result object which has two
alternatives: Success and Failure . The Failure alternative in turn has two alternatives
as well: FileNotFound and UnauthorizedAccess .
Now the intermediate layers can call each other, passing around the result type without
worrying what its structure is, as long as they don't access it:
// a function in the middle layer
let middleLayerDo action filePath =
let fileResult = performActionOnFile action filePath
// do some stuff
fileResult //return
// a function in the top layer
let topLayerDo action filePath =
let fileResult = middleLayerDo action filePath
// do some stuff
fileResult //return
Because of type inference, the middle and top layers do not need to specify the exact types
returned. If the lower layer changes the type definition at all, the intermediate layers will not
be affected.
Obviously at some point, a client of the top layer does want to access the result. And here is
where the requirement to match all patterns is enforced. The client must handle the case
with a Failure or else the compiler will complain. And furthermore, when handling the
Failure branch, it must handle the possible reasons as well. In other words, special case
handling of this sort can be enforced at compile time, not at runtime! And in addition the
possible reasons are explicitly documented by examining the reason type.
Here is an example of a client function that accesses the top layer:
/// get the first line of the file
let printFirstLineOfFile filePath =
let fileResult = topLayerDo (fun fs->fs.ReadLine()) filePath
match fileResult with
| Success result ->
// note type-safe string printing with %s
printfn "first line is: '%s'" result
| Failure reason ->
match reason with // must match EVERY reason
| FileNotFound file ->
printfn "File not found: %s" file
| UnauthorizedAccess (file,_) ->
printfn "You do not have access to the file: %s" file
198
You can see that this code must explicitly handle the Success and Failure cases, and
then for the failure case, it explicitly handles the different reasons. If you want to see what
happens if it does not handle one of the cases, try commenting out the line that handles
UnauthorizedAccess and see what the compiler says.
Now it is not required that you always handle all possible cases explicitly. In the example
below, the function uses the underscore wildcard to treat all the failure reasons as one. This
can considered bad practice if we want to get the benefits of the strictness, but at least it is
clearly done.
/// get the length of the text in the file
let printLengthOfFile filePath =
let fileResult =
topLayerDo (fun fs->fs.ReadToEnd().Length) filePath
match fileResult with
| Success result ->
// note type-safe int printing with %i
printfn "length is: %i" result
| Failure _ ->
printfn "An error happened but I don't want to be specific"
Now let's see all this code work in practice with some interactive tests.
First set up a good file and a bad file.
/// write some text to a file
let writeSomeText filePath someText =
use writer = new System.IO.StreamWriter(filePath:string)
writer.WriteLine(someText:string)
writer.Close()
let goodFileName = "good.txt"
let badFileName = "bad.txt"
writeSomeText goodFileName "hello"
199
Functions return error types for each expected case (such as FileNotFound ), but the
handling of these types does not need to make the calling code ugly.
Functions continue to throw exceptions for unexpected cases (such as OutOfMemory ),
which will generally be caught and logged at the top level of the program.
This technique is simple and convenient. Similar (and more generic) approaches are
standard in functional programming.
It is feasible to use this approach in C# too, but it is normally impractical, due to the lack of
union types and the lack of type inference (we would have to specify generic types
everywhere).
200
201
202
By wrapping the email address in a special type, we ensure that normal strings cannot be
used as arguments to email specific functions. (In practice, we would also hide the
constructor of the EmailAddress type as well, to ensure that only valid values could be
created in the first place.)
There is nothing here that couldn't be done in C#, but it would be quite a lot of work to create
a new value type just for this one purpose, so in C#, it is easy to be lazy and just pass
strings around.
203
let printingExample =
printf "an int %i" 2 // ok
printf "an int %i" 2.0 // wrong type
printf "an int %i" "hello" // wrong type
printf "an int %i" // missing param
printf "a string %s" "hello" // ok
printf "a string %s" 2 // wrong type
printf "a string %s" // missing param
printf "a string %s" "he" "lo" // too many params
printf "an int %i and string %s" 2 "hello" // ok
printf "an int %i and string %s" "hello" 2 // wrong type
printf "an int %i and string %s" 2 // missing param
Unlike C#, the compiler analyses the format string and determines what the number and
types of the arguments are supposed to be.
This can be used to constrain the types of parameters without explicitly having to specify
them. So for example, in the code below, the compiler can deduce the types of the
arguments automatically.
let printAString x = printf "%s" x
let printAnInt x = printf "%i" x
// the result is:
// val printAString : string -> unit //takes a string parameter
// val printAnInt : int -> unit //takes an int parameter
Units of measure
F# has the ability to define units of measure and associate them with floats. The unit of
measure is then "attached" to the float as a type and prevents mixing different types. This is
another feature that can be very handy if you need it.
204
Type-safe equality
One final example. In C# any class can be equated with any other class (using reference
equality by default). In general, this is a bad idea! For example, you shouldn't really be able
to compare a string with a person at all.
Here is some C# code which is perfectly valid and compiles fine:
using System;
var obj = new Object();
var ex = new Exception();
var b = (obj == ex);
205
Chances are, if you are testing equality between two different types, you are doing
something wrong.
In F#, you can even stop a type being compared at all! This is not as silly as it seems. For
some types, there may not be a useful default, or you may want to force equality to be based
on a specific field rather than the object as whole.
Here is an example of this:
// deny comparison
[<NoEquality; NoComparison>]
type CustomerAccount = {CustomerAccountId: int}
let x = {CustomerAccountId = 1}
x = x // error!
x.CustomerAccountId = x.CustomerAccountId // no error
206
A bad design in C#
In C#, we might think that this is simple enough and dive straight into coding. Here is a
straightforward implementation in C# that seems OK at first glance.
207
208
What would happen if we had even more complicated requirements and the code was
thousands of lines long? For example, the fragment that is repeated everywhere:
if (!this.IsPaidFor) { do something }
looks like it will be quite brittle if requirements change in some methods but not others.
Before you read the next section, think for a minute how you might better implement the
requirements above in C#, with these additional requirements:
If you try to do something that is not allowed in the requirements, you will get a compile
time error, not a run time error. For example, you must create a design such that you
cannot even call the RemoveItem method from an empty cart.
The contents of the cart in any state should be immutable. The benefit of this is that if I
am in the middle of paying for a cart, the cart contents can't change even if some other
process is adding or removing items at the same time.
A correct design in F#
Let's step back and see if we can come up with a better design. Looking at these
requirements, it's obvious that we have a simple state machine with three states and some
state transitions:
A Shopping Cart can be Empty, Active or PaidFor
When you add an item to an Empty cart, it becomes Active
When you remove the last item from an Active cart, it becomes Empty
When you pay for an Active cart, it becomes PaidFor
And now we can add the business rules to this model:
You can add an item only to carts that are Empty or Active
You can remove an item only from carts that are Active
You can only pay for carts that are Active
Here is the state diagram:
209
It's worth noting that these kinds of state-oriented models are very common in business
systems. Product development, customer relationship management, order processing, and
other workflows can often be modeled this way.
Now we have the design, we can reproduce it in F#:
type CartItem = string // placeholder for a more complicated type
type EmptyState = NoItems // don't use empty list! We want to
// force clients to handle this as a
// separate case. E.g. "you have no
// items in your cart"
type ActiveState = { UnpaidItems : CartItem list; }
type PaidForState = { PaidItems : CartItem list;
Payment : decimal}
type Cart =
| Empty of EmptyState
| Active of ActiveState
| PaidFor of PaidForState
We create a type for each state, and Cart type that is a choice of any one of the states. I
have given everything a distinct name (e.g. PaidItems and UnpaidItems rather than just
Items ) because this helps the inference engine and makes the code more self
documenting.
This is a much longer example than the earlier ones! Don't worry too much about the F#
syntax right now, but I hope that you can get the gist of the code, and see how it fits into
the overall design.
Also, do paste the snippets into a script file and evaluate them for yourself as they come
up.
Next we can create the operations for each state. The main thing to note is each operation
will always take one of the States as input and return a new Cart. That is, you start off with a
particular known state, but you return a Cart which is a wrapper for a choice of three
possible states.
210
// =============================
// operations on empty state
// =============================
let addToEmptyState item =
// returns a new Active Cart
Cart.Active {UnpaidItems=[item]}
// =============================
// operations on active state
// =============================
let addToActiveState state itemToAdd =
let newList = itemToAdd :: state.UnpaidItems
Cart.Active {state with UnpaidItems=newList }
let removeFromActiveState state itemToRemove =
let newList = state.UnpaidItems
|> List.filter (fun i -> i<>itemToRemove)
match newList with
| [] -> Cart.Empty NoItems
| _ -> Cart.Active {state with UnpaidItems=newList}
let payForActiveState state amount =
// returns a new PaidFor Cart
Cart.PaidFor {PaidItems=state.UnpaidItems; Payment=amount}
And we can create some cart level helper methods as well. At the cart level, we have to
explicitly handle each possibility for the internal state with a match..with expression.
211
We now have an active cart with one item in it. Note that " cartA " is a completely different
object from " emptyCart " and is in a different state.
212
So far, so good. Again, all these are distinct objects in different states,
Let's test the requirement that you cannot remove items from an empty cart:
let emptyCart3 = emptyCart2.Remove "B" //error
printf "emptyCart3="; emptyCart3.Display
213
Nothing happens. The cart is empty, so the Active branch is not called. We might want to
raise an error or log a message in the other branches, but no matter what we do we cannot
accidentally call the Pay method on an empty cart, because that state does not have a
method to call!
The same thing happens if we accidentally try to pay for a cart that is already paid.
// try to pay for cartAB
let cartABPaid =
match cartAB with
| Empty _ | PaidFor _ -> cartAB // return the same cart
| Active state -> state.Pay 100m
// try to pay for cartAB again
let cartABPaidAgain =
match cartABPaid with
| Empty _ | PaidFor _ -> cartABPaid // return the same cart
| Active state -> state.Pay 100m
You might argue that the client code above might not be representative of code in the real
world ? it is well-behaved and already dealing with the requirements.
So what happens if we have badly written or malicious client code that tries to force
payment:
match cartABPaid with
| Empty state -> state.Pay 100m
| PaidFor state -> state.Pay 100m
| Active state -> state.Pay 100m
If we try to force it like this, we will get compile errors. There is no way the client can create
code that does not meet the requirements.
Summary
We have designed a simple shopping cart model which has many benefits over the C#
design.
It maps to the requirements quite clearly. It is impossible for a client of this API to call
code that doesn't meet the requirements.
Using states means that the number of possible code paths is much smaller than the C#
version, so there will be many fewer unit tests to write.
Each function is simple enough to probably work the first time, as, unlike the C# version,
there are no conditionals anywhere.
214
215
If you are interested to see what the C# code for a solution looks like, here it is below. This
code meets the requirements above and guarantees correctness at compile time, as
desired.
The key thing to note is that, because C# doesn't have union types, the implementation uses
a "fold" function, a function that has three function parameters, one for each state. To use
the cart, the caller passes a set of three lambdas in, and the (hidden) state determines what
happens.
var paidCart = cartA.Do(
// lambda for Empty state
state => cartA,
// lambda for Active state
state => state.Pay(100),
// lambda for Paid state
state => cartA);
This approach means that the caller can never call the "wrong" function, such as "Pay" for
the Empty state, because the parameter to the lambda will not support it. Try it and see!
using System;
using System.Collections.Generic;
using System.Linq;
namespace WhyUseFsharp
{
public class ShoppingCart<TItem>
{
#region ShoppingCart State classes
/// <summary>
/// Represents the Empty state
/// </summary>
public class EmptyState
{
public ShoppingCart<TItem> Add(TItem item)
{
var newItems = new[] { item };
var newState = new ActiveState(newItems);
return FromState(newState);
}
}
/// <summary>
/// Represents the Active state
/// </summary>
public class ActiveState
216
{
public ActiveState(IEnumerable<TItem> items)
{
Items = items;
}
public IEnumerable<TItem> Items { get; private set; }
public ShoppingCart<TItem> Add(TItem item)
{
var newItems = new List<TItem>(Items) {item};
var newState = new ActiveState(newItems);
return FromState(newState);
}
public ShoppingCart<TItem> Remove(TItem item)
{
var newItems = new List<TItem>(Items);
newItems.Remove(item);
if (newItems.Count > 0)
{
var newState = new ActiveState(newItems);
return FromState(newState);
}
else
{
var newState = new EmptyState();
return FromState(newState);
}
}
public ShoppingCart<TItem> Pay(decimal amount)
{
var newState = new PaidForState(Items, amount);
return FromState(newState);
}
}
/// <summary>
/// Represents the Paid state
/// </summary>
public class PaidForState
{
public PaidForState(IEnumerable<TItem> items, decimal amount)
{
Items = items.ToList();
Amount = amount;
}
public IEnumerable<TItem> Items { get; private set; }
public decimal Amount { get; private set; }
217
}
#endregion ShoppingCart State classes
//====================================
// Execute of shopping cart proper
//====================================
private enum Tag { Empty, Active, PaidFor }
private readonly Tag _tag = Tag.Empty;
private readonly object _state; //has to be a generic object
/// <summary>
/// Private ctor. Use FromState instead
/// </summary>
private ShoppingCart(Tag tagValue, object state)
{
_state = state;
_tag = tagValue;
}
public static ShoppingCart<TItem> FromState(EmptyState state)
{
return new ShoppingCart<TItem>(Tag.Empty, state);
}
public static ShoppingCart<TItem> FromState(ActiveState state)
{
return new ShoppingCart<TItem>(Tag.Active, state);
}
public static ShoppingCart<TItem> FromState(PaidForState state)
{
return new ShoppingCart<TItem>(Tag.PaidFor, state);
}
/// <summary>
/// Create a new empty cart
/// </summary>
public static ShoppingCart<TItem> NewCart()
{
var newState = new EmptyState();
return FromState(newState);
}
/// <summary>
/// Call a function for each case of the state
/// </summary>
/// <remarks>
/// Forcing the caller to pass a function for each possible case means that al
l cases are handled at all times.
/// </remarks>
public TResult Do<TResult>(
218
}
/// <summary>
/// Extension methods for my own personal library
/// </summary>
public static class ShoppingCartExtension
{
/// <summary>
/// Helper method to Add
/// </summary>
public static ShoppingCart<TItem> Add<TItem>(this ShoppingCart<TItem> cart, TI
tem item)
{
return cart.Do(
state => state.Add(item), //empty case
219
220
221
Concurrency
Concurrency
We hear a lot about concurrency nowadays, how important it is, and how it is "the next major
revolution in how we write software".
So what do we actually mean by "concurrency" and how can F# help?
The simplest definition of concurrency is just "several things happening at once, and maybe
interacting with each other". It seems a trivial definition, but the key point is that most
computer programs (and languages) are designed to work serially, on one thing at a time,
and are not well-equipped to handle concurrency.
And even if computer programs are written to handle concurrency, there is an even more
serious problem: our brains do not do well when thinking about concurrency. It is commonly
acknowledged that writing code that handles concurrency is extremely hard. Or I should say,
writing concurrent code that is correct is extremely hard! It's very easy to write concurrent
code that is buggy; there might be race conditions, or operations might not be atomic, or
tasks might be starved or blocked unnecessarily, and these issues are hard to find by
looking at the code or using a debugger.
Before talking about the specifics of F#, let's try to classify some of the common types of
concurrency scenarios that we have to deal with as developers:
"Concurrent Multitasking". This is when we have a number of concurrent tasks (e.g.
processes or threads) within our direct control, and we want them to communicate with
each other and share data safely.
"Asynchronous" programming. This is when we initiate a conversation with a
separate system outside our direct control, and then wait for it to get back to us.
Common cases of this are when talking to the filesystem, a database, or the network.
These situations are typically I/O bound, so you want to do something else useful while
you are waiting. These types of tasks are often non-deterministic as well, meaning that
running the same program twice might give a different result.
"Parallel" programming. This is when we have a single task that we want to split into
independant subtasks, and then run the subtasks in parallel, ideally using all the cores
or CPUs that are available. These situations are typically CPU bound. Unlike the async
tasks, parallelism is typically deterministic, so running the same program twice will give
the same result.
"Reactive" programming. This is when we do not initiate tasks ourselves, but are
focused on listening for events which we then process as fast as possible. This situation
occurs when designing servers, and when working with a user interface.
222
Concurrency
Of course, these are vague definitions and overlap in practice. In general, though, for all
these cases, the actual implementations that address these scenarios tend to use two
distinct approaches:
If there are lots of different tasks that need to share state or resources without waiting,
then use a "buffered asynchronous" design.
If there are lots of identical tasks that do not need to share state, then use parallel tasks
using "fork/join" or "divide and conquer" approaches.
223
Asynchronous programming
Asynchronous programming
In this post we'll have a look at a few ways to write asynchronous code in F#, and a very
brief example of parallelism as well.
Let's see a simple example where we wait for a timer event to go off:
open System
let userTimerWithCallback =
// create an event to wait on
let event = new System.Threading.AutoResetEvent(false)
// create a timer and add an event handler that will signal the event
let timer = new System.Timers.Timer(2000.0)
timer.Elapsed.Add (fun _ -> event.Set() |> ignore )
//start
printfn "Waiting for timer at %O" DateTime.Now.TimeOfDay
timer.Start()
// keep working
printfn "Doing something useful while waiting for event"
// block on the timer via the AutoResetEvent
event.WaitOne() |> ignore
//done
printfn "Timer ticked at %O" DateTime.Now.TimeOfDay
224
Asynchronous programming
The code above is reasonably straightforward, but does require you to instantiate an
AutoResetEvent, and could be buggy if the lambda is defined incorrectly.
directly from the event, without needing a lambda. The ignore is added to ignore the
result.
the event.WaitOne() has been replaced by Async.RunSynchronously timerEvent which
blocks on the async object until it has completed.
That's it. Both simpler and easier to understand.
The async workflows can also be used with IAsyncResult , begin/end pairs, and other
standard .NET methods.
225
Asynchronous programming
For example, here's how you might do an async file write by wrapping the IAsyncResult
generated from BeginWrite .
let fileWriteWithAsync =
// create a stream to write to
use stream = new System.IO.FileStream("test.txt",System.IO.FileMode.Create)
// start
printfn "Starting async write"
let asyncResult = stream.BeginWrite(Array.empty,0,0,null,null)
// create an async wrapper around an IAsyncResult
let async = Async.AwaitIAsyncResult(asyncResult) |> Async.Ignore
// keep working
printfn "Doing something useful while waiting for write to complete"
// block on the timer now by waiting for the async to complete
Async.RunSynchronously async
// done
printfn "Async write completed"
the background.
This simple workflow just sleeps for 2 seconds.
let sleepWorkflow = async{
printfn "Starting sleep workflow at %O" DateTime.Now.TimeOfDay
do! Async.Sleep 2000
printfn "Finished sleep workflow at %O" DateTime.Now.TimeOfDay
}
Async.RunSynchronously sleepWorkflow
Note: the code do! Async.Sleep 2000 is similar to Thread.Sleep but designed to work with
asynchronous workflows.
Workflows can contain other async workflows nested inside them. Within the braces, the
nested workflows can be blocked on by using the let! syntax.
226
Asynchronous programming
Cancelling workflows
One very convenient thing about async workflows is that they support a built-in cancellation
mechanism. No special code is needed.
Consider a simple task that prints numbers from 1 to 100:
let testLoop = async {
for i in [1..100] do
// do something
printf "%i before.." i
// sleep a bit
do! Async.Sleep 10
printfn "..after"
}
Now let's say we want to cancel this task half way through. What would be the best way of
doing it?
In C#, we would have to create flags to pass in and then check them frequently, but in F#
this technique is built in, using the CancellationToken class.
227
Asynchronous programming
In F#, any nested async call will check the cancellation token automatically!
In this case it was the line:
do! Async.Sleep(10)
As you can see from the output, this line is where the cancellation happened.
228
Asynchronous programming
Note: The #time command toggles the timer on and off. It only works in the interactive
window, so this example must be sent to the interactive window in order to work
corrrectly.
We're using the #time option to show the total elapsed time, which, because they run in
parallel, is 2 secs. If they ran in series instead, it would take 3 seconds.
Also you might see that the output is garbled sometimes because both tasks are writing to
the console at the same time!
This last sample is a classic example of a "fork/join" approach, where a number of a child
tasks are spawned and then the parent waits for them all to finish. As you can see, F#
makes this very easy!
229
Asynchronous programming
So here is a simple URL downloader, very similar to the one we saw at the start of the
series:
open System.Net
open System
open System.IO
let fetchUrl url =
let req = WebRequest.Create(Uri(url))
use resp = req.GetResponse()
use stream = resp.GetResponseStream()
use reader = new IO.StreamReader(stream)
let html = reader.ReadToEnd()
printfn "finished downloading %s" url
Make a note of the time taken, and let's if we can improve on it!
Obviously the example above is inefficient -- only one web site at a time is visited. The
program would be faster if we could visit them all at the same time.
So how would we convert this to a concurrent algorithm? The logic would be something like:
Create a task for each web page we are downloading, and then for each task, the
download logic would be something like:
Start downloading a page from a website. While that is going on, pause and let
other tasks have a turn.
When the download is finished, wake up and continue on with the task
Finally, start all the tasks up and let them go at it!
Unfortunately, this is quite hard to do in a standard C-like language. In C# for example, you
have to create a callback for when an async task completes. Managing these callbacks is
painful and creates a lot of extra support code that gets in the way of understanding the
230
Asynchronous programming
logic. There are some elegant solutions to this, but in general, the signal to noise ratio for
concurrent programming in C# is very high*.
* As of the time of this writing. Future versions of C# will have the await keyword, which is
similar to what F# has now.
But as you can guess, F# makes this easy. Here is the concurrent F# version of the
downloader code:
open Microsoft.FSharp.Control.CommonExtensions
// adds AsyncGetResponse
// Fetch the contents of a web page asynchronously
let fetchUrlAsync url =
async {
let req = WebRequest.Create(Uri(url))
use! resp = req.AsyncGetResponse() // new keyword "use!"
use stream = resp.GetResponseStream()
use reader = new IO.StreamReader(stream)
let html = reader.ReadToEnd()
printfn "finished downloading %s" url
}
Note that the new code looks almost exactly the same as the original. There are only a few
minor changes.
The change from " use resp = " to " use! resp = " is exactly the change that we talked
about above -- while the async operation is going on, let other tasks have a turn.
We also used the extension method AsyncGetResponse defined in the CommonExtensions
namespace. This returns an async workflow that we can nest inside the main workflow.
In addition the whole set of steps is contained in the " async {...} " wrapper which turns
it into a block that can be run asynchronously.
And here is a timed download using the async version.
// a list of sites to fetch
let sites = ["http://www.bing.com";
"http://www.google.com";
"http://www.microsoft.com";
"http://www.amazon.com";
"http://www.yahoo.com"]
#time // turn interactive timer on
sites
|> List.map fetchUrlAsync // make a list of async tasks
|> Async.Parallel // set up the tasks to run in parallel
|> Async.RunSynchronously // start them off
#time // turn timer off
231
Asynchronous programming
Adjust the upper bounds of the loops as needed to make this run in about 0.2 seconds.
232
Asynchronous programming
Now let's combine a bunch of these into a single serial task (using composition), and test it
with the timer:
let parentTask =
childTask
|> List.replicate 20
|> List.reduce (>>)
//test
#time
parentTask()
#time
And to combine a bunch of asyncs into a single parallel task, we use Async.Parallel .
Let's test this and compare the timings:
let asyncParentTask =
asyncChildTask
|> List.replicate 20
|> Async.Parallel
//test
#time
asyncParentTask
|> Async.RunSynchronously
#time
On a dual-core machine, the parallel version is about 50% faster. It will get faster in
proportion to the number of cores or CPUs, of course, but sublinearly. Four cores will be
faster than one core, but not four times faster.
On the other hand, as with the async web download example, a few minor code changes
can make a big difference, while still leaving the code easy to read and understand. So in
cases where parallelism will genuinely help, it is nice to know that it is easy to arrange.
233
234
#nowarn "40"
let printerAgent = MailboxProcessor.Start(fun inbox->
// the message processing function
let rec messageLoop = async{
// read a message
let! msg = inbox.Receive()
// process a message
printfn "message is: %s" msg
// loop to top
return! messageLoop
}
// start the loop
messageLoop
)
The MailboxProcessor.Start function takes a simple function parameter. That function loops
forever, reading messages from the queue (or "inbox") and processing them.
Note: I have added the #nowarn "40" pragma to avoid the warning "FS0040", which can be
safely ignored in this case.
Here's the example in use:
235
// test it
printerAgent.Post "hello"
printerAgent.Post "hello again"
printerAgent.Post "hello a third time"
In the rest of this post we'll look at two slightly more useful examples:
Managing shared state without locks
Serialized and buffered access to shared IO
In both of these cases, a message based approach to concurrency is elegant, efficient, and
easy to program.
236
open System
open System.Threading
open System.Diagnostics
// a utility function
type Utility() =
static let rand = new Random()
static member RandomSleep() =
let ms = rand.Next(1,10)
Thread.Sleep ms
// an implementation of a shared counter using locks
type LockedCounter () =
static let _lock = new Object()
static let mutable count = 0
static let mutable sum = 0
static let updateState i =
// increment the counters and...
sum <- sum + i
count <- count + 1
printfn "Count is: %i. Sum is: %i" count sum
// ...emulate a short delay
Utility.RandomSleep()
237
The public Add method has explicit Monitor.Enter and Monitor.Exit expressions to
get and release the lock. This is the same as the lock{...} statement in C#.
We've also added a stopwatch to measure how long a client has to wait to get the lock.
The core "business logic" is the updateState method, which not only updates the state,
but adds a small random wait as well to emulate the time taken to do the processing.
Let's test it in isolation:
// test in isolation
LockedCounter.Add 4
LockedCounter.Add 5
Next, we'll create a task that will try to access the counter:
let makeCountingTask addFunction taskId = async {
let name = sprintf "Task%i" taskId
for i in [1..3] do
addFunction i
}
// test in isolation
let task = makeCountingTask LockedCounter.Add 1
Async.RunSynchronously task
In this case, when there is no contention at all, the wait times are all 0.
But what happens when we create 10 child tasks that all try to access the counter at once:
let lockedExample5 =
[1..10]
|> List.map (fun i -> makeCountingTask LockedCounter.Add i)
|> Async.Parallel
|> Async.RunSynchronously
|> ignore
Oh dear! Most tasks are now waiting quite a while. If two tasks want to update the state at
the same time, one must wait for the other's work to complete before it can do its own work,
which affects performance.
And if we add more and more tasks, the contention will increase, and the tasks will spend
more and more time waiting rather than working.
238
type MessageBasedCounter () =
static let updateState (count,sum) msg =
// increment the counters and...
let newSum = sum + msg
let newCount = count + 1
printfn "Count is: %i. Sum is: %i" newCount newSum
// ...emulate a short delay
Utility.RandomSleep()
// return the new state
(newCount,newSum)
// create the agent
static let agent = MailboxProcessor.Start(fun inbox ->
// the message processing function
let rec messageLoop oldState = async{
// read a message
let! msg = inbox.Receive()
// do the core logic
let newState = updateState oldState msg
// loop to top
return! messageLoop newState
}
// start the loop
messageLoop (0,0)
)
// public interface to hide the implementation
static member Add i = agent.Post i
This code is written in a more functional way; there are no mutable variables and no
locks anywhere. In fact, there is no code dealing with concurrency at all! The code only
239
has to focus on the business logic, and is consequently much easier to understand.
Let's test it in isolation:
// test in isolation
MessageBasedCounter.Add 4
MessageBasedCounter.Add 5
Next, we'll reuse a task we defined earlier, but calling MessageBasedCounter.Add instead:
let task = makeCountingTask MessageBasedCounter.Add 1
Async.RunSynchronously task
Finally let's create 5 child tasks that try to access the counter at once.
let messageExample5 =
[1..5]
|> List.map (fun i -> makeCountingTask MessageBasedCounter.Add i)
|> Async.Parallel
|> Async.RunSynchronously
|> ignore
We can't measure the waiting time for the clients, because there is none!
Shared IO
A similar concurrency problem occurs when accessing a shared IO resource such as a file:
If the IO is slow, the clients can spend a lot of time waiting, even without locks.
If multiple threads write to the resource at the same time, you can get corrupted data.
Both problems can be solved by using asynchronous calls combined with buffering -- exactly
what a message queue does.
In this next example, we'll consider the example of a logging service that many clients will
write to concurrently. (In this trivial case, we'll just write directly to the Console.)
We'll first look at an implementation without concurrency control, and then at an
implementation that uses message queues to serialize all requests.
IO without serialization
240
In order to make the corruption very obvious and repeatable, let's first create a "slow"
console that writes each individual character in the log message and pauses for a
millisecond between each character. During that millisecond, another thread could be writing
as well, causing an undesirable interleaving of messages.
let slowConsoleWrite msg =
msg |> String.iter (fun ch->
System.Threading.Thread.Sleep(1)
System.Console.Write ch
)
// test in isolation
slowConsoleWrite "abc"
Next, we will create a simple task that loops a few times, writing its name each time to the
logger:
let makeTask logger taskId = async {
let name = sprintf "Task%i" taskId
for i in [1..3] do
let msg = sprintf "-%s:Loop%i-" name i
logger msg
}
// test in isolation
let task = makeTask slowConsoleWrite 1
Async.RunSynchronously task
Next, we write a logging class that encapsulates access to the slow console. It has no
locking or serialization, and is basically not thread-safe:
type UnserializedLogger() =
// interface
member this.Log msg = slowConsoleWrite msg
// test in isolation
let unserializedLogger = UnserializedLogger()
unserializedLogger.Log "hello"
Now let's combine all these into a real example. We will create five child tasks and run them
in parallel, all trying to write to the slow console.
241
let unserializedExample =
let logger = new UnserializedLogger()
[1..5]
|> List.map (fun i -> makeTask logger.Log i)
|> Async.Parallel
|> Async.RunSynchronously
|> ignore
242
So now we can repeat the earlier unserialized example but using the SerializedLogger
instead. Again, we create five child tasks and run them in parallel:
let serializedExample =
let logger = new SerializedLogger()
[1..5]
|> List.map (fun i -> makeTask logger.Log i)
|> Async.Parallel
|> Async.RunSynchronously
|> ignore
Summary
There is much more to say about this message based approach. In a future series, I hope to
go into much more detail, including discussion of topics such as:
alternative implementations of message queues with MSMQ and TPL Dataflow.
cancellation and out of band messages.
error handling and retries, and handling exceptions in general.
how to scale up and down by creating or removing child agents.
avoiding buffer overruns and detecting starvation or inactivity.
243
244
open System
open System.Threading
/// create a timer and register an event handler,
/// then run the timer for five seconds
let createTimer timerInterval eventHandler =
// setup a timer
let timer = new System.Timers.Timer(float timerInterval)
timer.AutoReset <- true
// add an event handler
timer.Elapsed.Add eventHandler
// return an async task
async {
// start timer...
timer.Start()
// ...run for five seconds...
do! Async.Sleep 5000
// ... and stop
timer.Stop()
}
Now let's create a similar utility method to create a timer, but this time it will return an
"observable" as well, which is the stream of events.
245
The difference is that instead of registering a handler directly with an event, we are
"subscribing" to an event stream. Subtly different, and important.
Counting events
In this next example, we'll have a slightly more complex requirement:
Create a timer that ticks every 500ms.
At each tick, print the number of ticks so far and the current time.
To do this in a classic imperative way, we would probably create a class with a mutable
counter, as below:
246
type ImperativeTimerCount() =
let mutable count = 0
// the event handler. The event args are ignored
member this.handleEvent _ =
count <- count + 1
printfn "timer ticked with count %i" count
Here we see how you can build up layers of event transformations, just as you do with list
transformations in LINQ.
The first transformation is scan , which accumulates state for each event. It is roughly
equivalent to the List.fold function that we have seen used with lists. In this case, the
accumulated state is just a counter.
And then, for each event, the count is printed out.
Note that in this functional approach, we didn't have any mutable state, and we didn't need
to create any special classes.
247
First let's create some code that both implementations can use.
We'll want a generic event type that captures the timer id and the time of the tick.
type FizzBuzzEvent = {label:int; time: DateTime}
And then we need a utility function to see if two events are simultaneous. We'll be generous
and allow a time difference of up to 50ms.
let areSimultaneous (earlierEvent,laterEvent) =
let {label=_;time=t1} = earlierEvent
let {label=_;time=t2} = laterEvent
t2.Subtract(t1).Milliseconds < 50
In the imperative design, we'll need to keep track of the previous event, so we can compare
them. And we'll need special case code for the first time, when the previous event doesn't
exist
248
type ImperativeFizzBuzzHandler() =
let mutable previousEvent: FizzBuzzEvent option = None
let printEvent thisEvent =
let {label=id; time=t} = thisEvent
printf "[%i] %i.%03i " id t.Second t.Millisecond
let simultaneous = previousEvent.IsSome && areSimultaneous (previousEvent.Value,
thisEvent)
if simultaneous then printfn "FizzBuzz"
elif id = 3 then printfn "Fizz"
elif id = 5 then printfn "Buzz"
member this.handleEvent3 eventArgs =
let event = {label=3; time=DateTime.Now}
printEvent event
previousEvent <- Some event
member this.handleEvent5 eventArgs =
let event = {label=5; time=DateTime.Now}
printEvent event
previousEvent <- Some event
Now the code is beginning to get ugly fast! Already we have mutable state, complex
conditional logic, and special cases, just for such a simple requirement.
Let's test it:
// create the class
let handler = new ImperativeFizzBuzzHandler()
// create the two timers and register the two handlers
let timer3 = createTimer 300 handler.handleEvent3
let timer5 = createTimer 500 handler.handleEvent5
// run the two timers at the same time
[timer3;timer5]
|> Async.Parallel
|> Async.RunSynchronously
It does work, but are you sure the code is not buggy? Are you likely to accidentally break
something if you change it?
The problem with this imperative code is that it has a lot of noise that obscures the the
requirements.
Can the functional version do better? Let's see!
First, we create two event streams, one for each timer:
249
Next, we convert each event on the "raw" event streams into our FizzBuzz event type:
// convert the time events into FizzBuzz events with the appropriate id
let eventStream3 =
timerEventStream3
|> Observable.map (fun _ -> {label=3; time=DateTime.Now})
let eventStream5 =
timerEventStream5
|> Observable.map (fun _ -> {label=5; time=DateTime.Now})
Now, to see if two events are simultaneous, we need to compare them from the two different
streams somehow.
It's actually easier than it sounds, because we can:
combine the two streams into a single stream:
then create pairs of sequential events
then test the pairs to see if they are simultaneous
then split the input stream into two new output streams based on that test
Here's the actual code to do this:
// combine the two streams
let combinedStream =
Observable.merge eventStream3 eventStream5
// make pairs of events
let pairwiseStream =
combinedStream |> Observable.pairwise
// split the stream based on whether the pairs are simultaneous
let simultaneousStream, nonSimultaneousStream =
pairwiseStream |> Observable.partition areSimultaneous
Finally, we can split the nonSimultaneousStream again, based on the event id:
250
Let's review so far. We have started with the two original event streams and from them
created four new ones:
combinedStream contains all the events
simultaneousStream contains only the simultaneous events
fizzStream contains only the non-simultaneous events with id=3
buzzStream contains only the non-simultaneous events with id=5
251
252
The code might seem a bit long winded, but this kind of incremental, step-wise approach is
very clear and self-documenting.
Some of the benefits of this style are:
I can see that it meets the requirements just by looking at it, without even running it. Not
so with the imperative version.
From a design point of view, each final "output" stream follows the single responsibility
principle -- it only does one thing -- so it is very easy to associate behavior with it.
This code has no conditionals, no mutable state, no edge cases. It would be easy to
maintain or change, I hope.
It is easy to debug. For example, I could easily "tap" the output of the
simultaneousStream to see if it contains what I think it contains:
// debugging code
//simultaneousStream |> Observable.subscribe (fun e -> printfn "sim %A" e)
//nonSimultaneousStream |> Observable.subscribe (fun e -> printfn "non-sim %A" e)
Summary
Functional Reactive Programming (known as FRP) is a big topic, and we've only just
touched on it here. I hope this introduction has given you a glimpse of the usefulness of this
way of doing things.
If you want to learn more, see the documentation for the F# Observable module, which has
the basic transformations used above. And there is also the Reactive Extensions (Rx) library
which shipped as part of .NET 4. That contains many other additional transformations.
253
Completeness
Completeness
In this final set of posts, we will look at some other aspects of F# under the theme of
"completeness".
Programming languages coming from the academic world tend to focus on elegance and
purity over real-world usefulness, while more mainstream business languages such as C#
and Java are valued precisely because they are pragmatic; they can work in a wide array of
situations and have extensive tools and libraries to meet almost every need. In other words,
to be useful in the enterprise, a language needs to be complete, not just well-designed.
F# is unusual in that it successfully bridges both worlds. Although all the examples so far
have focused on F# as an elegant functional language, it does support an object-oriented
paradigm as well, and can integrate easily with other .NET languages and tools. As a result,
F# is not a isolated island, but benefits from being part of the whole .NET ecosystem.
The other aspects that make F# "complete" are being an official .NET language (with all the
support and documentation that that entails) and being designed to work in Visual Studio
(which provides a nice editor with IntelliSense support, a debugger, and so on). These
benefits should be obvious and won't be discussed here.
So, in this last section, we'll focus on two particular areas:
Seamless interoperation with .NET libraries. Obviously, there can be a mismatch
between the functional approach of F# and the imperative approach that is designed
into the base libraries. We'll look at some of the features of F# that make this integration
easier.
Full support for classes and other C# style code. F# is designed as a hybrid
functional/OO language, so it can do almost everything that C# can do as well. We'll
have a quick tour of the syntax for these other features.
254
indeed seamless.
For more complex requirements, F# natively supports .NET classes, interfaces, and
structures, so the interop is still very straightforward. For example, you can write an
ISomething interface in C# and have the implementation be done in F#.
But not only can F# call into existing .NET code, it can also expose almost any .NET API
back to other languages. For example, you can write classes and methods in F# and expose
them to C#, VB or COM. You can even do the above example backwards -- define an
ISomething interface in F# and have the implementation be done in C#! The benefit of all
this is that you don't have to discard any of your existing code base; you can start using F#
for some things while retaining C# or VB for others, and pick the best tool for the job.
In addition to the tight integration though, there are a number of nice features in F# that often
make working with .NET libraries more convenient than C# in some ways. Here are some of
my favorites:
You can use TryParse and TryGetValue without passing an "out" parameter.
You can resolve method overloads by using argument names, which also helps with
type inference.
You can use "active patterns" to convert .NET APIs into more friendly code.
You can dynamically create objects from an interface such as IDisposable without
creating a concrete class.
You can mix and match "pure" F# objects with existing .NET APIs
255
//using an Int32
let (i1success,i1) = System.Int32.TryParse("123");
if i1success then printfn "parsed as %i" i1 else printfn "parse failed"
let (i2success,i2) = System.Int32.TryParse("hello");
if i2success then printfn "parsed as %i" i2 else printfn "parse failed"
//using a DateTime
let (d1success,d1) = System.DateTime.TryParse("1/1/1980");
let (d2success,d2) = System.DateTime.TryParse("hello");
//using a dictionary
let dict = new System.Collections.Generic.Dictionary<string,string>();
dict.Add("a","hello")
let (e1success,e1) = dict.TryGetValue("a");
let (e2success,e2) = dict.TryGetValue("b");
The problem is that F# does not know if the argument is supposed to be a string or a stream.
You could explicitly specify the type of the argument, but that is not the F# way!
Instead, a nice workaround is enabled by the fact that in F#, when calling methods in .NET
libraries, you can specify named arguments.
let createReader2 fileName = new System.IO.StreamReader(path=fileName)
In many cases, such as the one above, just using the argument name is enough to resolve
the type issue. And using explicit argument names can often help to make the code more
legible anyway.
256
There are many situations where you want to use pattern matching against .NET types, but
the native libraries do not support this. Earlier, we briefly touched on the F# feature called
"active patterns" which allows you to dynamically create choices to match on. This can be
very for useful .NET integration.
A common case is that a .NET library class has a number of mutually exclusive
isSomething , isSomethingElse methods, which have to be tested with horrible looking
cascading if-else statements. Active patterns can hide all the ugly testing, letting the rest of
your code use a more natural approach.
For example, here's the code to test for various isXXX methods for System.Char .
let (|Digit|Letter|Whitespace|Other|) ch =
if System.Char.IsDigit(ch) then Digit
else if System.Char.IsLetter(ch) then Letter
else if System.Char.IsWhiteSpace(ch) then Whitespace
else Other
Once the choices are defined, the normal code can be straightforward:
let printChar ch =
match ch with
| Digit -> printfn "%c is a Digit" ch
| Letter -> printfn "%c is a Letter" ch
| Whitespace -> printfn "%c is a Whitespace" ch
| _ -> printfn "%c is something else" ch
// print a list
['a';'b';'1';' ';'-';'c'] |> List.iter printChar
Another common case is when you have to parse text or error codes to determine the type
of an exception or result. Here's an example that uses an active pattern to parse the error
number associated with SqlExceptions , making them more palatable.
First, set up the active pattern matching on the error number:
open System.Data.SqlClient
let (|ConstraintException|ForeignKeyException|Other|) (ex:SqlException) =
if ex.Number = 2601 then ConstraintException
else if ex.Number = 2627 then ConstraintException
else if ex.Number = 547 then ForeignKeyException
else Other
257
The example also demonstrates how the " use " keyword automatically disposes a resource
when it goes out of scope. Here is the output:
258
But we want to have all the benefits of pattern matching, etc, so we have created pure F#
types for cats and dogs instead of classes.
type Cat = Felix | Socks
type Dog = Butch | Lassie
But using this pure F# approach means that that we cannot pass the cats and dogs to the
showTheNoiseAnAnimalMakes function directly.
However, we don't have to create new sets of concrete classes just to implement IAnimal .
Instead, we can dynamically create the IAnimal interface by extending the pure F# types.
259
This approach gives us the best of both worlds. Pure F# types internally, but the ability to
convert them into interfaces as needed to interface with libraries.
260
open System.Reflection
open Microsoft.FSharp.Reflection
// create a record type...
type Account = {Id: int; Name: string}
// ... and show the fields
let fields =
FSharpType.GetRecordFields(typeof<Account>)
|> Array.map (fun propInfo -> propInfo.Name, propInfo.PropertyType.Name)
// create a union type...
type Choices = | A of int | B of string
// ... and show the choices
let choices =
FSharpType.GetUnionCases(typeof<Choices>)
|> Array.map (fun choiceInfo -> choiceInfo.Name)
261
262
// interface
type IEnumerator<'a> =
abstract member Current : 'a
abstract MoveNext : unit -> bool
// abstract base class with virtual methods
[<AbstractClass>]
type Shape() =
//readonly properties
abstract member Width : int with get
abstract member Height : int with get
//non-virtual method
member this.BoundingArea = this.Height * this.Width
//virtual method with base implementation
abstract member Print : unit -> unit
default this.Print () = printfn "I'm a shape"
// concrete class that inherits from base class and overrides
type Rectangle(x:int, y:int) =
inherit Shape()
override this.Width = x
override this.Height = y
override this.Print () = printfn "I'm a Rectangle"
//test
let r = Rectangle(2,3)
printfn "The width is %i" r.Width
printfn "The area is %i" r.BoundingArea
r.Print()
263
type Circle(rad:int) =
inherit Shape()
//mutable field
let mutable radius = rad
//property overrides
override this.Width = radius * 2
override this.Height = radius * 2
//alternate constructor with default radius
new() = Circle(10)
//property with get and set
member this.Radius
with get() = radius
and set(value) = radius <- value
// test constructors
let c1 = Circle() // parameterless ctor
printfn "The width is %i" c1.Width
let c2 = Circle(2) // main ctor
printfn "The width is %i" c2.Width
// test mutable property
c2.Radius <- 3
printfn "The width is %i" c2.Width
Generics
F# supports generics and all the associated constraints.
// standard generics
type KeyValuePair<'a,'b>(key:'a, value: 'b) =
member this.Key = key
member this.Value = value
// generics with constraints
type Container<'a,'b
when 'a : equality
and 'b :> System.Collections.ICollection>
(name:'a, values:'b) =
member this.Name = name
member this.Values = values
Structs
264
F# supports not just classes, but the .NET struct types as well, which can help to boost
performance in certain cases.
type Point2D =
struct
val X: float
val Y: float
new(x: float, y: float) = { X = x; Y = y }
end
//test
let p = Point2D() // zero initialized
let p2 = Point2D(2.0,3.0) // explicitly initialized
Exceptions
F# can create exception classes, raise them and catch them.
// create a new Exception class
exception MyError of string
try
let e = MyError("Oops!")
raise e
with
| MyError msg ->
printfn "The exception error was %s" msg
| _ ->
printfn "Some other exception"
Extension methods
Just as in C#, F# can extend existing classes with extension methods.
265
Parameter arrays
Just like C#'s variable length "params" keyword, this allows a variable length list of
arguments to be converted to a single array parameter.
open System
type MyConsole() =
member this.WriteLine([<ParamArray>] args: Object[]) =
for arg in args do
printfn "%A" arg
let cons = new MyConsole()
cons.WriteLine("abc", 42, 3.14, true)
Events
F# classes can have events, and the events can be triggered and responded to.
266
type MyButton() =
let clickEvent = new Event<_>()
[<CLIEvent>]
member this.OnClick = clickEvent.Publish
member this.TestEvent(arg) =
clickEvent.Trigger(this, arg)
// test
let myButton = new MyButton()
myButton.OnClick.Add(fun (sender, arg) ->
printfn "Click event with arg=%O" arg)
myButton.TestEvent("Hello World!")
Delegates
F# can do delegates.
// delegates
type MyDelegate = delegate of int -> int
let f = MyDelegate (fun x -> x * x)
let result = f.Invoke(5)
Enums
F# supports CLI enums types, which look similar to the "union" types, but are actually
different behind the scenes.
// enums
type Color = | Red=1 | Green=2 | Blue=3
let color1 = Color.Red // simple assignment
let color2:Color = enum 2 // cast from int
// created from parsing a string
let color3 = System.Enum.Parse(typeof<Color>,"Green") :?> Color // :?> is a downcast
[<System.FlagsAttribute>]
type FileAccess = | Read=1 | Write=2 | Execute=4
let fileaccess = FileAccess.Read ||| FileAccess.Write
Finally, F# can work with the WinForms and WPF user interface libraries, just like C#.
Here is a trivial example of opening a form and handling a click event.
open System.Windows.Forms
let form = new Form(Width= 400, Height = 300, Visible = true, Text = "Hello World")
form.TopMost <- true
form.Click.Add (fun args-> printfn "the form was clicked")
form.Show()
268
269
This series of posts will introduce you to the fundamentals of functional programming -- what
does it really mean to "program functionally", and how this approach differs from object
oriented or imperative programming.
Thinking Functionally: Introduction. A look at the basics of functional programming.
Mathematical functions. The impetus behind functional programming.
Function Values and Simple Values. Binding not assignment.
How types work with functions. Understanding the type notation.
Currying. Breaking multi-parameter functions into smaller one-parameter functions.
Partial application. Baking-in some of the parameters of a function.
Function associativity and composition. Building new functions from existing ones.
Defining functions. Lambdas and more.
Function signatures. A function signature can give you some idea of what it does.
Organizing functions. Nested functions and modules.
Attaching functions to types. Creating methods the F# way.
Worked example: A stack based calculator. Using combinators to build functionality.
270
272
Mathematical functions
Mathematical functions
The impetus behind functional programming comes from mathematics. Mathematical
functions have a number of very nice features that functional languages try to emulate in the
real world.
So first, let's start with a mathematical function that adds 1 to a number.
Add1(x) = x+1
What does this really mean? Well it seems pretty straightforward. It means that there is an
operation that starts with a number, and adds one to it.
Let's introduce some terminology:
The set of values that can be used as input to the function is called the domain. In this
case, it could be the set of real numbers, but to make life simpler for now, let's restrict it
to integers only.
The set of possible output values from the function is called the range (technically, the
image on the codomain). In this case, it is also the set of integers.
The function is said to map the domain to the range.
If you type that into the F# interactive window (don't forget the double semicolons) you will
see the result (the "signature" of the function):
val add1 : int -> int
273
Mathematical functions
274
Mathematical functions
Obviously, we can't have a case for every possible integer, but the principle is the same. You
can see that absolutely no calculation is being done at all, just a lookup.
I would not expect x to be changed by the adding of one to it. I would expect to get back a
different number (y) and x would be left untouched. In the world of mathematics, the integers
already exist as an unchangeable set, and the "add1" function simply defines a relationship
between them.
275
Mathematical functions
You have already seen an example of parallelism in the "why use F#?" series.
Evaluating functions lazily will be discussed in the "optimization" series.
Caching the results of functions is called "memoization" and will also be discussed in
the "optimization" series.
Not caring about the order of evaluation makes concurrent programming much easier,
and doesn't introduce bugs when functions are reordered or refactored.
276
Mathematical functions
277
It is important to understand that this is not assignment. "x" is not a "slot" or variable that is
assigned to the value and can be assigned to another value later on. It is a onetime
association of the name "x" with the value. The value is one of the predefined integers, and
cannot change. And so, once bound, x cannot change either; once associated with a value,
always associated with a value.
This concept is a critical part of thinking functionally: there are no "variables", only values.
Function values
If you think about this a bit more, you will see that the name " add1 " itself is just a binding to
"the function that adds one to its input". The function itself is independent of the name it is
bound to.
278
When you type let add1 x = x + 1 you are telling the F# compiler "every time you see the
name " add1 ", replace it with the function that adds 1 to its input". " add1 " is called a
function value.
To see that the function is independent of its name, try:
let add1 x = x + 1
let plus1 = add1
add1 5
plus1 5
You can see that " add1 " and " plus1 " are two names that refer ("bound to") to the same
function.
You can always identify a function value because its signature has the standard form domain
-> range . Here is a generic function value signature:
Simple values
Imagine an operation that always returned the integer 5 and didn't have any input.
279
There is no mapping arrow this time, just a single int. What's new is an equals sign with the
actual value printed after it. The F# compiler knows that this binding has a known value
which it will always return, namely the value 5.
In other words, we've just defined a constant, or in F# terms, a simple value.
You can always tell a simple value from a function value because all simple values have a
signature that looks like:
val aName: type = constant // Note that there is no arrow
instead of:
val c : int = 5
In a functional programming language like F#, most things are called "values". In an objectoriented language like C#, most things are called "objects". So what is the difference
between a "value" and an "object"?
A value, as we have seen above, is just a member of a domain. The domain of ints, the
domain of strings, the domain of functions that map ints to strings, and so on. In principle,
values are immutable. And values do not have any behavior attached them.
An object, in a standard definition, is an encapsulation of a data structure with its associated
behavior (methods). In general, objects are expected to have state (that is, be mutable), and
all operations that change the internal state must be provided by the object itself (via "dot"
notation).
In F#, even the primitive values have some object-like behavior. For example, you can dot
into a string to get its length:
"abc".Length
But, in general, we will avoid using "object" for standard values in F#, reserving it to refer to
instances of true classes, or other values that expose member methods.
Naming Values
Standard naming rules are used for value and function names, basically, any alphanumeric
string, including underscores. There are a couple of extras:
You can put an apostrophe anywhere in a name, except the first character. So:
A'b'c begin' // valid names
The final tick is often used to signal some sort of "variant" version of a value:
let f = x
let f' = derivative f
let f'' = derivative f'
You can also put double backticks around any string to make a valid identifier.
281
When trying to use natural language for business rules, unit tests, or BDD style
executable specifications a la Cucumber.
let ``is first time customer?`` = true
let ``add gift to order`` = ()
if ``is first time customer?`` then ``add gift to order``
// Unit test
let [<Test>] ``When input is 2 then expect square is 4``=
// code here
// BDD clause
let [<Given>] ``I have (.*) N products in my cart`` (n:int) =
// code here
Unlike C#, the naming convention for F# is that functions and values start with lowercase
letters rather than uppercase ( camelCase rather than PascalCase ) unless designed for
exposure to other .NET languages. Types and modules use uppercase however.
282
If you evaluate that in the F# interactive window, you will see the signatures:
val intToString : int -> string
val stringToInt : string -> int
This means:
intToString has a domain of int which it maps onto the range string .
stringToInt has a domain of string which it maps onto the range int .
Primitive types
The possible primitive types are what you would expect: string, int, float, bool, char, byte,
etc., plus many more derived from the .NET type system.
Here are some more examples of functions using primitive types:
let intToFloat x = float x // "float" fn. converts ints to floats
let intToBool x = (x = 2) // true if x equals 2
let stringToString x = x + " world"
283
Type annotations
In the previous examples, the F# compiler correctly determined the types of the parameters
and results. But this is not always the case. If you try the following code, you will get a
compiler error:
let stringLength x = x.Length
=> error FS0072: Lookup on object of indeterminate type
The compiler does not know what type "x" is, and therefore does not know if "Length" is a
valid method. In most cases, this can be fixed by giving the F# compiler a "type annotation"
so that it knows which type to use. In the corrected version below, we indicate that the type
of "x" is a string.
let stringLength (x:string) = x.Length
The parens around the x:string param are important. If they are missing, the compiler
thinks that the return value is a string! That is, an "open" colon is used to indicate the type of
the return value, as you can see in the example below.
let stringLengthAsInt (x:string) :int = x.Length
We're indicating that the x param is a string and the return value is an int.
284
You can see that the domain is (int->int) and the range is int . What does that mean? It
means that the input parameter is not a simple value, but a function, and what's more is
restricted only to functions that map ints to ints . The output is not a function, just an int.
Let's try it:
let add1 x = x + 1 // define a function of type (int -> int)
evalWith5ThenAdd2 add1 // test it
gives:
val add1 : int -> int
val it : int = 8
" add1 " is a function that maps ints to ints, as we can see from its signature. So it is a valid
parameter for the evalWith5ThenAdd2 function. And the result is 8.
By the way, the special word " it " is used for the last thing that was evaluated; in this case
the result we want. It's not a keyword, just a convention.
Here's another one:
let times3 x = x * 3 // a function of type (int -> int)
evalWith5ThenAdd2 times3 // test it
gives:
val times3 : int -> int
val it : int = 17
" times3 " is also a function that maps ints to ints, as we can see from its signature. So it is
also a valid parameter for the evalWith5ThenAdd2 function. And the result is 17.
Note that the input is sensitive to the types. If our input function uses floats rather than
ints , it will not work. For example, if we have:
285
meaning that the input function should have been an int->int function.
Functions as output
A function value can also be the output of a function. For example, the following function will
generate an "adder" function that adds using the input value.
let adderGenerator numberToAdd = (+) numberToAdd
which means that the generator takes an int , and creates a function (the "adder") that
maps ints to ints . Let's see how it works:
let add1 = adderGenerator 1
let add2 = adderGenerator 2
This creates two adder functions. The first generated function adds 1 to its input, and the
second adds 2. Note that the signatures are just as we would expect them to be.
val add1 : (int -> int)
val add2 : (int -> int)
And we can now use these generated functions in the normal way. They are
indistinguishable from functions defined explicitly
add1 5 // val it : int = 6
add2 5 // val it : int = 7
In this case F# could deduce that " fn " mapped ints to ints , so its signature would be
int->int
Obviously, " fn " is some kind of function that takes an int, but what does it return? The
compiler can't tell. If you do want to specify the type of the function, you can add a type
annotation for function parameters in the same way as for a primitive type.
let evalWith5AsInt (fn:int->int) = fn 5
let evalWith5AsFloat (fn:int->float) = fn 5
Because the main function returns a string, the " fn " function is also constrained to return a
string, so no explicit typing is required for "fn".
287
Well, even if a function returns no output, it still needs a range. There are no "void" functions
in mathematics-land. Every function must have some output, because a function is a
mapping, and a mapping has to have something to map to!
So in F#, functions like this return a special range called " unit ". This range has exactly one
value in it, called " () ". You can think of unit and () as somewhat like "void" (the type)
and "null" (the value) in C#. But unlike void/null, unit is a real type and () is a real value.
To see this, evaluate:
let whatIsThis = ()
Which means that the value " whatIsThis " is of type unit and has been bound to the value
()
So, going back to the signature of " printInt ", we can now understand it:
val printInt : int -> unit
This signature says: printInt has a domain of int which it maps onto nothing that we
care about.
Parameterless functions
Now that we understand unit, can we predict its appearance in other contexts? For example,
let's try to create a reusable "hello world" function. Since there is no input and no output, we
would expect it to have a signature unit -> unit . Let's see:
let printHello = printf "hello world" // print to console
288
hello world
val printHello : unit = ()
Not quite what we expected. "Hello world" is printed immediately and the result is not a
function, but a simple value of type unit. As we saw earlier, we can tell that this is a simple
value because it has a signature of the form:
val aName: type = constant
So in this case, we see that printHello is actually a simple value with the value () . It's
not a function that we can call again.
Why the difference between printInt and printHello ? In the printInt case, the value
could not be determined until we knew the value of the x parameter, so the definition was of
a function. In the printHello case, there were no parameters, so the right hand side could
be determined immediately. Which it was, returning the () value, with the side effect of
printing to the console.
We can create a true reusable function that is parameterless by forcing the definition to have
a unit argument, like this:
let printHelloFn () = printf "hello world" // print to console
and to call it, we have to pass the () value as a parameter, like so:
printHelloFn ()
289
To help in these situations, there is a special function ignore that takes anything and
returns the unit type. The correct version of this code would be:
do (1+1 |> ignore) // ok
let something =
2+2 |> ignore // ok
"hello"
Generic types
In many cases, the type of the function parameter can be any type, so we need a way to
indicate this. F# uses the .NET generic type system for this situation.
For example, the following function converts the parameter to a string and appends some
text:
let onAStick x = x.ToString() + " on a stick"
It doesn't matter what type the parameter is, as all objects understand ToString() .
The signature is:
val onAStick : 'a -> string
What is this type called 'a ? That is F#'s way of indicating a generic type that is not known
at compile time. The apostrophe in front of the "a" means that the type is generic. The
signature for the C# equivalent of this would be:
string onAStick<a>();
//or more idiomatically
string OnAStick<TObject>(); // F#'s use of 'a is like
// C#'s "TObject" convention
Note that the F# function is still strongly typed with a generic type. It does not take a
parameter of type Object . This strong typing is desirable so that when functions are
composed together, type safety is still maintained.
Here's the same function being used with an int, a float and a string
290
onAStick 22
onAStick 3.14159
onAStick "hello"
If there are two generic parameters, the compiler will give them different names: 'a for the
first generic, 'b for the second generic, and so on. Here's an example:
let concatString x y = x.ToString() + y.ToString()
The type signature for this has two generics: 'a and 'b :
val concatString : 'a -> 'b -> string
On the other hand, the compiler will recognize when only one generic type is required. In the
following example, the x and y parameters must be of the same type:
let isEqual x y = (x=y)
So the function signature has the same generic type for both of them:
val isEqual : 'a -> 'a -> bool
Generic parameters are also very important when it comes to lists and more abstract
structures, and we will be seeing them a lot in upcoming examples.
Other types
The types discussed so far are just the basic types. These types can be combined in various
ways to make much more complex types. A full discussion of these types will have to wait for
another series, but meanwhile, here is a brief introduction to them so that you can recognize
them in function signatures.
The "tuple" types. These are pairs, triples, etc., of other types. For example ("hello",
1) is a tuple made from a string and an int. The comma is the distinguishing
characteristic of a tuple -- if you see a comma in F#, it is almost certainly part of a tuple!
In function signatures, tuples are written as the "multiplication" of the two types involved. So
in this case, the tuple would have type:
291
The collection types. The most common of these are lists, sequences, and arrays.
Lists and arrays are fixed size, while sequences are potentially infinite (behind the
scenes, sequences are the same as IEnumerable ). In function signatures, they have
their own keywords: " list ", " seq ", and " [] " for arrays.
int list // List type e.g. [1;2;3]
string list // List type e.g. ["a";"b";"c"]
seq<int> // Seq type e.g. seq{1..10}
int [] // Array type e.g. [|1;2;3|]
The option type. This is a simple wrapper for objects that might be missing. There are
two cases: Some and None . In function signatures, they have their own " option "
keyword:
int option // Some(1)
The discriminated union type. These are built from a set of choices of other types. We
saw some examples of this in the "why use F#?" series. In function signatures, they are
referred to by the name of the type, so there is no special keyword.
The record type. These are like structures or database rows, a list of named slots. We
saw some examples of this in the "why use F#?" series as well. In function signatures,
they are referred to by the name of the type, so again there is no special keyword.
292
293
Currying
Currying
After that little digression on basic types, we can turn back to functions again, and in
particular the puzzle we mentioned earlier: if a mathematical function can only have one
parameter, then how is it possible that an F# function can have more than one?
The answer is quite simple: a function with multiple parameters is rewritten as a series of
new functions, each with only one parameter. And this is done automatically by the compiler
for you. It is called "currying", after Haskell Curry, a mathematician who was an important
influence on the development of functional programming.
To see how this works in practice, let's use a very basic example that prints two numbers:
//normal version
let printTwoParameters x y =
printfn "x=%i y=%i" x y
294
Currying
If you evaluate it with one argument, you don't get an error, you get back a function.
So what you are really doing when you call printTwoParameters with two arguments is:
You call printTwoParameters with the first argument (x)
printTwoParameters returns a new function that has "x" baked into it.
You then call the new function with the second argument (y)
Here is an example of the step by step version, and then the normal version again.
// step by step version
let x = 6
let y = 99
let intermediateFn = printTwoParameters x // return fn with
// x "baked in"
let result = intermediateFn y
// inline version of above
let result = (printTwoParameters x) y
// normal version
let result = printTwoParameters x y
295
Currying
//normal version
let addTwoParameters x y =
x + y
//explicitly curried version
let addTwoParameters x = // only one parameter!
let subFunction y =
x + y // new function with one param
subFunction // return the subfunction
// now use it step by step
let x = 6
let y = 99
let intermediateFn = addTwoParameters x // return fn with
// x "baked in"
let result = intermediateFn y
// normal version
let result = addTwoParameters x y
Again, the "two parameter function" is actually a one parameter function that returns an
intermediate function.
But wait a minute -- what about the " + " operation itself? It's a binary operation that must
take two parameters, surely? No, it is curried like every other function. There is a function
called " + " that takes one parameter and returns a new intermediate function, exactly like
addTwoParameters above.
When we write the statement x+y , the compiler reorders the code to remove the infix and
turns it into (+) x y , which is the function named + called with two parameters. Note that
the function named "+" needs to have parentheses around it to indicate that it is being used
as a normal function name rather than as an infix operator.
Finally, the two parameter function named + is treated as any other two parameter function
would be.
// using plus as a single value function
let x = 6
let y = 99
let intermediateFn = (+) x // return add with x baked in
let result = intermediateFn y
// using plus as a function with two parameters
let result = (+) x y
// normal version of plus as infix operator
let result = x + y
296
Currying
And yes, this works for all other operators and built in functions like printf.
// normal version of multiply
let result = 3 * 5
// multiply as a one parameter function
let intermediateFn = (*) 3 // return multiply with "3" baked in
let result = intermediateFn 5
// normal version of printfn
let result = printfn "x=%i y=%i" 3 5
// printfn as a one parameter function
let intermediateFn = printfn "x=%i y=%i" 3 // "3" is baked in
let result = intermediateFn 5
If you evaluate the explicitly curried implementation, you will see the parentheses in the
signature, as written above, but if you evaluate the normal implementation, which is implicitly
curried, the parentheses are left off, like so:
val printTwoParameters : int -> int -> unit
The parentheses are optional. If you are trying to make sense of function signatures it might
be helpful to add them back in mentally.
At this point you might be wondering, what is the difference between a function that returns
an intermediate function and a regular two parameter function?
Here's a one parameter function that returns a function:
297
Currying
The signatures are slightly different, but in practical terms, there is no difference*, only that
the second function is automatically curried for you.
298
Currying
A function signature can tell you how many parameters the function takes: just count the
number of arrows outside of parentheses. If the function takes or returns other function
parameters, there will be other arrows in parentheses, but these can be ignored. Here are
some examples:
int->int->int // two int parameters and returns an int
string->bool->int // first param is a string, second is a bool,
// returns an int
int->string->bool->unit // three params (int,string,bool)
// returns nothing (unit)
(int->string)->int // has only one parameter, a function
// value (from int to string)
// and returns a int
(int->string)->(int->bool) // takes a function (int to string)
// returns a function (int to bool)
What would you expect to happen when we call it as shown below? Will it print "hello" to the
console? Try to guess before evaluating it, and here's a hint: be sure to take a look at the
function signature.
299
Currying
// call it
printHello
It will not be called as expected. The original function expects a unit argument that was not
supplied, so you are getting a partially applied function (in this case with no arguments).
How about this? Will it compile?
let addXY x y =
printfn "x=%i y=%i" x
x + y
If you evaluate it, you will see that the compiler complains about the printfn line.
printfn "x=%i y=%i" x
//^^^^^^^^^^^^^^^^^^^^^
//warning FS0193: This expression is a function value, i.e. is missing
//arguments. Its type is ^a -> unit.
If you didn't understand currying, this message would be very cryptic! All expressions that
are evaluated standalone like this (i.e. not used as a return value or bound to something with
"let") must evaluate to the unit value. And in this case, it is does not evaluate to the unit
value, but instead evaluates to a function. This is a long winded way of saying that printfn
is missing an argument.
A common case of errors like this is when interfacing with the .NET library. For example, the
ReadLine method of a TextReader must take a unit parameter. It is often easy to forget this
and leave off the parens, in which case you do not get a compiler error immediately, but only
when you try to treat the result as a string.
let reader = new System.IO.StringReader("hello");
let line1 = reader.ReadLine // wrong but compiler doesn't
// complain
printfn "The line is %s" line1 //compiler error here!
// ==> error FS0001: This expression was expected to have
// type string but here has type unit -> string
let line2 = reader.ReadLine() //correct
printfn "The line is %s" line2 //no compiler error
In the code above, line1 is just a pointer or delegate to the Readline method, not the
string that we expected. The use of () in reader.ReadLine() actually executes the
function.
300
Currying
For example, in the last case, the compiler is saying that it expects the format argument to
have three parameters (the signature 'a -> 'b -> 'c -> 'd has three parameters) but it is
given only two (the signature 'a -> 'b -> unit has two parameters).
In cases not using printf , passing too many parameters will often mean that you end up
with a simple value that you then try to pass a parameter to. The compiler will complain that
the simple value is not a function.
let add1 x = x + 1
let x = add1 2 3
// ==> error FS0003: This value is not a function
// and cannot be applied
If you break the call into a series of explicit intermediate functions, as we did earlier, you can
see exactly what is going wrong.
let add1 x = x + 1
let intermediateFn = add1 2 //returns a simple value
let x = intermediateFn 3 //intermediateFn is not a function!
// ==> error FS0003: This value is not a function
// and cannot be applied
301
Partial application
Partial application
In the previous post on currying, we looked at breaking multiple parameter functions into
smaller one parameter functions. It is the mathematically correct way of doing it, but that is
not the only reason it is done -- it also leads to a very powerful technique called partial
function application. This is a very widely used style in functional programming, and it is
important to understand it.
The idea of partial application is that if you fix the first N parameters of the function, you get
a function of the remaining parameters. From the discussion on currying, you can probably
see how this comes about naturally.
Here are some simple examples that demonstrate this:
// create an "adder" by partial application of add
let add42 = (+) 42 // partial application
add42 1
add42 3
// create a new list by applying the add42 function
// to each element
[1;2;3] |> List.map add42
// create a "tester" by partial application of "less than"
let twoIsLessThan = (<) 2 // partial application
twoIsLessThan 1
twoIsLessThan 3
// filter each element with the twoIsLessThan function
[1;2;3] |> List.filter twoIsLessThan
// create a "printer" by partial application of printfn
let printer = printfn "printing param=%i"
// loop over each element and call the printer function
[1;2;3] |> List.iter printer
In each case, we create a partially applied function that we can then reuse in multiple
contexts.
The partial application can just as easily involve fixing function parameters, of course. Here
are some examples:
302
Partial application
The following more complex example shows how the same approach can be used to create
"plug in" behavior that is transparent.
We create a function that adds two numbers, but in addition takes a logging function
that will log the two numbers and the result.
The logging function has two parameters: (string) "name" and (generic) "value", so it
has signature string->'a->unit .
We then create various implementations of the logging function, such as a console
logger or a popup logger.
And finally we partially apply the main function to create new functions that have a
particular logger baked into them.
303
Partial application
These functions with the logger baked in can in turn be used like any other function. For
example, we can create a partial application to add 42, and then pass that into a list function,
just like we did for the simple " add42 " function.
// create a another adder with 42 baked in
let add42WithConsoleLogger = addWithConsoleLogger 42
[1;2;3] |> List.map add42WithConsoleLogger
[1;2;3] |> List.map add42 //compare without logger
These partially applied functions are a very useful tool. We can create library functions which
are flexible (but complicated), yet make it easy to create reusable defaults so that callers
don't have to be exposed to the complexity all the time.
304
Partial application
You can see that the order of the parameters can make a big difference in the ease of use
for partial application. For example, most of the functions in the List library such as
List.map and List.filter have a similar form, namely:
The list is always the last parameter. Here are some examples of the full form:
List.map (fun i -> i+1) [0;1;2;3]
List.filter (fun i -> i>1) [0;1;2;3]
List.sortBy (fun i -> -i ) [0;1;2;3]
If the library functions were written with the parameters in a different order, it would be much
more inconvenient to use them with partial application.
As you write your own multi-parameter functions, you might wonder what the best parameter
order is. As with all design questions, there is no "right" answer to this question, but here are
some commonly accepted guidelines:
1. Put earlier: parameters more likely to be static
2. Put last: the data structure or collection (or most varying argument)
3. For well-known operations such as "subtract", put in the expected order
Guideline 1 is straightforward. The parameters that are most likely to be "fixed" with partial
application should be first. We saw this with the logger example earlier.
Guideline 2 makes it easier to pipe a structure or collection from function to function. We
have seen this many times already with list functions.
305
Partial application
Similarly, partially applied list functions are easy to compose, because the list parameter
itself can be easily elided:
let compositeOp = List.map (fun i -> i+1)
>> List.filter (fun i -> i>5)
let result = compositeOp [1..10]
Once the string becomes the last parameter, we can then use them with pipes in the
expected way:
let result =
"hello"
|> replace "h" "j"
|> startsWith "j"
["the"; "quick"; "brown"; "fox"]
|> List.filter (startsWith "f")
306
Partial application
All it does is allow you to put the function argument in front of the function rather than after.
That's all.
let doSomething x y z = x+y+z
doSomething 1 2 3 // all parameters after function
If the function has multiple parameters, then it appears that the input is the final parameter.
Actually what is happening is that the function is partially applied, returning a function that
has a single parameter: the input
Here's the same example rewritten to use partial application
let doSomething x y =
let intermediateFn z = x+y+z
intermediateFn // return intermediateFn
let doSomethingPartial = doSomething 1 2
doSomethingPartial 3 // only one parameter after function now
3 |> doSomethingPartial // same as above - last parameter piped in
As you have already seen, the pipe operator is extremely common in F#, and used all the
time to preserve a natural flow. Here are some more usages that you might see:
"12" |> int // parses string "12" to an int
1 |> (+) 2 |> (*) 3 // chain of arithmetic
307
Partial application
let (<|) f x = f x
It seems that this function doesn't really do anything different from normal, so why does it
exist?
The reason is that, when used in the infix style as a binary operator, it reduces the need for
parentheses and can make the code cleaner.
printf "%i" 1+2 // error
printf "%i" (1+2) // using parens
printf "%i" <| 1+2 // using reverse pipe
You can also use piping in both directions at once to get a pseudo infix notation.
let add x y = x + y
(1+2) add (3+4) // error
1+2 |> add <| 3+4 // pseudo infix
308
Does it mean apply the function y to the argument z, and then take the result and use it as
an argument for x? In which case it is the same as:
let F x y z = x (y z)
Or does it mean apply the function x to the argument y, and then take the resulting function
and evaluate it with the argument z? In which case it is the same as:
let F x y z = (x y) z
The answer is the latter. Function application is left associative. That is, evaluating x y z is
the same as evaluating (x y) z . And evaluating w x y z is the same as evaluating ((w
x) y) z . This should not be a surprise. We have already seen that this is how partial
application works. If you think of x as a two parameter function, then (x y) z is the result of
partial application of the first parameter, followed by passing the z argument to the
intermediate function.
If you do want to do right association, you can use explicit parentheses, or you can use a
pipe. The following three forms are equivalent.
let F x y z = x (y z)
let F x y z = y z |> x // using forward pipe
let F x y z = x <| y z // using backward pipe
As an exercise, work out the signatures for these functions without actually evaluating them!
Function composition
309
We've mentioned function composition a number of times in passing now, but what does it
actually mean? It can seem quite intimidating at first, but it is actually quite simple.
Say that you have a function "f" that maps from type "T1" to type "T2", and say that you also
have a function "g" that maps from type "T2" to type "T3". Then you can connect the output
of "f" to the input of "g", creating a new function that maps from type "T1" to type "T3".
Here's an example
let f (x:int) = float x * 3.0 // f is int->float
let g (x:float) = x > 4.0 // g is float->bool
We can create a new function h that takes the output of "f" and uses it as the input for "g".
let h (x:int) =
let y = f(x)
g(y) // return output of g
So far, so straightforward. What is interesting is that we can define a new function called
"compose" that, given functions "f" and "g", combines them in this way without even knowing
their signatures.
let compose f g x = g ( f(x) )
If you evaluate this, you will see that the compiler has correctly deduced that if " f " is a
function from generic type 'a to generic type 'b , then " g " is constrained to have generic
type 'b as an input. And the overall signature is:
310
val compose : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c
(Note that this generic composition operation is only possible because every function has
one input and one output. This approach would not be possible in a non-functional
language.)
As we have seen, the actual definition of compose uses the " >> " symbol.
let (>>) f g x = g ( f(x) )
Given this definition, we can now use composition to build new functions from existing ones.
let add1 x = x + 1
let times2 x = x * 2
let add1Times2 x = (>>) add1 times2 x
//test
add1Times2 3
This explicit style is quite cluttered. We can do a few things to make it easier to use and
understand.
First, we can leave off the x parameter so that the composition operator returns a partial
application.
let add1Times2 = (>>) add1 times2
And now we have a binary operation, so we can put the operator in the middle.
let add1Times2 = add1 >> times2
And there you have it. Using the composition operator allows code to be cleaner and more
straightforward.
let add1 x = x + 1
let times2 x = x * 2
//old style
let add1Times2 x = times2(add1 x)
//new style
let add1Times2 = add1 >> times2
311
As long as the inputs and outputs match, the functions involved can use any kind of value.
For example, consider the following, which performs a function twice:
let twice f = f >> f //signature is ('a -> 'a) -> ('a -> 'a)
Note that the compiler has deduced that the function f must use the same type for both input
and output.
Now consider a function like " + ". As we have seen earlier, the input is an int , but the
output is actually a partially applied function (int->int) . The output of " + " can thus be
used as the input of " twice ". So we can write something like:
let add1 = (+) 1 // signature is (int -> int)
let add1Twice = twice add1 // signature is also (int -> int)
//test
add1Twice 9
because the input to "*" must be an int value, not an int->int function (which is what the
output of addition is).
312
But if we tweak it so that the first function has an output of just int instead, then it does
work:
let add1ThenMultiply = (+) 1 >> (*)
// (+) 1 has signature (int -> int) and output is an 'int'
//test
add1ThenMultiply 2 7
Composition can also be done backwards using the " << " operator, if needed.
let times2Add1 = add 1 << times 2
times2Add1 3
Reverse composition is mainly used to make code more English-like. For example, here is a
simple example:
let myList = []
myList |> List.isEmpty |> not // straight pipeline
myList |> (not << List.isEmpty) // using reverse composition
All it does is allow you to put the function argument in front of the function rather than after.
That's all. If the function has multiple parameters, then the input would be the final
parameter. Here's the example used earlier.
let doSomething x y z = x+y+z
doSomething 1 2 3 // all parameters after function
3 |> doSomething 1 2 // last parameter piped in
Composition is not the same thing and cannot be a substitute for a pipe. In the following
case the number 3 is not even a function, so its "output" cannot be fed into doSomething :
313
The compiler is complaining that "3" should be some sort of function 'a->'b .
Compare this with the definition of composition, which takes 3 arguments, where the first two
must be functions.
let (>>) f g x = g ( f(x) )
let add n x = x + n
let times n x = x * n
let add1Times2 = add 1 >> times 2
Trying to use a pipe instead doesn't work. In the following example, " add 1 " is a (partial)
function of type int->int , and cannot be used as the second parameter of " times 2 ".
let add1Times2 = add 1 |> times 2 // not allowed
// x |> f is the same as f(x) so rewriting it we have:
let add1Times2 = times 2 (add 1) // add1 should be an int
// error FS0001: Type mismatch. 'int -> int' does not match 'int'
The compiler is complaining that " times 2 " should take an int->int parameter, that is, be
of type (int->int)->'a .
314
Defining functions
Defining functions
We have seen how to create typical functions using the "let" syntax, below:
let add x y = x + y
In this section, we'll look at some other ways of creating functions, and tips for defining
functions.
Lambdas are often used when you have a short expression and you don't want to define a
function just for that expression. This is particularly common with list operations, as we have
seen already.
// with separately defined function
let add1 i = i + 1
[1..10] |> List.map add1
// inlined without separately defined function
[1..10] |> List.map (fun i -> i + 1)
315
Defining functions
The lambda version is slightly longer, but makes it clear that an intermediate function is
being returned.
You can nest lambdas as well. Here is yet another definition of adderGenerator , this time
using lambdas only.
let adderGenerator = fun x -> (fun y -> x + y)
Can you see that all three of the following definitions are the same thing?
let adderGenerator1 x y = x + y
let adderGenerator2 x = fun y -> x + y
let adderGenerator3 = fun x -> (fun y -> x + y)
If you can't see it, then do reread the post on currying. This is important stuff to understand!
316
Defining functions
This kind of matching can only occur when the matching is always possible. For example,
you cannot match on union types or lists this way, because some cases might not be
matched.
let f3 (x::xs) = // use pattern matching on a list
printfn "first element is=%A" x
317
Defining functions
The first definition, " addTwoParams ", takes two parameters, separated with spaces.
The second definition, " addTuple ", takes a single parameter. It then binds "x" and "y" to
the inside of the tuple and does the addition.
The third definition, " addConfusingTuple ", takes a single parameter just like " addTuple ",
but the tricky thing is that the tuple is unpacked and bound as part of the parameter
definition using pattern matching. Behind the scenes, it is exactly the same as
" addTuple ".
Let's look at the signatures (it is always a good idea to look at the signatures if you are
unsure)
val addTwoParams : int -> int -> int // two params
val addTuple : int * int -> int // tuple->int
val addConfusingTuple : int * int -> int // tuple->int
318
Defining functions
addTuple (1,2) // ok
addConfusingTuple (1,2) // ok
let x = (1,2)
addTuple x // ok
let y = 1,2 // it's the comma you need,
// not the parentheses!
addTuple y // ok
addConfusingTuple y // ok
Conversely, if you attempt to pass multiple arguments to a function expecting a tuple, you
will also get an obscure error.
addConfusingTuple 1 2 // error trying to pass two args
// => error FS0003: This value is not a function and
// cannot be applied
In this case, the compiler thinks that, since you are passing two arguments,
addConfusingTuple must be curryable. So then " addConfusingTuple 1 " would be a partial
application that returns another intermediate function. Trying to apply that intermediate
function with "2" gives an error, because there is no intermediate function! We saw this exact
same error in the post on currying, when we discussed the issues that can occur from
having too many parameters.
Note that the function signature is different from a true three parameter function. There is
only one arrow, so only one parameter, and the stars indicate that this is a tuple of
(int*int*int) .
319
Defining functions
When the tuples are meaningful in themselves. For example, if we are working with
three dimensional coordinates, a three-tuple might well be more convenient than three
separate dimensions.
Tuples are occasionally used to bundle data together in a single structure that should be
kept together. For example, the TryParse functions in .NET library return the result and
a Boolean as a tuple. But if you have a lot of data that is kept together as a bundle, then
you will probably want to define a record or class type to store it.
The reason is that .NET library functions are not curried and cannot be partially applied. All
the parameters must always be passed in, and using a tuple-like approach is the obvious
way to do this.
But do note that although these calls look like tuples, they are actually a special case. Real
tuples cannot be used, so the following code is invalid:
let tuple = ("a","b")
System.String.Compare tuple // error
System.String.Compare "a","b" // error
If you do want to partially apply .NET library functions, it is normally trivial to write wrapper
functions for them, as we have seen earlier, and as shown below:
320
Defining functions
321
Defining functions
Finally, do be sure to order the parameters appropriately to assist with partial application
(see the guidelines in the earlier post). For example, in the last function above, why did I put
the myCredentials parameter ahead of the aName parameter?
Parameter-less functions
Sometimes we may want functions that don't take any parameters at all. For example, we
may want a "hello world" function that we can call repeatedly. As we saw in a previous
section, the naive definition will not work.
let sayHello = printfn "Hello World!" // not what we want
And then the function must always be called with a unit argument:
// call it
sayHello()
This is particularly common with the .NET libraries. Some examples are:
322
Defining functions
Console.ReadLine()
System.Environment.GetCommandLineArgs()
System.IO.Directory.GetCurrentDirectory()
You must use parentheses around the symbols when defining them.
Note that for custom operators that begin with * , a space is required; otherwise the (* is
interpreted as the start of a comment:
let ( *+* ) x y = x + y + 1
Once defined, the new function can be used in the normal way, again with parens around
the symbols:
let result = (.*%) 2 3
If the function has exactly two parameters, you can use it as an infix operator without
parentheses.
let result = 2 .*% 3
You can also define prefix operators that start with ! or ~ (with some restrictions -- see
the F# documentation on operator overloading)
let (~%%) (s:string) = s.ToCharArray()
//use
let result = %% "hello"
323
Defining functions
In F# it is quite common to create your own operators, and many libraries will export
operators with names such as >=> and <*> .
Point-free style
We have already seen many examples of leaving off the last parameter of functions to
reduce clutter. This style is referred to as point-free style or tacit programming.
Here are some examples:
let add x y = x + y // explicit
let add x = (+) x // point free
let add1Times2 x = (x + 1) * 2 // explicit
let add1Times2 = (+) 1 >> (*) 2 // point free
let sum list = List.reduce (fun sum e -> sum+e) list // explicit
let sum = List.reduce (+) // point free
Combinators
The word "combinator" is used to describe functions whose result depends only on their
parameters. That means there is no dependency on the outside world, and in particular no
other functions or global value can be accessed at all.
324
Defining functions
In practice, this means that a combinator function is limited to combining its parameters in
various ways.
We have already seen some combinators already: the "pipe" operator and the "compose"
operator. If you look at their definitions, it is clear that all they do is reorder the parameters in
various ways
let (|>) x f = f x // forward pipe
let (<|) f x = f x // reverse pipe
let (>>) f g x = g (f x) // forward composition
let (<<) g f x = g (f x) // reverse composition
On the other hand, a function like "printf", although primitive, is not a combinator, because it
has a dependency on the outside world (I/O).
Combinator birds
Combinators are the basis of a whole branch of logic (naturally called "combinatory logic")
that was invented many years before computers and programming languages. Combinatory
logic has had a very large influence on functional programming.
To read more about combinators and combinatory logic, I recommend the book "To Mock a
Mockingbird" by Raymond Smullyan. In it, he describes many other combinators and
whimsically gives them names of birds. Here are some examples of some standard
combinators and their bird names:
let I x = x // identity function, or the Idiot bird
let K x y = x // the Kestrel
let M x = x >> x // the Mockingbird
let T x y = y x // the Thrush (this looks familiar!)
let Q x y z = y (x z) // the Queer bird (also familiar!)
let S x y z = x z (y z) // The Starling
// and the infamous...
let rec Y f x = f (Y f) x // Y-combinator, or Sage bird
The letter names are quite standard, so if you refer to "the K combinator", everyone will be
familiar with that terminology.
It turns out that many common programming patterns can be represented using these
standard combinators. For example, the Kestrel is a common pattern in fluent interfaces
where you do something but then return the original object. The Thrush is the pipe
operation, the Queer bird is forward composition, and the Y-combinator is famously used to
make functions recursive.
325
Defining functions
Indeed, there is a well-known theorem that states that any computable function whatsoever
can be built from just two basic combinators, the Kestrel and the Starling.
Combinator libraries
A combinator library is a code library that exports a set of combinator functions that are
designed to work together. The user of the library can then easily combine simple functions
together to make bigger and more complex functions, like building with Lego.
A well designed combinator library allows you to focus on the high level operations, and
push the low level "noise" to the background. We've already seen some examples of this
power in the examples in "why use F#" series, and the List module is full of them -- the
" fold " and " map " functions are also combinators, if you think about it.
Another advantage of combinators is that they are the safest type of function. As they have
no dependency on the outside world they cannot change if the global environment changes.
A function that reads a global value or uses a library function can break or alter between
calls if the context is different. This can never happen with combinators.
In F#, combinator libraries are available for parsing (the FParsec library), HTML
construction, testing frameworks, and more. We'll discuss and use combinators further in
later series.
Recursive functions
Often, a function will need to refer to itself in its body. The classic example is the Fibonacci
function:
let fib i =
match i with
| 1 -> 1
| 2 -> 1
| n -> fib(n-1) + fib(n-2)
You have to tell the compiler that this is a recursive function using the rec keyword.
326
Defining functions
Recursive functions and data structures are extremely common in functional programming,
and I hope to devote a whole later series to this topic.
327
Function signatures
Function signatures
It may not be obvious, but F# actually has two syntaxes - one for normal (value)
expressions, and one for type definitions. For example:
[1;2;3] // a normal expression
int list // a type expression
Some 1 // a normal expression
int option // a type expression
(1,"a") // a normal expression
int * string // a type expression
Type expressions have a special syntax that is different from the syntax used in normal
expressions. You have already seen many examples of this when you use the interactive
session, because the type of each expression has been printed along with its evaluation.
As you know, F# uses type inference to deduce types, so you don't often need to explicitly
specify types in your code, especially for functions. But in order to work effectively in F#, you
do need to understand the type syntax, so that you can build your own types, debug type
errors, and understand function signatures. In this post, we'll focus on its use in function
signatures.
Here are some example function signatures using the type syntax:
// expression syntax // type syntax
let add1 x = x + 1 // int -> int
let add x y = x + y // int -> int -> int
let print x = printf "%A" x // 'a -> unit
System.Console.ReadLine // unit -> string
List.sum // 'a list -> 'a
List.filter // ('a -> bool) -> 'a list -> 'a list
List.map // ('a -> 'b) -> 'a list -> 'b list
328
Function signatures
// function signature 1
int -> int -> int
This function takes two int parameters and returns another, so presumably it is some sort
of mathematical function such as addition, subtraction, multiplication, or exponentiation.
// function signature 2
int -> unit
This function takes an int and returns a unit , which means that the function is doing
something important as a side-effect. Since there is no useful return value, the side effect is
probably something to do with writing to IO, such as logging, writing to a file or database, or
something similar.
// function signature 3
unit -> string
This function takes no input but returns a string , which means that the function is
conjuring up a string out of thin air! Since there is no explicit input, the function probably has
something to do with reading (from a file say) or generating (a random string, say).
// function signature 4
int -> (unit -> string)
This function takes an int input and returns a function that when called, returns strings.
Again, the function probably has something to do with reading or generating. The input
probably initializes the returned function somehow. For example, the input could be a file
handle, and the returned function something like readline() . Or the input could be a seed
for a random string generator. We can't tell exactly, but we can make some educated
guesses.
// function signature 5
'a list -> 'a
This function takes a list of some type, but returns only one of that type, which means that
the function is merging or choosing elements from the list. Examples of functions with this
signature are List.sum , List.max , List.head and so on.
// function signature 6
('a -> bool) -> 'a list -> 'a list
329
Function signatures
This function takes two parameters: the first is a function that maps something to a bool (a
predicate), and the second is a list. The return value is a list of the same type. Predicates
are used to determine whether a value meets some sort of criteria, so it looks like the
function is choosing elements from the list based on whether the predicate is true or not and
then returning a subset of the original list. A typical function with this signature is
List.filter .
// function signature 7
('a -> 'b) -> 'a list -> 'b list
This function takes two parameters: the first maps type 'a to type 'b , and the second is a
list of 'a . The return value is a list of a different type 'b . A reasonable guess is that the
function takes each of the 'a s in the list, maps them to a 'b using the function passed in
as the first parameter, and returns the new list of 'b s. And indeed, the prototypical function
with this signature is List.map .
Now go to the MSDN documentation for the F# List module, and scan down the list of
functions, looking for something that matches. As it happens, there is only one function with
that signature:
append : 'T list -> 'T list -> 'T list
330
Function signatures
You can then use these types to constrain function values and parameters.
For example, the second definition below will fail because of type constraints. If you remove
the type constraint (as in the third definition) there will not be any problem.
let a:AdderGenerator = fun x -> (fun y -> x + y)
let b:AdderGenerator = fun (x:float) -> (fun y -> x + y)
let c = fun (x:float) -> (fun y -> x + y)
331
Organizing functions
Organizing functions
Now that you know how to define functions, how can you organize them?
In F#, there are three options:
functions can be nested inside other functions.
at an application level, the top level functions are grouped into "modules".
alternatively, you can also use the object-oriented approach and attach functions to
types as methods.
We'll look at the first two options in this post, and the third in the next post.
Nested Functions
In F#, you can define functions inside other functions. This is a great way to encapsulate
"helper" functions that are needed for the main function but shouldn't be exposed outside.
In the example below add is nested inside addThreeNumbers :
let addThreeNumbers x y z =
//create a nested helper function
let add n =
fun x -> x + n
// use the helper function
x |> add y |> add z
// test
addThreeNumbers 2 3 4
A nested function can access its parent function parameters directly, because they are in
scope. So, in the example below, the printError nested function does not need to have
any parameters of its own -- it can access the n and max values directly.
332
Organizing functions
A very common pattern is that the main function defines a nested recursive helper function,
and then calls it with the appropriate initial values. The code below is an example of this:
let sumNumbersUpTo max =
// recursive helper function with accumulator
let rec recursiveSum n sumSoFar =
match n with
| 0 -> sumSoFar
| _ -> recursiveSum (n-1) (n+sumSoFar)
// call helper function with initial values
recursiveSum max 0
// test
sumNumbersUpTo 10
When nesting functions, do try to avoid very deeply nested functions, especially if the nested
functions directly access the variables in their parent scopes rather than having parameters
passed to them. A badly nested function will be just as confusing as the worst kind of deeply
nested imperative branching.
Here's how not to do it:
333
Organizing functions
Modules
A module is just a set of functions that are grouped together, typically because they work on
the same data type or types.
A module definition looks very like a function definition. It starts with the module keyword,
then an = sign, and then the contents of the module are listed. The contents of the module
must be indented, just as expressions in a function definition must be indented.
Here's a module that contains two functions:
module MathStuff =
let add x y = x + y
let subtract x y = x - y
Now if you try this in Visual Studio, and you hover over the add function, you will see that
the full name of the add function is actually MathStuff.add , just as if MathStuff was a
class and add was a method.
Actually, that's exactly what is going on. Behind the scenes, the F# compiler creates a static
class with static methods. So the C# equivalent would be:
334
Organizing functions
If you realize that modules are just static classes, and that functions are static methods, then
you will already have a head-start on understanding how modules work in F#, as most of the
rules that apply to static classes also apply to modules.
And, just as in C# every standalone function must be part of a class, in F# every standalone
function must be part of a module.
You can also import all the functions in another module with the open directive, after which
you can use the short name, rather than having to specify the qualified name.
module OtherStuff =
open MathStuff // make all functions accessible
let add1 x = add x 1
The rules for using qualified names are exactly as you would expect. That is, you can always
use a fully qualified name to access a function, and you can use relative names or
unqualified names based on what other modules are in scope.
335
Organizing functions
Nested modules
Just like static classes, modules can contain child modules nested within them, as shown
below:
module MathStuff =
let add x y = x + y
let subtract x y = x - y
// nested module
module FloatLib =
let add x y :float = x + y
let subtract x y :float = x - y
And other modules can reference functions in the nested modules using either a full name or
a relative name as appropriate:
module OtherStuff =
open MathStuff
let add1 x = add x 1
// fully qualified
let add1Float x = MathStuff.FloatLib.add x 1.0
//with a relative path
let sub1Float x = FloatLib.subtract x 1.0
336
Organizing functions
For .FSX script files, the module declaration is not needed, in which case the module name
is automatically set to the filename of the script.
Here is an example of MathStuff declared as a top level module:
// top level module
module MathStuff
let add x y = x + y
let subtract x y = x - y
// nested module
module FloatLib =
let add x y :float = x + y
let subtract x y :float = x - y
Note the lack of indentation for the top level code (the contents of module MathStuff ), but
that the content of a nested module like FloatLib does still need to be indented.
By the way, if you are playing with these examples in the interactive window, you might
want to right-click and do "Reset Session" every so often, so that the code is fresh and
337
Organizing functions
Shadowing
Here's our example module again. Notice that MathStuff has an add function and
FloatLib also has an add function.
module MathStuff =
let add x y = x + y
let subtract x y = x - y
// nested module
module FloatLib =
let add x y :float = x + y
let subtract x y :float = x - y
Now what happens if I bring both of them into scope, and then use add ?
open MathStuff
open MathStuff.FloatLib
let result = add 1 2 // Compiler error: This expression was expected to
// have type float but here has type int
What happened was that the MathStuff.FloatLib module has masked or overridden the
original MathStuff module, which has been "shadowed" by FloatLib .
As a result you now get a FS0001 compiler error because the first parameter 1 is expected
to be a float. You would have to change 1 to 1.0 to fix this.
Unfortunately, this is invisible and easy to overlook. Sometimes you can do cool tricks with
this, almost like subclassing, but more often, it can be annoying if you have functions with
the same name (such as the very common map ).
If you don't want this to happen, there is a way to stop it by using the
RequireQualifiedAccess attribute. Here's the same example where both modules are
338
Organizing functions
[<RequireQualifiedAccess>]
module MathStuff =
let add x y = x + y
let subtract x y = x - y
// nested module
[<RequireQualifiedAccess>]
module FloatLib =
let add x y :float = x + y
let subtract x y :float = x - y
But we can still access the functions (without any ambiguity) via their qualified name:
let result = MathStuff.add 1 2
let result = MathStuff.FloatLib.add 1.0 2.0
Access Control
F# supports the use of standard .NET access control keywords such as public , private ,
and internal . The MSDN documentation has the complete details.
These access specifiers can be put on the top-level ("let bound") functions, values,
types and other declarations in a module. They can also be specified for the modules
themselves (you might want a private nested module, for example).
Everything is public by default (with a few exceptions) so you will need to use private
or internal if you want to protect them.
These access specifiers are just one way of doing access control in F#. Another completely
different way is to use module "signature" files, which are a bit like C header files. They
describe the content of the module in an abstract way. Signatures are very useful for doing
serious encapsulation, but that discussion will have to wait for the planned series on
encapsulation and capability based security.
Namespaces
339
Organizing functions
Namespaces in F# are similar to namespaces in C#. They can be used to organize modules
and types to avoid name collisions.
A namespace is declared with a namespace keyword, as shown below.
namespace Utilities
module MathStuff =
// functions
let add x y = x + y
let subtract x y = x - y
Because of this namespace, the fully qualified name of the MathStuff module now
becomes Utilities.MathStuff and the fully qualified name of the add function now
becomes Utilities.MathStuff.add .
With the namespace, the indentation rules apply, so that the module defined above must
have its content indented, as it it were a nested module.
You can also declare a namespace implicitly by adding dots to the module name. That is,
the code above could also be written as:
module Utilities.MathStuff
// functions
let add x y = x + y
let subtract x y = x - y
The fully qualified name of the MathStuff module is still Utilities.MathStuff , but in this
case, the module is a top-level module and the contents do not need to be indented.
Some additional things to be aware of when using namespaces:
Namespaces are optional for modules. And unlike C#, there is no default namespace
for an F# project, so a top level module without a namespace will be at the global level.
If you are planning to create reusable libraries, be sure to add some sort of namespace
to avoid naming collisions with code in other libraries.
Namespaces can directly contain type declarations, but not function declarations. As
noted earlier, all function and value declarations must be part of a module.
Finally, be aware that namespaces don't work well in scripts. For example, if you try to
to send a namespace declaration such as namespace Utilities below to the interactive
window, you will get an error.
Namespace hierarchies
340
Organizing functions
You can create a namespace hierarchy by simply separating the names with periods:
namespace Core.Utilities
module MathStuff =
let add x y = x + y
And if you want to put two namespaces in the same file, you can. Note that all namespaces
must be fully qualified -- there is no nesting.
namespace Core.Utilities
module MathStuff =
let add x y = x + y
namespace Core.Extra
module MoreMathStuff =
let add x y = x + y
One thing you can't do is have a naming collision between a namespace and a module.
namespace Core.Utilities
module MathStuff =
let add x y = x + y
namespace Core
// fully qualified name of module
// is Core.Utilities
// Collision with namespace above!
module Utilities =
let add x y = x + y
341
Organizing functions
In the alternative approach, the type is declared inside the module and given a simple name
such as " T " or the name of the module. So the functions are accessed with names like
MyModule.Func1 and MyModule.Func2 while the type itself is accessed with a name like
MyModule.T . Here's an example:
module Customer =
// Customer.T is the primary type for this module
type T = {AccountId:int; Name:string}
// constructor
let create id name =
{T.AccountId=id; T.Name=name}
// method that works on the type
let isValid {T.AccountId=id; } =
id > 0
// test
let customer = Customer.create 42 "bob"
Customer.isValid customer |> printfn "Is valid?=%b"
342
Organizing functions
Note that in both cases, you should have a constructor function that creates new instances
of the type (a factory method, if you will), Doing this means that you will rarely have to
explicitly name the type in your client code, and therefore, you not should not care whether it
lives in the module or not!
So which approach should you choose?
The former approach is more .NET like, and much better if you want to share your
libraries with other non-F# code, as the exported class names are what you would
expect.
The latter approach is more common for those used to other functional languages. The
type inside a module compiles into nested classes, which is not so nice for interop.
For yourself, you might want to experiment with both. And in a team programming situation,
you should choose one style and be consistent.
And here is a alternative way to do it. The module keyword has simply been replaced with
namespace .
// use a namespace
namespace Example
// declare the type outside any module
type PersonType = {First:string; Last:string}
In both cases, PersonType will have the same fully qualified name.
Note that this only works with types. Functions must always live in a module.
343
Organizing functions
344
You don't have to add a member at the same time that you declare the type, you can always
add it later in the same module:
345
module Person =
type T = {First:string; Last:string} with
// member defined with type declaration
member this.FullName =
this.First + " " + this.Last
// constructor
let create first last =
{First=first; Last=last}
// another member added later
type T with
member this.SortableName =
this.Last + ", " + this.First
// test
let person = Person.create "John" "Doe"
let fullname = person.FullName
let sortableName = person.SortableName
These examples demonstrate what are called "intrinsic extensions". They are compiled into
the type itself and are always available whenever the type is used. They also show up when
you use reflection.
With intrinsic extensions, it is even possible to have a type definition that divided across
several files, as long as all the components use the same namespace and are all compiled
into the same assembly. Just as with partial classes in C#, this can be useful to separate
generated code from authored code.
Optional extensions
Another alternative is that you can add an extra member from a completely different module.
These are called "optional extensions". They are not compiled into the type itself, and
require some other module to be in scope for them to work (this behavior is just like C#
extension methods).
For example, let's say we have a Person type defined:
346
module Person =
type T = {First:string; Last:string} with
// member defined with type declaration
member this.FullName =
this.First + " " + this.Last
// constructor
let create first last =
{First=first; Last=last}
// another member added later
type T with
member this.SortableName =
this.Last + ", " + this.First
Uh-oh, we have an error. What's wrong is that the PersonExtensions is not in scope. Just as
for C#, any extensions have to be brought into scope in order to be used.
Once we do that, everything is fine:
// bring the extension into scope first!
open PersonExtensions
let person = Person.create "John" "Doe"
let uppercaseName = person.UppercaseName
347
You can extend types that are in the .NET libraries as well. But be aware that when
extending a type, you must use the actual type name, not a type abbreviation.
For example, if you try to extend int , you will fail, because int is not the true name of the
type:
type int with
member this.IsEven = this % 2 = 0
Static members
You can make the member functions static by:
adding the keyword static
dropping the this placeholder
module Person =
type T = {First:string; Last:string} with
// member defined with type declaration
member this.FullName =
this.First + " " + this.Last
// static constructor
static member Create first last =
{First=first; Last=last}
// test
let person = Person.T.Create "John" "Doe"
let fullname = person.FullName
And you can create static members for system types as well:
348
In the following example, we start with a type with no members initially, then define some
functions, then finally attach the fullName function to the type.
349
module Person =
// type with no members initially
type T = {First:string; Last:string}
// constructor
let create first last =
{First=first; Last=last}
// standalone function
let fullName {First=first; Last=last} =
first + " " + last
// attach preexisting function as a member
type T with
member this.FullName = fullName this
// test
let person = Person.create "John" "Doe"
let fullname = Person.fullName person // functional style
let fullname2 = person.FullName // OO style
The standalone fullName function has one parameter, the person. In the attached member,
the parameter comes from the this self-reference.
350
module Person =
// type with no members initially
type T = {First:string; Last:string}
// constructor
let create first last =
{First=first; Last=last}
// standalone function
let hasSameFirstAndLastName (person:T) otherFirst otherLast =
person.First = otherFirst && person.Last = otherLast
// attach preexisting function as a member
type T with
member this.HasSameFirstAndLastName = hasSameFirstAndLastName this
// test
let person = Person.create "John" "Doe"
let result1 = Person.hasSameFirstAndLastName person "bob" "smith" // functional style
let result2 = person.HasSameFirstAndLastName "bob" "smith" // OO style
Why does this work? Hint: think about currying and partial application!
Tuple-form methods
When we start having methods with more than one parameter, we have to make a decision:
we could use the standard (curried) form, where parameters are separated with spaces,
and partial application is supported.
we could pass in all the parameters at once, comma-separated, in a single tuple.
The "curried" form is more functional, and the "tuple" form is more object-oriented.
The tuple form is also how F# interacts with the standard .NET libraries, so let's examine this
approach in more detail.
As a testbed, here is a Product type with two methods, each implemented using one of the
approaches. The CurriedTotal and TupleTotal methods each do the same thing: work out
the total price for a given quantity and discount.
351
No difference so far.
We know that curried version can be partially applied:
let totalFor10 = product.CurriedTotal 10
let discounts = [1.0..5.0]
let totalForDifferentDiscounts
= discounts |> List.map totalFor10
But the tuple approach can do a few things that that the curried one can't, namely:
Named parameters
Optional parameters
Overloading
As you can see, when names are used, the parameter order can be changed.
Note: if some parameters are named and some are not, the named ones must always be
last.
352
This explicit matching of the None and Some can be tedious, and there is a slightly more
elegant solution for handling optional parameters.
There is a function defaultArg which takes the parameter as the first argument and a
default for the second argument. If the parameter is set, the value is returned. And if not, the
default value is returned.
Let's see the same code rewritten to use defaultArg
type Product = {SKU:string; Price: float} with
// optional discount
member this.TupleTotal2(qty,?discount) =
let extPrice = this.Price * float qty
let discount = defaultArg discount 0.0
//return
extPrice - discount
353
Method overloading
In C#, you can have multiple methods with the same name that differ only in their function
signature (e.g. different parameter types and/or number of parameters)
In the pure functional model, that does not make sense -- a function works with a particular
domain type and a particular range type. The same function cannot work with different
domains and ranges.
However, F# does support method overloading, but only for methods (that is functions
attached to types) and of these, only those using tuple-style parameter passing.
Here's an example, with yet another variant on the TupleTotal method!
type Product = {SKU:string; Price: float} with
// no discount
member this.TupleTotal3(qty) =
printfn "using non-discount method"
this.Price * float qty
// with discount
member this.TupleTotal3(qty, discount) =
printfn "using discount method"
(this.Price * float qty) - discount
Normally, the F# compiler would complain that there are two methods with the same name,
but in this case, because they are tuple based and because their signatures are different, it
is acceptable. (To make it obvious which one is being called, I have added a small
debugging message.)
And here's a test:
let product = {SKU="ABC"; Price=2.0}
// discount not specified
let total1 = product.TupleTotal3(10)
// discount specified
let total2 = product.TupleTotal3(10,1.0)
354
If you are coming from an object-oriented background, you might be tempted to use
methods everywhere, because that is what you are familiar with. But be aware that there
some major downsides to using methods as well:
Methods don't play well with type inference
Methods don't play well with higher order functions
In fact, by overusing methods you would be needlessly bypassing the most powerful and
useful aspects of programming in F#.
Let's see what I mean.
Now let's see how well each one works with type inference. Say that I want to print the full
name of a person, so I will define a function printFullName that takes a person as a
parameter.
Here's the code using the module level standalone function.
open Person
// using standalone function
let printFullName person =
printfn "Name is %s" (fullName person)
// type inference worked:
// val printFullName : Person.T -> unit
355
This compiles without problems, and the type inference has correctly deduced that
parameter was a person
Now let's try the "dotted" version:
open Person
// using method with "dotting into"
let printFullName2 person =
printfn "Name is %s" (person.FullName)
This does not compile at all, because the type inference does not have enough information
to deduce the parameter. Any object might implement .FullName -- there is just not enough
to go on.
Yes, we could annotate the function with the parameter type, but that defeats the whole
purpose of type inference.
356
And this is just a simple example. Object methods don't compose well, are hard to pipe, and
so on.
So, a plea for those of you new to functionally programming. Don't use methods at all if you
can, especially when you are learning. They are a crutch that will stop you getting the full
benefit from functional programming.
357
The first steps to designing a system like this is to think about how it would be used.
Following a Forth like syntax, we will give each action a label, so that the example above
might want to be written something like:
EMPTY ONE THREE ADD TWO MUL SHOW
We might not be able to get this exact syntax, but let's see how close we can get.
But, hold on, let's wrap it in a single case union type to make it more descriptive, like this:
type Stack = StackContents of float list
358
For more details on why this is nicer, read the discussion of single case union types in this
post.
Now, to create a new stack, we use StackContents as a constructor:
let newStack = StackContents [1.0;2.0;3.0]
And to extract the contents of an existing Stack, we pattern match with StackContents :
let (StackContents contents) = newStack
// "contents" value set to
// float list = [1.0; 2.0; 3.0]
Next, what should the order of the parameters be? Should the stack parameter come first or
last? If you remember the discussion of designing functions for partial application, you will
remember that the most changeable thing should come last. You'll see shortly that this
guideline will be born out.
Finally, the function can be made more concise by using pattern matching in the function
parameter itself, rather than using a let in the body of the function.
359
Much nicer!
And by the way, look at the nice signature it has:
val push : float -> Stack -> Stack
As we know from a previous post, the signature tells you a lot about the function. In this
case, I could probably guess what it did from the signature alone, even without knowing that
the name of the function was "push". This is one of the reasons why it is a good idea to have
explicit type names. If the stack type had just been a list of floats, it wouldn't have been as
self-documenting.
Anyway, now let's test it:
let emptyStack = StackContents []
let stackWith1 = push 1.0 emptyStack
let stackWith2 = push 2.0 stackWith1
Works great!
But wait a minute! Can you see that the stack parameter is used on both sides? In fact, we
don't need to mention it at all. Instead we can skip the stack parameter and write the
functions using partial application as follows:
360
Now you can see that if the parameters for push were in a different order, we wouldn't have
been able to do this.
While we're at it, let's define a function that creates an empty stack as well:
let EMPTY = StackContents []
These intermediate stacks are annoying ? can we get rid of them? Yes! Note that these
functions ONE, TWO, THREE all have the same signature:
Stack -> Stack
This means that they can be chained together nicely! The output of one can be fed into the
input of the next, as shown below:
let result123 = EMPTY |> ONE |> TWO |> THREE
let result312 = EMPTY |> THREE |> ONE |> TWO
361
In other words, the pop function will have to return two values, the top plus the new stack.
The easiest way to do this in F# is just to use a tuple.
Here's the implementation:
/// Pop a value from the stack and return it
/// and the new stack as a tuple
let pop (StackContents contents) =
match contents with
| top::rest ->
let newStack = StackContents rest
(top,newStack)
362
It works!
Time to refactor...
It is obvious that there is significant duplicate code between these two functions. How can
we refactor?
Both functions pop two values from the stack, apply some sort of binary function, and then
push the result back on the stack. This leads us to refactor out the common code into a
"binary" function that takes a two parameter math function as a parameter:
363
Note that in this implementation, I've switched to using ticks to represent changed states of
the "same" object, rather than numeric suffixes. Numeric suffixes can easily get quite
confusing.
Question: why are the parameters in the order they are, instead of mathFn being after
stack ?
Now that we have binary , we can define ADD and friends more simply:
Here's a first attempt at ADD using the new binary helper:
let ADD aStack = binary (fun x y -> x + y) aStack
But we can eliminate the lambda, as it is exactly the definition of the built-in + function!
Which gives us:
let ADD aStack = binary (+) aStack
And again, we can use partial application to hide the stack parameter. Here's the final
definition:
let ADD = binary (+)
364
Note that in this case, we pop the original stack but ignore the diminished version. The final
result of the function is the original stack, as if it had never been popped.
So now finally, we can write the code example from the original requirements
EMPTY |> ONE |> THREE |> ADD |> TWO |> MUL |> SHOW
Going further
This is fun -- what else can we do?
365
And with these additional functions in place, we can write some nice examples:
START
|> ONE |> TWO |> SHOW
START
|> ONE |> TWO |> ADD |> SHOW
|> THREE |> ADD |> SHOW
START
|> THREE |> DUP |> DUP |> MUL |> MUL // 27
START
|> ONE |> TWO |> ADD |> SHOW // 3
|> THREE |> MUL |> SHOW // 9
|> TWO |> SWAP |> DIV |> SHOW // 9 div 2 = 4.5
So, because the input and output types are the same, these functions can be composed
using the composition operator >> , not just chained together with pipes.
366
In each of these cases, a new function is defined by composing other functions together to
make a new one. This is a good example of the "combinator" approach to building up
functionality.
Pipes vs composition
We have now seen two different ways that this stack based model can be used; by piping or
by composition. So what is the difference? And why would we prefer one way over another?
The difference is that piping is, in a sense, a "realtime transformation" operation. When you
use piping you are actually doing the operations right now, passing a particular stack around.
On the other hand, composition is a kind of "plan" for what you want to do, building an
overall function from a set of parts, but not actually running it yet.
367
So for example, I can create a "plan" for how to square a number by combining smaller
operations:
let COMPOSED_SQUARE = DUP >> MUL
This causes a compilation error. I have to have some sort of concrete stack instance to
make it work:
let stackWith2 = EMPTY |> TWO
let twoSquared = stackWith2 |> DUP |> MUL
And even then, I only get the answer for this particular input, not a plan for all possible
inputs, as in the COMPOSED_SQUARE example.
The other way to create a "plan" is to explicitly pass in a lambda to a more primitive function,
as we saw near the beginning:
let LAMBDA_SQUARE = unary (fun x -> x * x)
This is much more explicit (and is likely to be faster) but loses all the benefits and clarity of
the composition approach.
So, in general, go for the composition approach if you can!
368
369
// Numbers
// ------------------------------let ONE = push 1.0
let TWO = push 2.0
let THREE = push 3.0
let FOUR = push 4.0
let FIVE = push 5.0
// Math functions
// ------------------------------let ADD = binary (+)
let SUB = binary (-)
let MUL = binary (*)
let DIV = binary (../)
let NEG = unary (fun x -> -x)
// ==============================================
// Words based on composition
// ==============================================
let SQUARE =
DUP >> MUL
let CUBE =
DUP >> DUP >> MUL >> MUL
let SUM_NUMBERS_UPTO =
DUP // n
>> ONE >> ADD // n+1
>> MUL // n(n+1)
>> TWO >> SWAP >> DIV // n(n+1) / 2
Summary
370
So there we have it, a simple stack based calculator. We've seen how we can start with a
few primitive operations ( push , pop , binary , unary ) and from them, build up a whole
domain specific language that is both easy to implement and easy to use.
As you might guess, this example is based heavily on the Forth language. I highly
recommend the free book "Thinking Forth", which is not just about the Forth language, but
about (non object-oriented!) problem decomposition techniques which are equally applicable
to functional programming.
I got the idea for this post from a great blog by Ashley Feniello. If you want to go deeper into
emulating a stack based language in F#, start there. Have fun!
371
In this series of posts we'll look at how functions and values are combined into expressions,
and the different kinds of expressions that are available in F#.
Expressions and syntax: Introduction. How to code in F#.
Expressions vs. statements. Why expressions are safer and make better building
blocks.
Overview of F# expressions. Control flows, lets, dos, and more.
Binding with let, use, and do. How to use them.
F# syntax: indentation and verbosity. Understanding the offside rule.
Parameter and value naming conventions. a, f, x and friends.
Control flow expressions. And how to avoid using them.
Exceptions. Syntax for throwing and catching.
Match expressions. The workhorse of F#.
Formatted text using printf. Tips and techniques for printing and logging.
Worked example: Parsing command line arguments. Pattern matching in practice.
Worked example: Roman numerals. More pattern matching in practice.
372
373
374
First, let's look at a statement based approach. Statements don't return values, so you have
to use temporary variables that are assigned to from within statement bodies. Here are
some examples using a C-like language (OK, C#) rather than F#:
public void IfThenElseStatement(bool aBool)
{
int result; //what is the value of result before it is used?
if (aBool)
{
result = 42; //what is the result in the 'else' case?
}
Console.WriteLine("result={0}", result);
}
Because the "if-then" is a statement, the result variable must be defined outside the
statement and but assigned to inside the statement, which leads to some issues:
The result variable has to be set up outside the statement itself. What initial value
should it be set to?
What if I forget to assign to the result variable in the if statement? The purpose of
the "if "statement is purely to have side effects (the assignment to the variables). This
means that the statements are potentially buggy, because it would be easy to forget to
do an assignment in one branch. And because the assignment was just a side effect,
the compiler could not offer any warning. Since the result variable has already been
defined in scope, I could easily use it, unaware that it was invalid.
What is the value of the result variable in the "else" case? In this case, I haven't
specified a value. Did I forget? Is this a potential bug?
Finally, the reliance on side-effects to get things done means that the statements are
not easily usable in another context (for example, extracted for refactoring, or
parallelizing) because they have a dependency on a variable that is not part of the
statement itself.
Note: the code above will not compile in C# because the compiler will complain if you use an
unassigned local variable like this. But having to define some default value for result
before it is even used is still a problem.
For comparison, here is the same code, rewritten in an expression-oriented style:
public void IfThenElseExpression(bool aBool)
{
int result = aBool ? 42 : 0;
Console.WriteLine("result={0}", result);
}
375
The " mutable " keyword is considered a code smell in F#, and is discouraged except in
certain special cases. It should be avoided at all cost while you are learning!
In the expression based version, the mutable variable has been eliminated and there is no
reassignment anywhere.
let IfThenElseExpression aBool =
let result = if aBool then 42 else 0
// note that the else case must be specified
printfn "result=%i" result
Once we have the if statement converted into an expression, it is now trivial to refactor it
and move the entire subexpression to a different context without introducing errors.
Here's the refactored version in C#:
public int StandaloneSubexpression(bool aBool)
{
return aBool ? 42 : 0;
}
public void IfThenElseExpressionRefactored(bool aBool)
{
int result = StandaloneSubexpression(aBool);
Console.WriteLine("result={0}", result);
}
And in F#:
376
I've used an old-style "for" statement, where the index variables are declared outside the
loop. Many of the issues discussed earlier apply to the loop index " i " and the max value
" length ", such as: can they be used outside the loop? And what happens if they are not
assigned to?
A more modern version of a for-loop addresses these issues by declaring and assigning the
loop variables in the "for" loop itself, and by requiring the " sum " variable to be initialized:
377
This more modern version follows the general principle of combining the declaration of a
local variable with its first assignment.
But of course, we can keep improving by using a foreach loop instead of a for loop:
public void LoopStatementForEach()
{
var array = new int[] { 1, 2, 3 };
int sum = 0; // initialization is required
foreach (var i in array)
{
sum += i;
}
Console.WriteLine("sum={0}", sum);
}
Each time, not only are we condensing the code, but we are reducing the likelihood of
errors.
But taking that principle to its logical conclusion leads to a completely expression based
approach! Here's how it might be done using LINQ:
public void LoopExpression()
{
var array = new int[] { 1, 2, 3 };
var sum = array.Aggregate(0, (sumSoFar, i) => sumSoFar + i);
Console.WriteLine("sum={0}", sum);
}
378
Note that I could have used LINQ's built-in "sum" function, but I used Aggregate in order to
show how the sum logic embedded in a statement can be converted into a lambda and used
as part of an expression.
In the next post, we'll look at the various kinds of expressions in F#.
379
Overview of F# expressions
Overview of F# expressions
In this post we'll look at the different kinds of expressions that are available in F# and some
general tips for using them.
380
Overview of F# expressions
In other languages, these might be statements, but in F# they really do return values, as you
can see by binding a value to the result:
let x1 = fun () -> 1
let x2 = match 1 with
| 1 -> "a"
| _ -> "b"
let x3 = if true then "a" else "b"
let x4 = for i in [1..10]
do printf "%i" i
let x5 = try
let result = 1 / 0
printfn "%i" result
with
| e ->
printfn "%s" e.Message
381
Overview of F# expressions
How can " let " be an expression? The reason will be discussed in the next post on "let",
"use" and "do".
382
Overview of F# expressions
The rule about requiring unit values until the last expression still applies, of course:
let x = 1;2 // error: "1;" should be a unit expression
let x = ignore 1;2 // ok
let x = printf "hello";2 // ok
What happens is that both "true" and "false" are printed, even though the test function will
never actually evaluate the "else" branch. Why? Because the (printfn "false") expression
is evaluated immediately, regardless of how the test function will be using it.
This style of evaluation is called "eager". It has the advantage that it is easy to understand,
but it does mean that it can be inefficient on occasion.
The alternative style of evaluation is called "lazy", whereby expressions are only evaluated
when they are needed. The Haskell language follows this approach, so a similar example in
Haskell would only print "true".
In F#, there are a number of techniques to force expressions not to be evaluated
immediately. The simplest it to wrap it in a function that only gets evaluated on demand:
// create a clone of if-then-else that accepts functions rather than simple values
let test b t f = if b then t() else f()
// call it with two different functions
test true (fun () -> printfn "true") (fun () -> printfn "false")
The problem with this is that now the "true" function might be evaluated twice by mistake,
when we only wanted to evaluate it once!
383
Overview of F# expressions
So, the preferred way for expressions not to be evaluated immediately is to use the Lazy<>
wrapper.
// create a clone of if-then-else with no restrictions...
let test b t f = if b then t else f
// ...but call it with lazy values
let f = test true (lazy (printfn "true")) (lazy (printfn "false"))
The final result value f is also a lazy value, and can be passed around without being
evaluated until you are finally ready to get the result.
f.Force() // use Force() to force the evaluation of a lazy value
If you never need the result, and never call Force() , then the wrapped value will never be
evaluated.
There will much more on laziness in an upcoming series on performance.
384
"let" bindings
The let binding is straightforward, it has the general form:
let aName = someExpression
But there are two uses of let that are subtly different. One is to define a named expression
at a the top level of a module*, and the other is to define a local name used in the context of
some expression. This is somewhat analogous to the difference between "top level" method
names and "local" variable names in C#.
* and in a later series, when we talk about OO features, classes can have top level let
bindings too.
Here's an example of both types:
module MyModule =
let topLevelName =
let nestedName1 = someExpression
let nestedName2 = someOtherExpression
finalExpression
The top level name is a definition, which is part of the module, and you can access it with a
fully qualified name such as MyModule.topLevelName . It's the equivalent of a class method, in
a sense.
But the nested names are completely inaccessible to anyone -- they are only valid within the
context of the top level name binding.
385
We have already seen examples of how bindings can use patterns directly
let a,b = 1,2
type Person = {First:string; Last:string}
let alice = {First="Alice"; Last="Doe"}
let {First=first} = alice
The details of the various pattern bindings depends on the type being bound, and will be
discussed further in later posts on pattern matching.
That is, every time you see the symbol "nestedName" in the second expression (called the
body expression), substitute it with the first expression.
So for example, the expression:
386
// standard syntax
let f () =
let x = 1
let y = 2
x + y // the result
really means:
// syntax using "in" keyword
let f () =
let x = 1 in // the "in" keyword is available in F#
let y = 2 in
x + y // the result
In a sense, the nested names are just "macros" or "placeholders" that disappear when the
expression is compiled. And therefore you should be able to see that the nested let s have
no effect on the expression as whole. So, for example, the type of an expression containing
nested let s is just the type of the final body expression.
If you understand how nested let bindings work, then certain errors become
understandable. For example, if there is nothing for a nested "let" to be "in", the entire
expression is not complete. In the example below, there is nothing following the let line,
which is an error:
let f () =
let x = 1
// error FS0588: Block following this 'let' is unfinished.
// Expect an expression.
And you cannot have multiple expression results, because you cannot have multiple body
expressions. Anything evaluated before the final body expression must be a " do "
expression (see below), and return unit .
387
let f () =
2 + 2 // warning FS0020: This expression should
// have type 'unit'
let x = 1
x + 1 // this is the final result
In a case like this, you must pipe the results into "ignore".
let f () =
2 + 2 |> ignore
let x = 1
x + 1 // this is the final result
"use" bindings
The use keyword serves the same purpose as let -- it binds the result of an expression
to a named value.
The key difference is that is also automatically disposes the value when it goes out of scope.
Obviously, this means that use only applies in nested situations. You cannot have a top
level use and the compiler will warn you if you try.
module A =
use f () = // Error
let x = 1
x + 1
To see how a proper use binding works, first let's create a helper function that creates an
IDisposable on the fly.
388
We can see that "done" is printed, and then immediately after that, myResource goes out of
scope, its Dispose is called, and "hello disposed" is also printed.
On the other hand, if we test it using the regular let binding, we don't get the same effect.
let exampleLetBinding name =
let myResource = makeResource name
printfn "done"
//test
exampleLetBinding "hello"
In this case, we see that "done" is printed, but Dispose is never called.
389
If you need to work with a disposable "outside" the function that created it, probably the best
way is to use a callback.
The function then would work as follows:
create the disposable.
evaluate the callback with the disposable
call Dispose on the disposable
Here's an example:
let usingResource name callback =
use myResource = makeResource name
callback myResource
printfn "done"
let callback aResource = printfn "Resource is %A" aResource
do usingResource "hello" callback
This approach guarantees that the same function that creates the disposable also disposes
of it and there is no chance of a leak.
Another possible way is to not use a use binding on creation, but use a let binding
instead, and make the caller responsible for disposing.
Here's an example:
let returnValidResource name =
// "let" binding here instead of "use"
let myResource = makeResource name
myResource // still valid
let testValidResource =
// "use" binding here instead of "let"
use resource = returnValidResource "hello"
printfn "done"
Personally, I don't like this approach, because it is not symmetrical and separates the create
from the dispose, which could lead to resource leaks.
390
In practice, the using function is not used that often, because it is so easy to make your
own custom version of it, as we saw earlier.
Misusing "use"
One trick in F# is to appropriate the use keyword to do any kind of "stop" or "revert"
functionality automatically.
The way to do this is:
Create an extension method for some type
In that method, start the behavior you want but then return an IDisposable that stops
the behavior.
For example, here is an extension method that starts a timer and then returns an
IDisposable that stops it.
391
module TimerExtensions =
type System.Timers.Timer with
static member StartWithDisposable interval handler =
// create the timer
let timer = new System.Timers.Timer(interval)
// add the handler and start it
do timer.Elapsed.Add handler
timer.Start()
// return an IDisposable that calls "Stop"
{ new System.IDisposable with
member disp.Dispose() =
do timer.Stop()
do printfn "Timer stopped"
}
So now in the calling code, we create the timer and bind it with use . When the timer value
goes out of scope, it will stop automatically!
open TimerExtensions
let testTimerWithDisposable =
let handler = (fun _ -> printfn "elapsed")
use timer = System.Timers.Timer.StartWithDisposable 100.0 handler
System.Threading.Thread.Sleep 500
This same approach can be used for other common pairs of operations, such as:
opening/connecting and then closing/disconnecting a resource (which is what
IDisposable is supposed to be used for anyway, but your target type might not have
implemented it)
registering and then deregistering an event handler (instead of using WeakReference )
in a UI, showing a splash screen at the start of a block of code, and then automatically
closing it at the end of the block
I wouldn't recommend this approach generally, because it does hide what is going on, but on
occasion it can be quite useful.
"do" bindings
Sometimes we might want to execute code independently of a function or value definition.
This can be useful in module initialization, class initialization and so on.
392
That is, rather than having " let x = do something " we just the " do something " on its own.
This is analogous to a statement in an imperative language.
You can do this by prefixing the code with " do ":
do printf "logging"
But in both cases, the expression must return unit. If it does not, you will get a compiler error.
do 1 + 1 // warning: This expression is a function
As always, you can force a non-unit result to be discarded by piping the results into
" ignore ".
do ( 1+1 |> ignore )
You will also see the " do " keyword used in loops in the same way.
Note that although you can sometimes omit it, it is considered good practice to always have
an explicit " do ", as it acts as documentation that you do not want a result, only the sideeffects.
393
This is somewhat analogous to a static class constructor in C#, except that if there are
multiple modules, the order of initialization is fixed and they are initialized in order of
declaration.
394
395
class AttributeTest
{
[Obsolete]
public static int MyObsoleteFunction(int x, int y)
{
return x + y;
}
[CLSCompliant(false)]
public static void NonCompliant()
{
}
}
module AttributeTest =
[<Obsolete>]
let myObsoleteFunction x y = x + y
[<CLSCompliant(false)>]
let nonCompliant () = ()
396
Just as in C#, the args are an array of strings. But unlike C#, where the static Main method
can be void , the F# function must return an int.
Also, a big gotcha is that the function that has this attribute must be the very last function in
the last file in the project! Otherwise you get this error:
error FS0191: A function labelled with the 'EntryPointAttribute' atribute must be the
last declaration in the last file in the compilation sequence
Now in F# projects, there are no forward references allowed. That is, expressions that refer
to other expressions must be declared after them. And so logically, the highest, most toplevel function of them all, main , must come last of all.
397
open System.Reflection
module AssemblyInfo =
[<assembly: AssemblyTitle("MyAssembly")>]
[<assembly: AssemblyVersion("1.2.0.0")>]
[<assembly: AssemblyFileVersion("1.2.3.4152")>]
do () // do nothing -- just a placeholder for the attribute
It works the same way in F# as in C#. One thing to note is that the extern declaration ...
puts the types before the parameters, C-style.
398
open System.Runtime.InteropServices
open System.Text
[<DllImport("shlwapi", CharSet = CharSet.Ansi, EntryPoint = "PathCanonicalize", SetLas
tError = true)>]
extern bool PathCanonicalize(StringBuilder lpszDst, string lpszSrc)
let TestPathCanonicalize() =
let input = @"A:\name_1\.\name_2\..\name_3"
let expected = @"A:\name_1\name_3"
let builder = new StringBuilder(260)
let success = PathCanonicalize(builder, input)
let actual = builder.ToString()
printfn "actual=%s success=%b" actual (expected = actual)
// test
TestPathCanonicalize()
Interop with unmanaged code is a big topic which will need its own series.
399
Various tokens can trigger new offside lines to be created. For example, when the F# sees
the " = " used in a let expression, a new offside line is created at the position of the very
next symbol or word encountered.
400
//character columns
//34567890123456789
let f = let x=1 // line is now at column 11 (start of "let x=")
x+1 // must start at column 11 from now on
// | // offside line at col 11
let f = let x=1 // line is now at column 11 (start of "let x=")
x+1 // offside!
Other tokens have the same behavior, including parentheses, " then ", " else ", " try ",
" finally " and " do ", and " -> " in match clauses.
//character columns
//34567890123456789
let f =
let g = (
1+2) // first char after "(" defines
// a new line at col 5
g
let f =
if true then
1+2 // first char after "then" defines
// a new line at col 5
let f =
match 1 with
| 1 ->
1+2 // first char after match "->" defines
// a new line at col 8
The offside lines can be nested, and are pushed and popped as you would expect:
401
//character columns
//34567890123456789
let f =
let g = let x = 1 // first word after "let g ="
// defines a new offside line at col 12
x + 1 // "x" must align at col 12
// pop the offside line stack now
g + 1 // back to previous line. "g" must align
// at col 4
New offside lines can never go forward further than the previous line on the stack:
let f =
let g = ( // let defines a new line at col 4
1+2) // oops! Cant define new line less than 4
g
Special cases
There are number of special cases which have been created to make code formatting more
flexible. Many of them will seem natural, such as aligning the start of each part of an ifthen-else expression or a try-catch expression. There are some non-obvious ones,
however.
Infix operators such as "+", "|>" and ">>" are allowed to be outside the line by their length
plus one space:
//character columns
//34567890123456789
let x = 1 // defines a new line at col 10
+ 2 // "+" allowed to be outside the line
+ 3
let f g h = g // defines a new line at col 15
>> h // ">>" allowed to be outside the line
If an infix operator starts a line, that line does not have to be strict about the alignment:
let x = 1 // defines a new line at col 10
+ 2 // infix operators that start a line don't count
* 3 // starts with "*" so doesn't need to align
- 4 // starts with "-" so doesn't need to align
If a " fun " keyword starts an expression, the "fun" does not start a new offside line:
402
//character columns
//34567890123456789
let f = fun x -> // "fun" should define a new line at col 9
let y = 1 // but doesn't. The real line starts here.
x + y
"Verbose" syntax
By default, F# uses indentation to indicate block structure -- this is called "light" syntax.
There is an alternative syntax that does not use indentation; it is called "verbose" syntax.
With verbose syntax, you are not required to use indentation, and whitespace is not
significant, but the downside is that you are required to use many more keywords, including
things like:
" in " keywords after every "let" and "do" binding
" begin "/" end " keywords for code blocks such as if-then-else
" done " keywords at the end of loops
keywords at the beginning and end of type definitions
Here is an example of verbose syntax with wacky indentation that would not otherwise be
acceptable:
#indent "off"
let f =
let x = 1 in
if x=2 then
begin "a" end else begin
"b"
end
#indent "on"
Verbose syntax is always available, even in "light" mode, and is occasionally useful. For
example, when you want to embed "let" into a one line expression:
403
Other cases when you might want to use verbose syntax are:
when outputting generated code
to be compatible with OCaml
if you are visually impaired or blind and use a screen reader
or just to gain some insight into the abstract syntax tree used by the F# parser
Other than these cases, verbose syntax is rarely used in practice.
404
Here is the same implementation, with terser, idiomatic names and more compact code:
let primesUpTo n =
let rec sieve l =
match l with
| [] -> []
| p::xs ->
p :: sieve [for x in xs do if (x % p) > 0 then yield x]
[2..n] |> sieve
The cryptic names are not always better, of course, but if the function is kept to a few lines
and the operations used are standard, then this is a fairly common idiom.
405
Another reason for the short names is that often, they cannot be assigned to anything
meaningful. For example, the definition of the pipe operator is:
let (|>) x f = f x
We don't know what f and x are going to be, f could be any function and x could be
any value. Making this explicit does not make the code any more understandable.
let (|>) aValue aFunction = aFunction aValue // any better?
406
If-then-else
How to avoid using if-then-else
The best way to avoid if-then-else is to use "match" instead. You can match on a
boolean, which is similar to the classic then/else branches. But much, much better, is to
avoid the equality test and actually match on the thing itself, as shown in the last
implementation below.
407
// bad
let f x =
if x = 1
then "a"
else "b"
// not much better
let f x =
match x=1 with
| true -> "a"
| false -> "b"
// best
let f x =
match x with
| 1 -> "a"
| _ -> "b"
Part of the reason why direct matching is better is that the equality test throws away useful
information that you often need to retrieve again.
This is demonstrated by the next scenario, where we want to get the first element of a list in
order to print it. Obviously, we must be careful not to attempt this for an empty list.
The first implementation does a test for empty and then a second operation to get the first
element. A much better approach is to match and extract the element in one single step, as
shown in the second implementation.
// bad
let f list =
if List.isEmpty list
then printfn "is empty"
else printfn "first element is %s" (List.head list)
// much better
let f list =
match list with
| [] -> printfn "is empty"
| x::_ -> printfn "first element is %s" x
408
// bad
let f list =
if List.isEmpty list
then printfn "is empty"
elif (List.head list) > 0
then printfn "first element is > 0"
else printfn "first element is <= 0"
// much better
let f list =
match list with
| [] -> printfn "is empty"
| x::_ when x > 0 -> printfn "first element is > 0"
| x::_ -> printfn "first element is <= 0"
Again, the second implementation is easier to understand and also more efficient.
The moral of the tale is: if you find yourself using if-then-else or matching on booleans,
consider refactoring your code.
But as a consequence, both branches must return the same type! If this is not true, then the
expression as a whole cannot return a consistent type and the compiler will complain.
Here is an example of different types in each branch:
let v = if true then "a" else 2
// error FS0001: This expression was expected to have
// type string but here has type int
The "else" clause is optional, but if it is absent, the "else" clause is assumed to return unit,
which means that the "then" clause must also return unit. You will get a complaint from the
compiler if you make this mistake.
409
If the "then" clause returns unit, then the compiler will be happy.
let v2 = if true then printfn "a" // OK as printfn returns unit
Note that there is no way to return early in a branch. The return value is the entire
expression. In other words, the if-then-else expression is more closely related to the C#
ternary if operator (?:) than to the C# if-then-else statement.
Returning functions
Don't forget that an if-then-else expression can return any value, including function values.
For example:
let greetings =
if (System.DateTime.Now.Hour < 12)
then (fun name -> "good morning, " + name)
else (fun name -> "good day, " + name)
//test
greetings "Alice"
Of course, both functions must have the same type, meaning that they must have the same
function signature.
Loops
How to avoid using loops
410
The best way to avoid loops is to use the built in list and sequence functions instead. Almost
anything you want to do can be done without using explicit loops. And often, as a side
benefit, you can avoid mutable values as well. Here are some examples to start with, and for
more details please read the upcoming series devoted to list and sequence operations.
Example: Printing something 10 times:
// bad
for i = 1 to 10 do
printf "%i" i
// much better
[1..10] |> List.iter (printf "%i")
411
// bad
let printRandomNumbersUntilMatched matchValue maxValue =
let mutable continueLooping = true // another mutable value
let randomNumberGenerator = new System.Random()
while continueLooping do
// Generate a random number between 1 and maxValue.
let rand = randomNumberGenerator.Next(maxValue)
printf "%d " rand
if rand = matchValue then
printfn "\nFound a %d!" matchValue
continueLooping <- false
// much better
let printRandomNumbersUntilMatched matchValue maxValue =
let randomNumberGenerator = new System.Random()
let sequenceGenerator _ = randomNumberGenerator.Next(maxValue)
let isNotMatch = (<>) matchValue
//create and process the sequence of rands
Seq.initInfinite sequenceGenerator
|> Seq.takeWhile isNotMatch
|> Seq.iter (printf "%d ")
// done
printfn "\nFound a %d!" matchValue
//test
printRandomNumbersUntilMatched 10 20
As with if-then-else, there is a moral; if you find yourself using loops and mutables, please
consider refactoring your code to avoid them.
412
Summary
I'll repeat what I said at the top of the post: do avoid using imperative control flow when you
are learning to think functionally. And understand the exceptions that prove the rule; the oneliners whose use is acceptable.
413
Exceptions
Exceptions
Just like other .NET languages, F# supports throwing and catching exceptions. As with the
control flow expressions, the syntax will feel familiar, but again there are a few catches that
you should know about.
syntax shown below, where the "content" of the exception is any F# type:
exception MyFSharpError1 of string
exception MyFSharpError2 of string * int
That's it! Defining new exception classes is a lot easier than in C#!
Throwing exceptions
There are three basic ways to throw an exception
Using one of the built in functions, such as "invalidArg"
Using one of the standard .NET exception classes
Using your own custom exception types
These four probably cover most of the exceptions you would regularly throw. Here is how
they are used:
414
Exceptions
By the way, there's a very useful variant of failwith called failwithf that includes
printf style formatting, so that you can make custom messages easily:
open System
let f x =
if x = "bad" then
failwithf "Operation '%s' failed at time %O" x DateTime.Now
else
printfn "Operation '%s' succeeded at time %O" x DateTime.Now
// test
f "good"
f "bad"
Exceptions
Question: what do you think the function signature will be if both branches raise exceptions?
let f x =
if x then failwith "error in true branch"
else failwith "error in false branch"
Catching exceptions
Exceptions are caught using a try-catch block, as in other languages. F# calls it try-with
instead, and testing for each type of exception uses the standard pattern matching syntax.
416
Exceptions
try
failwith "fail"
with
| Failure msg -> "caught: " + msg
| MyFSharpError1 msg -> " MyFSharpError1: " + msg
| :? System.InvalidOperationException as ex -> "unexpected"
If the exception to catch was thrown with failwith (e.g. a System.Exception) or a custom
F# exception, you can match using the simple tag approach shown above.
On the other hand, to catch a specific .NET exception class, you have to match using the
more complicated syntax:
:? (exception class) as ex
Again, as with if-then-else and the loops, the try-with block is an expression that returns a
value. This means that all branches of the try-with expression must return the same type.
Consider this example:
let divide x y=
try
(x+1) / y // error here -- see below
with
| :? System.DivideByZeroException as ex ->
printfn "%s" ex.Message
The reason is that the " with " branch is of type unit , while the " try " branch is of type
int . So the two branches are of incompatible types.
To fix this, we need to make the " with " branch also return type int . We can do this easily
using the semicolon trick to chain expressions on one line.
417
Exceptions
let divide x y=
try
(x+1) / y
with
| :? System.DivideByZeroException as ex ->
printfn "%s" ex.Message; 0 // added 0 here!
//test
divide 1 1
divide 1 0
Now that the try-with expression has a defined type, the whole function can be assigned a
type, namely int -> int -> int , as expected.
As before, if any branch throws an exception, it doesn't count when types are being
determined.
Rethrowing exceptions
If needed, you can call the " reraise() " function in a catch handler to propagate the same
exception up the call chain. This is the same as the C# throw keyword.
let divide x y=
try
(x+1) / y
with
| :? System.DivideByZeroException as ex ->
printfn "%s" ex.Message
reraise()
//test
divide 1 1
divide 1 0
Try-finally
Another familiar expression is try-finally . As you might expect, the "finally" clause will be
called no matter what.
let f x =
try
if x then "ok" else failwith "fail"
finally
printf "this will always be printed"
418
Exceptions
The return type of the try-finally expression as a whole is always the same as return type of
the "try" clause on its own. The "finally" clause has no effect on the type of the expression as
a whole. So in the above example, the whole expression has type string .
The "finally" clause must always return unit, so any non-unit values will be flagged by the
compiler.
let f x =
try
if x then "ok" else failwith "fail"
finally
1+1 // This expression should have type 'unit
Exceptions
Note the use of Some and None Option types in the tryDivide code to signal to the client
whether the value is valid.
With the first function, the client code must handle the exception explicitly.
// client code must handle exceptions explicitly
try
let n = divideExn 1 0
printfn "result is %i" n
with
| :? System.DivideByZeroException as ex -> printfn "divide by zero"
Note that there is no constraint that forces the client to do this, so this approach can be a
source of errors.
With the second function the client code is simpler, and the client is constrained to handle
both the normal case and the error case.
// client code must test both cases
match tryDivide 1 0 with
| Some n -> printfn "result is %i" n
| None -> printfn "divide by zero"
This "normal vs. try" approach is very common in the .NET BCL, and also occurs in a few
cases in the F# libraries too. For example, in the List module:
List.find will throw a KeyNotFoundException if the key is not found
But List.tryFind will return an Option type, with None if the key is not found
If you are going to use this approach, do have a naming convention. For example:
"doSomethingExn" for functions that expect clients to catch exceptions.
"tryDoSomething" for functions that handle normal exceptions for you.
Note that I prefer to have an "Exn" suffix on "doSomething" rather than no suffix at all. It
makes it clear that you expect clients to catch exceptions even in normal cases.
420
Exceptions
The overall problem with this approach is that you have to do extra work to create pairs of
functions, and you reduce the safety of the system by relying on the client to catch
exceptions if they use the unsafe version of the function.
421
Exceptions
open System.Data.SqlClient
type NonQueryResult =
| Success of int
| LoginError of SqlException
| ConstraintError of SqlException
| ForeignKeyError of SqlException
let executeNonQuery (sqlCommmand:SqlCommand) =
try
use sqlConnection = new SqlConnection("myconnection")
sqlCommmand.Connection <- sqlConnection
let result = sqlCommmand.ExecuteNonQuery()
Success result
with
| :?SqlException as ex -> // if a SqlException
match ex.Number with
| 18456 -> // login Failed
LoginError ex
| 2601 | 2627 -> // handle constraint error
ConstraintError ex
| 547 -> // handle FK error
ForeignKeyError ex
| _ -> // don't handle any other cases
reraise()
// all non SqlExceptions are thrown normally
The client is then forced to handle the common cases, while uncommon exceptions will be
caught by a handler higher up the call chain.
let myCmd = new SqlCommand("DELETE Product WHERE ProductId=1")
let result = executeNonQuery myCmd
match result with
| Success n -> printfn "success"
| LoginError ex -> printfn "LoginError: %s" ex.Message
| ConstraintError ex -> printfn "ConstraintError: %s" ex.Message
| ForeignKeyError ex -> printfn "ForeignKeyError: %s" ex.Message
Unlike a traditional error code approach, the caller of the function does not have to handle
any errors immediately, and can simply pass the structure around until it gets to someone
who knows how to handle it, as shown below:
422
Exceptions
On the other hand, unlike C#, the result of a expression cannot be accidentally thrown away.
So if a function returns an error result, the caller must handle it (unless it really wants to be
badly behaved and send it to ignore )
let presentationLayerFunction =
do deleteProduct 1 // error: throwing away a result code!
423
Match expressions
Match expressions
Pattern matching is ubiquitous in F#. It is used for binding values to expressions with let ,
and in function parameters, and for branching using the match..with syntax.
We have briefly covered binding values to expressions in a post in the "why use F#?" series,
and it will be covered many times as we investigate types.
So in this post, we'll cover the match..with syntax and its use for control flow.
If you squint at it just right, it looks a bit like a series of lambda expressions:
match [something] with
| lambda-expression-1
| lambda-expression-2
| lambda-expression-3
So one way of thinking about match..with is that it is a choice between a set of lambda
expressions. But how to make the choice?
This is where the patterns come in. The choice is made based on whether the "match with"
value can be matched with the parameter of the lambda expression. The first lambda whose
parameter can be made to match the input value "wins"!
So for example, if the param is the wildcard _ , it will always match, and if first, always win.
424
Match expressions
_ -> expression
Order is important!
Looking at the following example:
let x =
match 1 with
| 1 -> "a"
| 2 -> "b"
| _ -> "z"
We can see that there are three lambda expressions to match, in this order:
fun 1 -> "a"
fun 2 -> "b"
fun _ -> "z"
So, the 1 pattern gets tried first, then then the 2 pattern, and finally, the _ pattern.
On the other hand, if we changed the order to put the wildcard first, it would be tried first and
always win immediately:
let x =
match 1 with
| _ -> "z"
| 1 -> "a"
| 2 -> "b"
In this case, the F# compiler helpfully warns us that the other rules will never be matched.
So this is one major difference between a " switch " or " case " statement compared with a
match..with . In a match..with , the order is important.
425
Match expressions
Guideline 1: The alignment of the | expression clauses should be directly under the
match
Guideline 3: The expression after the arrow -> should be on a new line
Again, the result expression can be on the same line as the arrow, but using a new line
again keeps the indenting consistent and helps to separate the match pattern from the result
expression.
let f x =
match x with
| "a very long pattern that breaks up the flow" -> "something"
| _ -> "anything"
let f x =
match x with
| "a very long pattern that breaks up the flow" ->
"something"
| _ ->
"anything"
426
Match expressions
Of course, when all the patterns are very compact, a common sense exception can be
made:
let f list =
match list with
| [] -> "something"
| x::xs -> "something else"
match..with is an expression
It is important to realize that match..with is not really a "control flow" construct. The
"control" does not "flow" down the branches, but instead, the whole thing is an expression
that gets evaluated at some point, just like any other expression. The end result in practice
might be the same, but it is a conceptual difference that can be important.
One consequence of it being an expression is that all branches must evaluate to the same
type -- we have already seen this same behavior with if-then-else expressions and for loops.
let x =
match 1 with
| 1 -> 42
| 2 -> true // error wrong type
| _ -> "hello" // error wrong type
427
Match expressions
[2..10]
|> List.map (fun i ->
match i with
| 2 | 3 | 5 | 7 -> sprintf "%i is prime" i
| _ -> sprintf "%i is not prime" i
)
Exhaustive matching
Another consequence of being an expression is that there must always be some branch that
matches. The expression as a whole must evaluate to something!
That is, the valuable concept of "exhaustive matching" comes from the "everything-is-anexpression" nature of F#. In a statement oriented language, there would be no requirement
for this to happen.
Here's an example of an incomplete match:
let x =
match 42 with
| 1 -> "a"
| 2 -> "b"
The compiler will warn you if it thinks there is a missing branch. And if you deliberately
ignore the warning, then you will get a nasty runtime error ( MatchFailureException ) when
none of the patterns match.
428
Match expressions
let x =
match 42 with
| 1 -> "a"
| 2 -> "b"
| _ -> "z"
You see this pattern frequently, and I have used it a lot in these examples. It's the equivalent
of having a catch-all default in a switch statement.
But if you want to get the full benefits of exhaustive pattern matching, I would encourage you
not to use wildcards, and try to match all the cases explicitly if you can. This is particularly
true if you are matching on the cases of a union type:
type Choices = A | B | C
let x =
match A with
| A -> "a"
| B -> "b"
| C -> "c"
//NO default match
By being always explicit in this way, you can trap any error caused by adding a new case to
the union. If you had a wildcard match, you would never know.
If you can't have every case be explicit, you might try to document your boundary conditions
as much as possible, and assert an runtime error for the wildcard case.
let x =
match -1 with
| 1 -> "a"
| 2 -> "b"
| i when i >= 0 && i<=100 -> "ok"
// the last case will always match
| x -> failwithf "%i is out of range" x
Types of patterns
There are lots of different ways of matching patterns, which we'll look at next.
For more details on the various patterns, see the MSDN documentation.
Binding to values
The most basic pattern is to bind to a value as part of the match:
429
Match expressions
let y =
match (1,0) with
// binding to a named value
| (1,x) -> printfn "x=%A" x
By the way, I have deliberately left this pattern (and others in this post) as incomplete. As an
exercise, make them complete without using the wildcard.
It is important to note that the values that are bound must be distinct for each pattern. So
you can't do something like this:
let elementsAreEqual aTuple =
match aTuple with
| (x,x) ->
printfn "both parts are the same"
| (_,_) ->
printfn "both parts are different"
This second option can also be rewritten using "guards" ( when clauses) instead. Guards will
be discussed shortly.
AND and OR
You can combine multiple patterns on one line, with OR logic and AND logic:
let y =
match (1,0) with
// OR -- same as multiple cases on one line
| (2,x) | (3,x) | (4,x) -> printfn "x=%A" x
// AND -- must match both patterns at once
// Note only a single "&" is used
| (2,x) & (_,1) -> printfn "x=%A" x
The OR logic is particularly common when matching a large number of union cases:
430
Match expressions
type Choices = A | B | C | D
let x =
match A with
| A | B | C -> "a or b or c"
| D -> "d"
Matching on lists
Lists can be matched explicitly in the form [x;y;z] or in the "cons" form head::tail :
let y =
match [1;2;3] with
// binding to explicit positions
// square brackets used!
| [1;x;y] -> printfn "x=%A y=%A" x y
// binding to head::tail.
// no square brackets used!
| 1::tail -> printfn "tail=%A" tail
// empty list
| [] -> printfn "empty"
431
Match expressions
The second example shows how we can carry state from one iteration of the loop to the next
using a special "accumulator" parameter (called sumSoFar in this example). This is a very
common pattern.
432
Match expressions
Matching the whole and the part with the "as" keyword
Sometimes you want to match the individual components of the value and also the whole
thing. You can use the as keyword for this.
let y =
match (1,0) with
// binding to three values
| (x,y) as t ->
printfn "x=%A and y=%A" x y
printfn "The whole tuple is %A" t
Matching on subtypes
You can match on subtypes, using the :? operator, which gives you a crude polymorphism:
433
Match expressions
This only works to find subclasses of a parent class (in this case, Object). The overall type of
the expression has the parent class as input.
Note that in some cases, you may need to "box" the value.
let detectType v =
match v with
| :? int -> printfn "this is an int"
| _ -> printfn "something else"
// error FS0008: This runtime coercion or type test from type 'a to int
// involves an indeterminate type based on information prior to this program point.
// Runtime type tests are not allowed on some types. Further type annotations are need
ed.
The message tells you the problem: "runtime type tests are not allowed on some types". The
answer is to "box" the value which forces it into a reference type, and then you can type
check it:
let detectTypeBoxed v =
match box v with // used "box v"
| :? int -> printfn "this is an int"
| _ -> printfn "something else"
//test
detectTypeBoxed 1
detectTypeBoxed 3.14
In my opinion, matching and dispatching on types is a code smell, just as it is in objectoriented programming. It is occasionally necessary, but used carelessly is an indication of
poor design.
In a good object oriented design, the correct approach would be to use polymorphism to
replace the subtype tests, along with techniques such as double dispatch. So if you are
doing this kind of OO in F#, you should probably use those same techniques.
434
Match expressions
And indeed, this trick will work whenever you want to match on a set of values -- just group
them all into a single tuple.
let matchOnTwoTuples x y =
match (x,y) with
| (1,_),(1,_) -> "both start with 1"
| (_,2),(_,2) -> "both end with 2"
| _ -> "something else"
// test
matchOnTwoTuples (1,3) (1,2)
matchOnTwoTuples (3,2) (1,2)
Pattern matching is based on patterns only -- it can't use functions or other kinds of
conditional tests.
435
Match expressions
But there is a way to do the equality test as part of the pattern match -- using an additional
when clause to the left of the function arrow. These clauses are known as "guards".
This is nicer, because we have integrated the test into the pattern proper, rather than using a
test after the match has been done.
Guards can be used for all sorts of things that pure patterns can't be used for, such as:
comparing the bound values
testing object properties
doing other kinds of matching, such as regular expressions
conditionals derived from functions
Let's look at some examples of these:
// -------------------------------// comparing values in a when clause
let makeOrdered aTuple =
match aTuple with
// swap if x is bigger than y
| (x,y) when x > y -> (y,x)
// otherwise leave alone
| _ -> aTuple
//test
makeOrdered (1,2)
makeOrdered (2,1)
// -------------------------------// testing properties in a when clause
let isAM aDate =
match aDate:System.DateTime with
| x when x.Hour <= 12->
printfn "AM"
// otherwise leave alone
| _ ->
printfn "PM"
436
Match expressions
//test
isAM System.DateTime.Now
// -------------------------------// pattern matching using regular expressions
open System.Text.RegularExpressions
let classifyString aString =
match aString with
| x when Regex.Match(x,@".+@.+").Success->
printfn "%s is an email" aString
// otherwise leave alone
| _ ->
printfn "%s is something else" aString
//test
classifyString "alice@example.com"
classifyString "google.com"
// -------------------------------// pattern matching using arbitrary conditionals
let fizzBuzz x =
match x with
| i when i % 15 = 0 ->
printfn "fizzbuzz"
| i when i % 3 = 0 ->
printfn "fizz"
| i when i % 5 = 0 ->
printfn "buzz"
| i ->
printfn "%i" i
//test
[1..30] |> List.iter fizzBuzz
437
Match expressions
open System.Text.RegularExpressions
// create an active pattern to match an email address
let (|EmailAddress|_|) input =
let m = Regex.Match(input,@".+@.+")
if (m.Success) then Some input else None
// use the active pattern in the match
let classifyString aString =
match aString with
| EmailAddress x ->
printfn "%s is an email" x
// otherwise leave alone
| _ ->
printfn "%s is something else" aString
//test
classifyString "alice@example.com"
classifyString "google.com"
In the special case of function definitions we can simplify this dramatically by using the
function keyword.
let f =
function
| _ -> "something"
As you can see, the aValue parameter has completely disappeared, along with the
match..with .
This keyword is not the same as the fun keyword for standard lambdas, rather it combines
fun and match..with in a single step.
438
Match expressions
The function keyword works anywhere a function definition or lambda can be used, such
as nested matches:
// using match..with
let f aValue =
match aValue with
| x ->
match x with
| _ -> "something"
// using function keyword
let f =
function
| x ->
function
| _ -> "something"
A minor drawback of function compared with match..with is that you can't see the
original input value and have to rely on value bindings in the pattern.
439
Match expressions
The try..with expression implements pattern matching in the same way as match..with .
So in the above example we see the use of matching on a custom pattern
| Failure msg is an example of matching on (what looks like) an active pattern
| :? System.InvalidOperationException as ex is an example of matching on the subtype
440
Match expressions
can be rewritten using the List module in at least three different ways!
// simplest
let loopAndSum1 aList = List.sum aList
[1..10] |> loopAndSum1
// reduce is very powerful
let loopAndSum2 aList = List.reduce (+) aList
[1..10] |> loopAndSum2
// fold is most powerful of all
let loopAndSum3 aList = List.fold (fun sum i -> sum+i) 0 aList
[1..10] |> loopAndSum3
Similarly, the Option type (discussed at length in this post) has an associated Option
module with many useful functions.
For example, a function that does a match on Some vs None can be replaced with
Option.map :
441
Match expressions
Chances are, we will matching these cases a lot, so let's create a generic function that will
do the matching for us.
module Temperature =
let fold fahrenheitFunction celsiusFunction aTemp =
match aTemp with
| F f -> fahrenheitFunction f
| C c -> celsiusFunction c
442
Match expressions
Note that the conversion functions wrap the converted values in a new TemperatureType , so
the convert function has the signature:
val convert : TemperatureType -> TemperatureType
We can even call convert twice in a row, and we should get back the same temperature that
we started with!
let resultInC = C 20.0 |> convert |> convert
There will be much more discussion on folds in the upcoming series on recursion and
recursive types.
443
Match expressions
444
The C-style technique of using printf and the associated family of functions such as
printfn , sprintf and so on.
String.Format vs printf
The composite formatting technique is available in all .NET languages, and you are probably
familiar with it from C#.
Console.WriteLine("A string: {0}. An int: {1}. A float: {2}. A bool: {3}","hello",42,3
.14,true)
The printf technique, on the other hand, is based on the C-style format strings:
printfn "A string: %s. An int: %i. A float: %f. A bool: %b" "hello" 42 3.14 true
As you have seen, the printf technique is very common in F#, while String.Format ,
Console.Write and so on, are rarely used.
Why is printf preferred and considered idiomatic for F#? The reasons are:
It is statically type checked.
It is a well-behaved F# function and so supports partial application, etc.
It supports native F# types.
445
The equivalent code using composite formatting will compile fine but either work incorrectly
but silently, or give a runtime error:
// wrong parameter type
Console.WriteLine("A string: {0}", 42) //works!
// wrong number of parameters
Console.WriteLine("A string: {0}","Hello",42) //works!
Console.WriteLine("A string: {0}. An int: {1}","Hello") //FormatException
And of course, printf can be used for function parameters anywhere a standard function
can be used.
let doSomething printerFn x y =
let result = x + y
printerFn "result is" result
let callback = printfn "%s %i"
do doSomething callback 3 4
This also includes the higher order functions for lists, etc:
446
// tuple printing
let t = (1,2)
Console.WriteLine("A tuple: {0}", t)
printfn "A tuple: %A" t
// record printing
type Person = {First:string; Last:string}
let johnDoe = {First="John"; Last="Doe"}
Console.WriteLine("A record: {0}", johnDoe )
printfn "A record: %A" johnDoe
// union types printing
type Temperature = F of int | C of int
let freezing = F 32
Console.WriteLine("A union: {0}", freezing )
printfn "A union: %A" freezing
As you can see, tuple types have a nice ToString() but other user defined types don't, so if
you want to use them with the .NET formatting functions, you will have to override the
ToString() method explicitly.
printf gotchas
There are a couple of "gotchas" to be aware of when using printf .
First, if there are too few parameters, rather than too many, the compiler will not complain
immediately, but might give cryptic errors later.
// too few parameters
printfn "A string: %s An int: %i" "Hello"
The reason, of course, is that this is not an error at all; printf is just being partially applied!
See the discussion of partial application if you are not clear of why this happens.
Another issue is that the "format strings" are not actually strings.
447
In the .NET formatting model, the formatting strings are normal strings, so you can pass
them around, store them in resource files, and so on. Which means that the following code
works fine:
let netFormatString = "A string: {0}"
Console.WriteLine(netFormatString, "hello")
On the other hand, the "format strings" that are the first argument to printf are not really
strings at all, but something called a TextWriterFormat . Which means that the following
code does not work:
let fsharpFormatString = "A string: %s"
printfn fsharpFormatString "Hello"
The compiler does some magic behind the scenes to convert the string constant "A string:
%s" into the appropriate TextWriterFormat. The TextWriterFormat is the key component that
"knows" the type of the format string, such as string->unit or string->int->unit , which in
turn allows printf to be typesafe.
If you want to emulate the compiler, you can create your own TextWriterFormat value from a
string using the Printf.TextWriterFormat type in the Microsoft.FSharp.Core.Printf module.
If the format string is "inline", the compiler can deduce the type for you during binding:
let format:Printf.TextWriterFormat<_> = "A string: %s"
printfn format "Hello"
But if the format string is truly dynamic (e.g. stored in a resource or created on the fly), the
compiler cannot deduce the type for you, and you must explicitly provide it with the
constructor.
In the example below, my first format string has a single string parameter and returns a unit,
so I have to specify string->unit as the format type. And in the second case, I have to
specify string->int->unit as the format type.
let formatAString = "A string: %s"
let formatAStringAndInt = "A string: %s. An int: %i"
//convert to TextWriterFormat
let twFormat1 = Printf.TextWriterFormat<string->unit>(formatAString)
printfn twFormat1 "Hello"
let twFormat2 = Printf.TextWriterFormat<string->int->unit>(formatAStringAndInt)
printfn twFormat2 "Hello" 42
448
I won't go into detail on exactly how printf and TextWriterFormat` work together right now -just be aware that is not just a matter of simple format strings being passed around.
Finally, it's worth noting that printf and family are not thread-safe, while Console.Write
and family are.
Escaping %
The % character on its own will cause an error. To escape it, just double it up:
printfn "unescaped: %" // error
printfn "escape: %%"
449
When formatting fixed width columns and tables, you need to have control of the alignment
and width.
You can do that with the "width" and "flags" options.
%5s , %5i . A number sets the width of the value
%*s , %*i . A star sets the width of the value dynamically (from an extra parameter just
Formatting integers
There are some special options for basic integer types:
%i or %d for signed ints
%u for unsigned ints
%x and %X for lowercase and uppercase hex
%o for octal
450
The specifiers do not enforce any type safety within the integer types. As you can see from
the examples above, you can pass a signed int to an unsigned specifier without problems.
What is different is how it is formatted. The unsigned specifiers treat the int as unsigned no
matter how it is actually typed.
Note that BigInteger is not a basic integer type, so you must format it with %A or %O .
printfn "bigInt: %i " 123456789I // Error
printfn "bigInt: %A " 123456789I // OK
You can control the formatting of signs and zero padding using the flags:
%0i pads with zeros
%+i shows a plus sign
% i shows a blank in place of a plus sign
451
The decimal type can be used with the floating point specifiers, but you might lose some
precision. The %M specifier can be used to ensure that no precision is lost. You can see the
difference with this example:
452
You can control the precision of floats using a precision specification, such as %.2f and
%.4f . For the %f and %e specifiers, the precision affects the number of digits after the
decimal point, while for %g it is the number of digits in total. Here's an example:
printfn "2 digits precision: %.2f. 4 digits precision: %.4f." 123.456789 123.456789
// output => 2 digits precision: 123.46. 4 digits precision: 123.4568.
printfn "2 digits precision: %.2e. 4 digits precision: %.4e." 123.456789 123.456789
// output => 2 digits precision: 1.23e+002. 4 digits precision: 1.2346e+002.
printfn "2 digits precision: %.2g. 4 digits precision: %.4g." 123.456789 123.456789
// output => 2 digits precision: 1.2e+02. 4 digits precision: 123.5.
The alignment and width flags work for floats and decimals as well.
printfn "|%f|" pi // normal
printfn "|%10f|" pi // width
printfn "|%010f|" pi // zero-pad
printfn "|%-10f|" pi // left aligned
printfn "|%0-10f|" pi // left zero-pad
Obviously, since the callback function takes no parameters, it will probably be a closure that
does reference some other value. Here's an example that prints random numbers:
453
open System
open System.IO
//define the function using a closure
let printRand =
let rand = new Random()
// return the actual printing function
fun (tw:TextWriter) -> tw.Write(rand.Next(1,100))
//test it
for i in [1..5] do
printfn "rand = %t" printRand
For the %a specifier, the callback function takes an extra parameter. That is, when using
the %a specifier, you must pass in both a function and a value to format.
Here's an example of custom formatting a tuple:
open System
open System.IO
//define the callback function
//note that the data parameter comes after the TextWriter
let printLatLong (tw:TextWriter) (lat,long) =
tw.Write("lat:{0} long:{1}", lat, long)
// test it
let latLongs = [ (1,2); (3,4); (5,6)]
for latLong in latLongs do
// function and value both passed in to printfn
printfn "latLong = %a" printLatLong latLong
Date formatting
There are no special format specifiers for dates in F#.
If you want to format dates, you have a couple of options:
Use ToString to convert the date into a string, and then use the %s specifier
Use a custom callback function with the %a specifier as described above
Here are the two approaches in use:
454
C# equivalent
Comment
Console.Write and
Console.WriteLine
eprintf and
eprintfn
Console.Error.Write and
Console.Error.WriteLine
fprintf and
fprintfn
TextWriter.Write and
TextWriter.WriteLine
sprintf
String.Format
bprintf
StringBuilder.AppendFormat
No equivalent
kprintf , kfprintf ,
ksprintf and
kbprintf
All of these except bprintf and the kXXX family are automatically available (via
Microsoft.FSharp.Core.ExtraTopLevelOperators). But if you need to access them using a
module, they are in the Printf module.
455
The usage of these should be obvious (except for the kXXX family, of which more below).
A particularly useful technique is to use partial application to "bake in" a TextWriter or
StringBuilder.
Here is an example using a StringBuilder:
let printToSb s i =
let sb = new System.Text.StringBuilder()
// use partial application to fix the StringBuilder
let myPrint format = Printf.bprintf sb format
do myPrint "A string: %s. " s
do myPrint "An int: %i" i
//get the result
sb.ToString()
// test
printToSb "hello" 42
456
This stops the compiler complaining about an incorrect type. The reason why is nonobvious. We briefly mentioned the TextWriterFormat above as the first parameter to
printf . It turns out that printf is not actually a particular function, like String.Format ,
but rather a generic function that has to be parameterized with a TextWriterFormat (or the
similar StringFormat) in order to become "real".
So, to be safe, it is best to always pair a printf with a format parameter, rather than being
overly aggressive with the partial application.
457
open System
open System.IO
// a logging library such as log4net
// or System.Diagnostics.Trace
type Logger(name) =
let currentTime (tw:TextWriter) =
tw.Write("{0:s}",DateTime.Now)
let logEvent level msg =
printfn "%t %s [%s] %s" currentTime level name msg
member this.LogInfo msg =
logEvent "INFO" msg
member this.LogError msg =
logEvent "ERROR" msg
static member CreateLogger name =
new Logger(name)
458
// my application code
module MyApplication =
let logger = Logger.CreateLogger("MyApp")
// create a logInfo using the Logger class
let logInfo format =
let doAfter s =
logger.LogInfo(s)
Printf.ksprintf doAfter format
// create a logError using the Logger class
let logError format =
let doAfter s =
logger.LogError(s)
System.Windows.Forms.MessageBox.Show(s) |> ignore
Printf.ksprintf doAfter format
// function to exercise the logging
let test() =
do logInfo "Message #%i" 1
do logInfo "Message #%i" 2
do logError "Oops! an error occurred in my app"
Finally, when we run the test function, we should get the message written to the console,
and also see the popup message:
MyApplication.test()
You could also create an object-oriented version of the helper methods by creating a
"FormattingLogger" wrapper class around the logging library, as shown below.
459
type FormattingLogger(name) =
let logger = Logger.CreateLogger(name)
// create a logInfo using the Logger class
member this.logInfo format =
let doAfter s =
logger.LogInfo(s)
Printf.ksprintf doAfter format
// create a logError using the Logger class
member this.logError format =
let doAfter s =
logger.LogError(s)
System.Windows.Forms.MessageBox.Show(s) |> ignore
Printf.ksprintf doAfter format
static member createLogger name =
new FormattingLogger(name)
// my application code
module MyApplication2 =
let logger = FormattingLogger.createLogger("MyApp2")
let test() =
do logger.logInfo "Message #%i" 1
do logger.logInfo "Message #%i" 2
do logger.logError "Oops! an error occurred in app 2"
// test
MyApplication2.test()
The object-oriented approach, although more familiar, is not automatically better! The pros
and cons of OO methods vs. pure functions are discussed here.
460
Application design in F#
We've seen that a generic function takes input and emits output. But in a sense, that
approach applies at any level of functional code, even at the top level.
In fact, we can say that a functional application takes input, transforms it, and emits output:
Now ideally, the transformations work within the pure type-safe world that we create to
model the domain, but unfortunately, the real world is untyped! That is, the input is likely to
be simple strings or bytes, and the output also.
How can we work with this? The obvious solution is to have a separate stage to convert the
input to our pure internal model, and then another separate stage to convert from the
internal model to the output.
In this way, we can hide the messiness of the real world from the core of the application.
This "keep your model pure" approach is similar to the "Hexagonal Architecture" concept in
the large, or the MVC pattern in the small.
In this post and the next, we'll see some simple examples of this.
461
We talked about the match expression in general in the previous post, so let's look at a real
example where it is useful, namely parsing a command line.
We'll design and implement two slightly different versions, one with a basic internal model,
and second one with some improvements.
Requirements
Let's say that we have three commandline options: "verbose", "subdirectories", and
"orderby". "Verbose" and "subdirectories" are flags, while "orderby" has two choices: "by
size" and "by name".
So the command line params would look like
MYAPP [/V] [/S] [/O order]
/V verbose
/S include subdirectories
/O order by. Parameter is one of
N - order by name.
S - order by size
First version
Following the design rule above, we can see that:
the input will be an array (or list) of strings, one for each argument.
the internal model will be a set of types that model the (tiny) domain.
the output is out of scope in this example.
So we'll start by first creating the internal model of the parameters, and then look at how we
can parse the input into types used in the internal model.
Here's a first stab at the model:
// constants used later
let OrderByName = "N"
let OrderBySize = "S"
// set up a type to represent the options
type CommandLineOptions = {
verbose: bool;
subdirectories: bool;
orderby: string;
}
462
463
becomes empty, at which point we can exit the loop and return the optionsSoFar value as
the final result.
There are two special cases:
Matching the "orderBy" option creates a submatch pattern that looks at the first item in
the rest of the list and if not found, complains about a missing second parameter.
The very last match on the main match..with is not a wildcard, but a "bind to value".
Just like a wildcard, this will always succeed, but because we havd bound to the value,
it allows us to print the offending unmatched argument.
Note that for printing errors, we use eprintf rather than printf . This will write to
STDERR rather than STDOUT.
So now let's test this:
parseCommandLine ["/v"; "/s"]
Oops! That didn't work -- we need to pass in an initial optionsSoFar argument! Lets try
again:
// define the defaults to pass in
let defaultOptions = {
verbose = false;
subdirectories = false;
orderby = ByName
}
// test it
parseCommandLine ["/v"] defaultOptions
parseCommandLine ["/v"; "/s"] defaultOptions
parseCommandLine ["/o"; "S"] defaultOptions
464
This is a very common situation: you have a recursive function that takes a "accumulator"
parameter, but you don't want to be passing initial values all the time.
The answer is simple: just create another function that calls the recursive function with the
defaults.
Normally, this second one is the "public" one and the recursive one is hidden, so we will
rewrite the code as follows:
Rename parseCommandLine to parseCommandLineRec . There are other naming
conventions you could use as well, such as parseCommandLine' with a tick mark, or
innerParseCommandLine .
In this case the helper function can stand on its own. But if you really want to hide it, you can
put it as a nested subfunction in the defintion of parseCommandLine itself.
// create the "public" parse function
let parseCommandLine args =
// create the defaults
let defaultOptions =
// implementation as above
// inner recursive function
let rec parseCommandLineRec args optionsSoFar =
// implementation as above
// call the recursive one with the initial options
parseCommandLineRec args defaultOptions
465
In this case, I think it would just make things more complicated, so I have kept them
separate.
So, here is all the code at once, wrapped in a module:
module CommandLineV1 =
// constants used later
let OrderByName = "N"
let OrderBySize = "S"
// set up a type to represent the options
type CommandLineOptions = {
verbose: bool;
subdirectories: bool;
orderby: string;
}
// create the "helper" recursive function
let rec parseCommandLineRec args optionsSoFar =
match args with
// empty list means we're done.
| [] ->
optionsSoFar
// match verbose flag
| "/v"::xs ->
let newOptionsSoFar = { optionsSoFar with verbose=true}
parseCommandLineRec xs newOptionsSoFar
// match subdirectories flag
| "/s"::xs ->
let newOptionsSoFar = { optionsSoFar with subdirectories=true}
parseCommandLineRec xs newOptionsSoFar
// match orderBy by flag
| "/o"::xs ->
//start a submatch on the next arg
match xs with
| "S"::xss ->
let newOptionsSoFar = { optionsSoFar with orderby=OrderBySize}
parseCommandLineRec xss newOptionsSoFar
| "N"::xss ->
let newOptionsSoFar = { optionsSoFar with orderby=OrderByName}
parseCommandLineRec xss newOptionsSoFar
// handle unrecognized option and keep looping
| _ ->
eprintfn "OrderBy needs a second argument"
parseCommandLineRec xs optionsSoFar
466
// happy path
CommandLineV1.parseCommandLine ["/v"]
CommandLineV1.parseCommandLine ["/v"; "/s"]
CommandLineV1.parseCommandLine ["/o"; "S"]
// error handling
CommandLineV1.parseCommandLine ["/v"; "xyz"]
CommandLineV1.parseCommandLine ["/o"; "xyz"]
Second version
In our initial model we used bool and string to represent the possible values.
type CommandLineOptions = {
verbose: bool;
subdirectories: bool;
orderby: string;
}
467
myObject.SetUpComplicatedOptions(true,false,true,false,false);
Because the bool doesn't represent anything at the domain level, it is very easy to make
mistakes.
The solution to both these problems is to be as specific as possible when defining the
domain, typically by creating lots of very specific types.
So here's a new version of CommandLineOptions :
type OrderByOption = OrderBySize | OrderByName
type SubdirectoryOption = IncludeSubdirectories | ExcludeSubdirectories
type VerboseOption = VerboseOutput | TerseOutput
type CommandLineOptions = {
verbose: VerboseOption;
subdirectories: SubdirectoryOption;
orderby: OrderByOption
}
468
optionsSoFar
// match verbose flag
| "/v"::xs ->
let newOptionsSoFar = { optionsSoFar with verbose=VerboseOutput}
parseCommandLineRec xs newOptionsSoFar
// match subdirectories flag
| "/s"::xs ->
let newOptionsSoFar = { optionsSoFar with subdirectories=IncludeSubdirecto
ries}
parseCommandLineRec xs newOptionsSoFar
// match sort order flag
| "/o"::xs ->
//start a submatch on the next arg
match xs with
| "S"::xss ->
let newOptionsSoFar = { optionsSoFar with orderby=OrderBySize}
parseCommandLineRec xss newOptionsSoFar
| "N"::xss ->
let newOptionsSoFar = { optionsSoFar with orderby=OrderByName}
parseCommandLineRec xss newOptionsSoFar
// handle unrecognized option and keep looping
| _ ->
printfn "OrderBy needs a second argument"
parseCommandLineRec xs optionsSoFar
// handle unrecognized option and keep looping
| x::xs ->
printfn "Option '%s' is unrecognized" x
parseCommandLineRec xs optionsSoFar
// create the "public" parse function
let parseCommandLine args =
// create the defaults
let defaultOptions = {
verbose = TerseOutput;
subdirectories = ExcludeSubdirectories;
orderby = OrderByName
}
// call the recursive one with the initial options
parseCommandLineRec args defaultOptions
// ==============================
// tests
// happy path
CommandLineV2.parseCommandLine ["/v"]
CommandLineV2.parseCommandLine ["/v"; "/s"]
CommandLineV2.parseCommandLine ["/o"; "S"]
469
// error handling
CommandLineV2.parseCommandLine ["/v"; "xyz"]
CommandLineV2.parseCommandLine ["/o"; "xyz"]
470
| "/v" ->
let newOptionsSoFar = {optionsSoFar with verbose=VerboseOutput}
{options=newOptionsSoFar; parseMode=TopLevel}
// match subdirectories flag
| "/s"->
let newOptionsSoFar = { optionsSoFar with subdirectories=IncludeSubdirecto
ries}
{options=newOptionsSoFar; parseMode=TopLevel}
// match sort order flag
| "/o" ->
{options=optionsSoFar; parseMode=OrderBy}
// handle unrecognized option and keep looping
| x ->
printfn "Option '%s' is unrecognized" x
{options=optionsSoFar; parseMode=TopLevel}
// parse the orderBy arguments
// return a new FoldState
let parseOrderBy arg optionsSoFar =
match arg with
| "S" ->
let newOptionsSoFar = { optionsSoFar with orderby=OrderBySize}
{options=newOptionsSoFar; parseMode=TopLevel}
| "N" ->
let newOptionsSoFar = { optionsSoFar with orderby=OrderByName}
{options=newOptionsSoFar; parseMode=TopLevel}
// handle unrecognized option and keep looping
| _ ->
printfn "OrderBy needs a second argument"
{options=optionsSoFar; parseMode=TopLevel}
// create a helper fold function
let foldFunction state element =
match state with
| {options=optionsSoFar; parseMode=TopLevel} ->
// return new state
parseTopLevel element optionsSoFar
| {options=optionsSoFar; parseMode=OrderBy} ->
// return new state
parseOrderBy element optionsSoFar
// create the "public" parse function
let parseCommandLine args =
let defaultOptions = {
verbose = TerseOutput;
subdirectories = ExcludeSubdirectories;
orderby = OrderByName
}
471
let initialFoldState =
{options=defaultOptions; parseMode=TopLevel}
// call fold with the initial state
args |> List.fold foldFunction initialFoldState
// ==============================
// tests
// happy path
CommandLineV3.parseCommandLine ["/v"]
CommandLineV3.parseCommandLine ["/v"; "/s"]
CommandLineV3.parseCommandLine ["/o"; "S"]
// error handling
CommandLineV3.parseCommandLine ["/v"; "xyz"]
CommandLineV3.parseCommandLine ["/o"; "xyz"]
By the way, can you see a subtle change of behavior in this version?
In the previous versions, if there was no parameter to the "orderBy" option, the recursive
loop would still parse it next time. But in the 'fold' version, this token is swallowed and lost.
To see this, compare the two implementations:
// verbose set
CommandLineV2.parseCommandLine ["/o"; "/v"]
// verbose not set!
CommandLineV3.parseCommandLine ["/o"; "/v"]
To fix this would be even more work. Again this argues for the second implementation as the
easiest to debug and maintain.
Summary
In this post we've seen how to apply pattern matching to a real-world example.
More importantly, we've seen how easy it is to create a properly designed internal model for
even the smallest domain. And that this internal model provides more type safety and
documentation than using primitive types such as string and bool.
In the next example, we'll do even more pattern matching!
472
473
Requirements
Let's start with the requirements:
1) Accept a string of letters like "MMMXCLXXIV" as a string and convert it to an integ
er.
The conversions are: I=1; V=5; X=10; L=50; C=100; D=500; and M=1000;
If a lower letter comes before a higher one, the value of the higher is reduced accord
ingly, so
IV=4; IX=9; XC=90; and so on.
2) As an additional step, validate the string of letters to see if it is a valid numbe
r. For example: "IIVVMM" is a not a valid roman numeral.
First version
As before we'll start by first creating the internal model, and then look at how we can parse
the input into the internal model.
Here's a first stab at the model. We'll treat a RomanNumeral as a list of RomanDigits .
type RomanDigit = int
type RomanNumeral = RomanDigit list
No, stop right there! A RomanDigit is not just any digit, it has to be taken from a limited set.
474
Also RomanNumeral should not just be a type alias for a list of digits. It would be better if it
was its own special type as well. We can do this by creating a single case union type.
Here's a much better version:
type RomanDigit = I | V | X | L | C | D | M
type RomanNumeral = RomanNumeral of RomanDigit list
Note that we're using the function keyword instead of the match..with expression.
To convert a list of digits, we'll use a recursive loop again. There is a special case when we
need to look ahead to the next digit, and if it is larger than the current one, then use their
difference.
475
Note that the "less than" operation did not have to be defined. The types are automatically
sorted by their order of declaration.
Finally, we can convert the RomanNumeral type itself by unpacking the contents into a list and
calling digitsToInt .
/// converts a RomanNumeral to an integer
let toInt (RomanNumeral digits) = digitsToInt digits
// test
let x = RomanNumeral [I;I;I;I]
x |> toInt
let x = RomanNumeral [M;C;M;L;X;X;I;X]
x |> toInt
476
let charToRomanDigit =
function
| 'I' -> I
| 'V' -> V
| 'X' -> X
| 'L' -> L
| 'C' -> C
| 'D' -> D
| 'M' -> M
The compiler doesn't like that! What happens if we get some other character?
This is a great example of how exhaustive pattern matching can force you to think about
missing requirements.
So, what should be done for bad input. How about printing an error message?
Let's try again and add a case to handle all other characters:
let charToRomanDigit =
function
| 'I' -> I
| 'V' -> V
| 'X' -> X
| 'L' -> L
| 'C' -> C
| 'D' -> D
| 'M' -> M
| ch -> eprintf "%c is not a valid character" ch
The compiler doesn't like that either! The normal cases return a valid RomanDigit but the
error case returns unit . As we saw in the earlier post, every branch must return the same
type.
How can we fix this? We could throw an exception, but that seems a bit excessive. If we
think about it some more, there's no way that charToRomanDigit can always return a valid
RomanDigit . Sometimes it can, and sometimes it can't. In other words, we need to use
477
type ParsedChar =
| Digit of RomanDigit
| BadChar of char
let charToRomanDigit =
function
| 'I' -> Digit I
| 'V' -> Digit V
| 'X' -> Digit X
| 'L' -> Digit L
| 'C' -> Digit C
| 'D' -> Digit D
| 'M' -> Digit M
| ch -> BadChar ch
Note that I have removed the error message. Since the bad char is being returned, the caller
can print its own message for the BadChar case.
Next, we should check the function signature to make sure it is what we expect:
charToRomanDigit : char -> ParsedChar
But the compiler complains again with "FS0072: Lookup on object of indeterminate type",
That typically happens when you use a method rather than a function. Any object could
implement .ToCharArray() so the type inference cannot tell what type is meant.
In this case, the solution is just to use an explicit type annotation on the parameter -- our first
so far!
let toRomanDigitList (s:string) =
s.ToCharArray()
|> List.ofArray
|> List.map charToRomanDigit
478
It still has the pesky ParsedChar in it rather than RomanDigits . How do we want to proceed?
Answer, let's pass the buck again and let someone else deal with it!
"Passing the buck" in this case is actually a good design principle. This function doesn't
know what its clients might want to do -- some might want to ignore errors, while others
might want to fail fast. So just pass back the information and let them decide.
In this case, the client is the top level function that creates a RomanNumeral type. Here's our
first attempt:
// convert a string to a RomanNumeral
let toRomanNumeral s =
toRomanDigitList s
|> RomanNumeral
The compiler is not happy -- the RomanNumeral constructor requires a list of RomanDigits ,
but the toRomanDigitList is giving us a list of ParsedChars instead.
Now we finally do have to commit to an error handling policy. Let's choose to ignore bad
chars, but print out errors when they occur. We'll use the List.choose function for this. It's
similar to List.map , but in addition has a filter built into it. Elements that are valid ( Some
something ) are returned, but elements that are None are filtered out.
479
Let's test!
// test good cases
"IIII" |> toRomanNumeral
"IV" |> toRomanNumeral
"VI" |> toRomanNumeral
"IX" |> toRomanNumeral
"MCMLXXIX" |> toRomanNumeral
"MCMXLIV" |> toRomanNumeral
"" |> toRomanNumeral
// error cases
"MC?I" |> toRomanNumeral
"abc" |> toRomanNumeral
Validation rules
The validation rules were not listed in the requirements, so let's put down our best guess
based on what we know about Roman numerals:
Five in a row of any digit is not allowed
Some digits are allowed in runs of up to 4. They are I,X,C, and M. The others (V,L,D)
can only appear singly.
Some lower digits can come before a higher digit, but only if they appear singly. E.g.
"IX" is ok but "IIIX" is not.
But this is only for pairs of digits. Three ascending numbers in a row is invalid. E.g. "IX"
is ok but "IXC" is not.
A single digit with no runs is always allowed
480
481
Again, note that "equality" and "less than" did not need to be defined.
And let's test the validation:
// test valid
let validList = [
[I;I;I;I]
[I;V]
[I;X]
[I;X;V]
[V;X]
[X;I;V]
[X;I;X]
[X;X;I;I]
]
let testValid = validList |> List.map isValidDigitList
let invalidList = [
// Five in a row of any digit is not allowed
[I;I;I;I;I]
// Two in a row for V,L, D is not allowed
[V;V]
[L;L]
[D;D]
// runs of 2,3,4 in the middle are invalid if next digit is higher
[I;I;V]
[X;X;X;M]
[C;C;C;C;D]
// three ascending numbers in a row is invalid
[I;V;X]
[X;L;D]
]
let testInvalid = invalidList |> List.map isValidDigitList
Finally, we add a top level function to test validity of the RomanNumeral type itself.
482
483
| C -> 100
| D -> 500
| M -> 1000
/// converts a list of digits to an integer
let rec digitsToInt =
function
// empty is 0
| [] -> 0
// special case when a smaller comes before larger
// convert both digits and add the difference to the sum
// Example: "IV" and "CM"
| smaller::larger::ns when smaller < larger ->
(digitToInt larger - digitToInt smaller) + digitsToInt ns
// otherwise convert the digit and add to the sum
| digit::ns ->
digitToInt digit + digitsToInt ns
/// converts a RomanNumeral to an integer
let toInt (RomanNumeral digits) = digitsToInt digits
// ==========================================
// Input logic
// ==========================================
type ParsedChar =
| Digit of RomanDigit
| BadChar of char
let charToRomanDigit =
function
| 'I' -> Digit I
| 'V' -> Digit V
| 'X' -> Digit X
| 'L' -> Digit L
| 'C' -> Digit C
| 'D' -> Digit D
| 'M' -> Digit M
| ch -> BadChar ch
let toRomanDigitList (s:string) =
s.ToCharArray()
|> List.ofArray
|> List.map charToRomanDigit
/// Convert a string to a RomanNumeral
/// Does not validate the input.E.g. "IVIV" would be valid
let toRomanNumeral s =
toRomanDigitList s
|> List.choose (
484
function
| Digit digit ->
Some digit
| BadChar ch ->
eprintfn "%c is not a valid character" ch
None
)
|> RomanNumeral
// ==========================================
// Validation logic
// ==========================================
let runsAllowed =
function
| I | X | C | M -> true
| V | L | D -> false
let noRunsAllowed = runsAllowed >> not
// check for validity
let rec isValidDigitList digitList =
match digitList with
// empty list is valid
| [] -> true
// A run of 5 or more anything is invalid
// Example: XXXXX
| d1::d2::d3::d4::d5::_
when d1=d2 && d1=d3 && d1=d4 && d1=d5 ->
false
// 2 or more non-runnable digits is invalid
// Example: VV
| d1::d2::_
when d1=d2 && noRunsAllowed d1 ->
false
// runs of 2,3,4 in the middle are invalid if next digit is higher
// Example: IIIX
| d1::d2::d3::d4::higher::ds
when d1=d2 && d1=d3 && d1=d4
&& runsAllowed d1 // not really needed because of the order of matching
&& higher > d1 ->
false
| d1::d2::d3::higher::ds
when d1=d2 && d1=d3
&& runsAllowed d1
&& higher > d1 ->
false
485
| d1::d2::higher::ds
when d1=d2
&& runsAllowed d1
&& higher > d1 ->
false
// three ascending numbers in a row is invalid
// Example: IVX
| d1::d2::d3::_ when d1<d2 && d2<= d3 ->
false
// A single digit with no runs is always allowed
| _::ds ->
// check the remainder of the list
isValidDigitList ds
// top level check for validity
let isValid (RomanNumeral digitList) =
isValidDigitList digitList
Second version
The code works, but there is something that's bugging me about it. The validation logic
seems very complicated. Surely the Romans didn't have to think about all of this?
And also, I can think of examples that should fail validation, but pass, such as "VIV":
"VIV" |> toRomanNumeral |> isValid
We could try to tighten up our validation rules, but let's try another tack. Complicated logic is
often a sign that you don't quite understand the domain properly.
In other words -- could we change the internal model to make everything simpler?
What about if we stopped trying to map letters to digits, and created a domain that mapped
how the Romans thought it. In this model "I", "II", "III", "IV" and so on would each be a
separate digit.
Let's run with it and see what happens.
Here's the new types for the domain. I now have a digit type for every possible digit. The
RomanNumeral type stays the same.
486
type RomanDigit =
| I | II | III | IIII
| IV | V
| IX | X | XX | XXX | XXXX
| XL | L
| XC | C | CC | CCC | CCCC
| CD | D
| CM | M | MM | MMM | MMMM
type RomanNumeral = RomanNumeral of RomanDigit list
Calculating the sum of the digits is now trivial. No special cases needed:
/// converts a list of digits to an integer
let digitsToInt list =
list |> List.sumBy digitToInt
// tests
[IIII] |> digitsToInt
[IV] |> digitsToInt
[V;I] |> digitsToInt
[IX] |> digitsToInt
[M;CM;L;X;X;IX] |> digitsToInt // 1979
[M;CM;XL;IV] |> digitsToInt // 1944
487
488
| 'C'::'C'::ns ->
Digit CC :: (toRomanDigitListRec ns)
| 'M'::'M'::ns ->
Digit MM :: (toRomanDigitListRec ns)
| 'I'::'V'::ns ->
Digit IV :: (toRomanDigitListRec ns)
| 'I'::'X'::ns ->
Digit IX :: (toRomanDigitListRec ns)
| 'X'::'L'::ns ->
Digit XL :: (toRomanDigitListRec ns)
| 'X'::'C'::ns ->
Digit XC :: (toRomanDigitListRec ns)
| 'C'::'D'::ns ->
Digit CD :: (toRomanDigitListRec ns)
| 'C'::'M'::ns ->
Digit CM :: (toRomanDigitListRec ns)
// 1 letter matches
| 'I'::ns ->
Digit I :: (toRomanDigitListRec ns)
| 'V'::ns ->
Digit V :: (toRomanDigitListRec ns)
| 'X'::ns ->
Digit X :: (toRomanDigitListRec ns)
| 'L'::ns ->
Digit L :: (toRomanDigitListRec ns)
| 'C'::ns ->
Digit C :: (toRomanDigitListRec ns)
| 'D'::ns ->
Digit D :: (toRomanDigitListRec ns)
| 'M'::ns ->
Digit M :: (toRomanDigitListRec ns)
// bad letter matches
| badChar::ns ->
BadChar badChar :: (toRomanDigitListRec ns)
// 0 letter matches
| [] ->
[]
Well, this is much longer than the first version, but otherwise basically the same.
The top level functions are unchanged.
489
490
Alas, after all that, we still didn't fix the bad case that triggered the rewrite!
"VIV" |> toRomanNumeral |> isValid
There is a not-too-complicated fix for this, but I think it's time to leave it alone now!
491
| I | II | III | IIII
| IV | V
| IX | X | XX | XXX | XXXX
| XL | L
| XC | C | CC | CCC | CCCC
| CD | D
| CM | M | MM | MMM | MMMM
type RomanNumeral = RomanNumeral of RomanDigit list
// ==========================================
// Output logic
// ==========================================
/// Converts a single RomanDigit to an integer
let digitToInt =
function
| I -> 1 | II -> 2 | III -> 3 | IIII -> 4
| IV -> 4 | V -> 5
| IX -> 9 | X -> 10 | XX -> 20 | XXX -> 30 | XXXX -> 40
| XL -> 40 | L -> 50
| XC -> 90 | C -> 100 | CC -> 200 | CCC -> 300 | CCCC -> 400
| CD -> 400 | D -> 500
| CM -> 900 | M -> 1000 | MM -> 2000 | MMM -> 3000 | MMMM -> 4000
/// converts a RomanNumeral to an integer
let toInt (RomanNumeral digits) = digitsToInt digits
// ==========================================
// Input logic
// ==========================================
type ParsedChar =
| Digit of RomanDigit
| BadChar of char
let rec toRomanDigitListRec charList =
match charList with
// match the longest patterns first
// 4 letter matches
| 'I'::'I'::'I'::'I'::ns ->
Digit IIII :: (toRomanDigitListRec ns)
| 'X'::'X'::'X'::'X'::ns ->
Digit XXXX :: (toRomanDigitListRec ns)
| 'C'::'C'::'C'::'C'::ns ->
Digit CCCC :: (toRomanDigitListRec ns)
| 'M'::'M'::'M'::'M'::ns ->
Digit MMMM :: (toRomanDigitListRec ns)
// 3 letter matches
| 'I'::'I'::'I'::ns ->
Digit III :: (toRomanDigitListRec ns)
| 'X'::'X'::'X'::ns ->
492
493
494
both versions.
Overall, I prefer the second implementation because of the lack of special cases.
As a fun experiment, try writing the same code in C# or your favorite imperative language!
Making it object-oriented
Finally, let's see how we might make this object oriented. We don't care about the helper
functions, so we probably just need three methods:
A static constructor
A method to convert to a int
A method to convert to a string
And here they are:
type RomanNumeral with
static member FromString s =
toRomanNumeral s
member this.ToInt() =
toInt this
override this.ToString() =
sprintf "%A" this
Note: you can ignore the compiler warning about deprecated overrides.
Let's use this in an object oriented way now:
let r = RomanNumeral.FromString "XXIV"
let s = r.ToString()
let i = r.ToInt()
Summary
In this post we've seen lots and lots of pattern matching!
But again, as with the last post, what's equally important is that we've seen how easy it is to
create a properly designed internal model for very trivial domains. And again, our internal
model used no primitive types -- there is no excuse not to create lots of little types in order to
495
represent the domain better. For example, the ParsedChar type -- would you have bothered
to create that in C#?
And as should be clear, the choice of an internal model can make a lot of difference to the
complexity of the design. But if and when we do refactor, the compiler will almost always
warn us if we have forgotten something.
496
F# is not just about functions; the powerful type system is another key ingredient. And just
as with functions, understanding the type system is critical to being fluent and comfortable in
the language.
In addition to the common .NET types. F# has some other types that are very common in
functional languages but not available in imperative languages like C# or Java.
This series introduces these types and how to use them.
Understanding F# types: Introduction. A new world of types.
Overview of types in F#. A look at the big picture.
Type abbreviations. Also known as aliases.
Tuples. Multiplying types together.
Records. Extending tuples with labels.
Discriminated Unions. Adding types together.
The Option type. And why it is not null or nullable.
Enum types. Not the same as a union type.
Built-in .NET types. Ints, strings, bools, etc.
Units of measure. Type safety for numerics.
Understanding type inference. Behind the magic curtain.
497
498
Overview of types in F#
Overview of types in F#
Before we dive into all the specific types, let's look at the big picture.
499
Overview of types in F#
500
Overview of types in F#
That is, in F# you can define new types almost as if you were doing algebra:
define typeZ = typeX "plus" typeY
define typeW = typeX "times" typeZ
I will hold off explaining what sum and product mean in practice until we get to the detailed
discussion of tuples (products) and discriminated union (sum) types later in this series.
The key point is that an infinite number of new types can be made by combining existing
types together using these "product" and "sum" methods in various ways. Collectively these
are called "algebraic data types" or ADTs (not to be confused with abstract data types, also
called ADTs). Algebraic data types can be used to model anything, including lists, trees, and
other recursive types.
The sum or "union" types, in particular, are very valuable, and once you get used to them,
you will find them indispensible!
As we said in a previous post, there is a special syntax for defining new types that is
different from the normal expression syntax. So do be aware of this difference.
Types can only be declared in namespaces or modules. But that doesn't mean you always
have to create them at the top level -- you can create types in nested modules if you need to
hide them.
501
Overview of types in F#
module sub =
// type declared in a module
type A = int * int
module private helper =
// type declared in a submodule
type B = B of string list
//internal access is allowed
let b = B ["a";"b"]
//outside access not allowed
let b = sub.helper.B ["a";"b"]
What is interesting is that the same "constructor" syntax is also used to "deconstruct" the
type when doing pattern matching:
502
Overview of types in F#
As you read through this series, pay attention to how the constructors are used in both ways.
Abbrev
(Alias)
Tuple
Example
Distinguishing
features
Always available to be
used and are not
explicitly defined with
the type keyword.
Usage indicated by
comma (with optional
parentheses).
503
Overview of types in F#
Record
type Product =
{code:ProductCode;
price:float }
type Message<'a> = {id:int;
body:'a}
Curly braces.
Uses semicolon to
separate fields.
//usage
let p = {code="X123";
price=9.99}
let m = {id=1;
body="hello"}
Discriminated
Union
Enum
type MeasurementUnit = Cm |
Inch | Mile
type Name =
| Nickname of string
| FirstLast of string *
string
type Tree<'a> =
| E
| T of Tree<'a> * 'a *
Tree<'a>
//usage
let u = Inch
let name = Nickname("John")
let t = T(E,"John",E)
Has function-style
parameter list after
name for use as
504
Overview of types in F#
Class
Has "member"
keyword.
Has "new" keyword for
secondary
constructors.
//usage
let p =
Product("X123",9.99)
let p2 = Product("X123")
Interface
Struct
type IPrintable =
abstract member Print :
unit -> unit
type Product=
struct
val code:string
val price:float
new(code) = { code =
code; price = 0.0 }
end
//usage
let p = Product()
let p2 = Product("X123")
505
Type abbreviations
Type abbreviations
Let's start with the simplest type definition, a type abbreviation or alias.
It has the form:
type [typename] = [existingType]
where "existing type" can be any type: one of the basic types we have already seen, or one
of the extended types we will be seeing soon.
Some examples:
type RealNumber = float
type ComplexNumber = float * float
type ProductCode = string
type CustomerId = int
type AdditionFunction = int->int->int
type ComplexAdditionFunction =
ComplexNumber-> ComplexNumber -> ComplexNumber
the compiler will erase the alias and return a plain int->int->int as the function signature.
506
Type abbreviations
In particular, there is no true encapsulation. I could use an explicit int anywhere I used a
CustomerId and the compiler would not complain. And if I had attempted to create safe
we will need to use single case union types, as described in a later post.
507
Tuples
Tuples
We're ready for our first extended type -- the tuple.
Let's start by stepping back again and looking at a type such as "int". As we hinted at before,
rather than thinking of "int" as a abstract thing, you can think of it as concrete collection of all
its possible values, namely the set {...,-3, -2, -1, 0, 2, 3, ...}.
So next, imagine two copies of this "int" collection. We can "multiply" them together by taking
the Cartesian product of them; that is, making a new list of objects by picking every possible
combination of the two "int" lists, as shown below:
As we have already seen, these pairs are called tuples in F#. And now you can see why
they have the type signature that they do. In this example, the "int times int" type is called
" int * int ", and the star symbol means "multiply" of course! The valid instances of this
new type are all the pairs: (-2,2),(-1,0), (2,2) and so on.
Let's see how they might be used in practice:
let t1 = (2,3)
let t2 = (-2,7)
Now if you evaluate the code above you will see that the types of t1 and t2 are int*int as
expected.
val t1 : int * int = (2, 3)
val t2 : int * int = (-2, 7)
508
Tuples
This "product" approach can be used to make tuples out of any mixture of types. Here is one
for "int times bool".
And here is the usage in F#. The tuple type above has the signature " int*bool ".
let t3 = (2,true)
let t4 = (7,false)
// the signatures are:
val t3 : int * bool = (2, true)
val t4 : int * bool = (7, false)
Strings can be used as well, of course. The universe of all possible strings is very large, but
conceptually it is the same thing. The tuple type below has the signature " string*int ".
509
Tuples
let t5 = ("hello",42)
let t6 = ("goodbye",99)
// the signatures are:
val t5 : string * int = ("hello", 42)
val t6 : string * int = ("goodbye", 99)
And there is no reason to stop at multiplying just two types together. Why not three? Or four?
For example, here is the type int * bool * string .
Generic tuples
Generics can be used in tuples too.
510
Tuples
which means that " genericTupleFn " takes a generic tuple ('a * 'b) and returns a unit
511
Tuples
When pattern matching like this, you must have the same number of elements, otherwise
you will get an error:
let z1,z2 = z // error FS0001: Type mismatch.
// The tuples have differing lengths
If you don't need some of the values, you can use the "don't care" symbol (the underscore)
as a placeholder.
let _,z5,_,z6 = z // ignore 1st and 3rd elements
As you might guess, a two element tuple is commonly called a "pair" and a three element
tuple is called a "triple" and so on. In the special case of pairs, there are functions fst and
snd which extract the first and second element.
let x = 1,2
fst x
snd x
They only work on pairs. Trying to use fst on a triple will give an error.
512
Tuples
let x = 1,2,3
fst x // error FS0001: Type mismatch.
// The tuples have differing lengths of 2 and 3
513
Tuples
As with most F# values, tuples are immutable and the elements within them cannot be
assigned to. So how do you change a tuple? The short answer is that you can't -- you must
always create a new one.
Say that you need to write a function that, given a tuple, adds one to each element. Here's
an obvious implementation:
let addOneToTuple aTuple =
let (x,y,z) = aTuple
(x+1,y+1,z+1) // create a new one
// try it
addOneToTuple (1,2,3)
This seems a bit long winded -- is there a more compact way? Yes, because you can
deconstruct a tuple directly in the parameters of a function, so that the function becomes a
one liner:
let addOneToTuple (x,y,z) = (x+1,y+1,z+1)
// try it
addOneToTuple (1,2,3)
Equality
Tuples have an automatically defined equality operation: two tuples are equal if they have
the same length and the values in each slot are equal.
(1,2) = (1,2) // true
(1,2,3,"hello") = (1,2,3,"bye") // false
(1,(2,3),4) = (1,(2,3),4) // true
514
Tuples
Tuples also have an automatically defined hash value based on the values in the tuple, so
that tuples can be used as dictionary keys without problems.
(1,2,3).GetHashCode()
Tuple representation
And as noted in a previous post, tuples have a nice default string representation, and can be
serialized easily.
(1,2,3).ToString()
515
Records
Records
As we noted in the previous post, plain tuples are useful in many cases. But they have some
disadvantages too. Because all tuple types are pre-defined, you can't distinguish between a
pair of floats used for geographic coordinates say, vs. a similar tuple used for complex
numbers. And when tuples have more than a few elements, it is easy to get confused about
which element is in which place.
In these situations, what you would like to do is label each slot in the tuple, which will both
document what each element is for and force a distinction between tuples made from the
same types.
Enter the "record" type. A record type is exactly that, a tuple where each element is labeled.
type ComplexNumber = { real: float; imaginary: float }
type GeoCoord = { lat: float; long: float }
A record type has the standard preamble: type [typename] = followed by curly braces.
Inside the curly braces is a list of label: type pairs, separated by semicolons (remember,
all lists in F# use semicolon separators -- commas are for tuples).
Let's compare the "type syntax" for a record type with a tuple type:
type ComplexNumberRecord = { real: float; imaginary: float }
type ComplexNumberTuple = float * float
516
Records
As always, if you don't need some of the values, you can use the underscore as a
placeholder; or more cleanly, just leave off the unwanted label altogether.
let { lat=_; long=myLong2 } = myGeoCoord // "deconstruct"
let { long=myLong3 } = myGeoCoord // "deconstruct"
If you just need a single property, you can use dot notation rather than pattern matching.
let x = myGeoCoord.lat
let y = myGeoCoord.long
Note that you can leave a label off when deconstructing, but not when constructing:
let myGeoCoord = { lat = 1.1; } // error FS0764: No assignment
// given for field 'long'
One of the most noticeable features of record types is use of curly braces. Unlike Cstyle languages, curly braces are rarely used in F# -- only for records, sequences,
computation expressions (of which sequences are a special case), and object
expressions (creating implementations of interfaces on the fly). These other uses will be
discussed later.
Label order
Unlike tuples, the order of the labels is not important. So the following two values are the
same:
517
Records
Naming conflicts
In the examples above, we could construct a record by just using the label names " lat "
and " long ". Magically, the compiler knew what record type to create. (Well, in truth, it was
not really that magical, as only one record type had those exact labels.)
But what happens if there are two record types with the same labels? How can the compiler
know which one you mean? The answer is that it can't -- it will use the most recently defined
type, and in some cases, issue a warning. Try evaluating the following:
type Person1 = {first:string; last:string}
type Person2 = {first:string; last:string}
let p = {first="Alice"; last="Jones"}
What type is p ? Answer: Person2 , which was the last type defined with those labels.
And if you try to deconstruct, you will get a warning about ambiguous field labels.
let {first=f; last=l} = p
How can you fix this? Simply by adding the type name as a qualifier to at least one of the
labels.
let p = {Person1.first="Alice"; last="Jones"}
let { Person1.first=f; last=l} = p
If needed, you can even add a fully qualified name (with namespace). Here's an example
using modules.
module Module1 =
type Person = {first:string; last:string}
module Module2 =
type Person = {first:string; last:string}
module Module3 =
let p = {Module1.Person.first="Alice";
Module1.Person.last="Jones"}
518
Records
Of course, if you can ensure there is only one version in the local namespace, you can avoid
having to do this at all.
module Module3b =
open Module1 // bring into the local namespace
let p = {first="Alice"; last="Jones"} // will be Module1.Person
The moral of the story is that when defining record types, you should try to use unique labels
if possible, otherwise you will get ugly code at best, and unexpected behavior at worst.
Note that in F#, unlike some other functional languages, two types with exactly the same
structural definition are not the same type. This is called a "nominal" type system, where
two types are only equal if they have the same name, as opposed to a "structural" type
system, where definitions with identical structures will be the same type regardless of
what they are called.
519
Records
You can see that having explicit labels in the return value makes it much easier to
understand (of course, in practice we would probably use an Option type, discussed later).
And here's the word and letter count example using records rather than tuples:
//define return type
type WordAndLetterCountResult = {wordCount:int; letterCount:int}
let wordAndLetterCount (s:string) =
let words = s.Split [|' '|]
let letterCount = words |> Array.sumBy (fun word -> word.Length )
{wordCount=words.Length; letterCount=letterCount}
//test
wordAndLetterCount "to be or not to be"
520
Records
But again you can simplify by deconstructing directly in the parameters of a function, so that
the function becomes a one liner:
let addOneToGeoCoord {lat=x; long=y} = {lat=x+1.0; long=y+1.0}
// try it
addOneToGeoCoord {lat=1.0; long=2.0}
or depending on your taste, you can also use dot notation to get the properties:
let addOneToGeoCoord aGeoCoord =
{lat=aGeoCoord.lat + 1.0; long= aGeoCoord.long + 1.0}
In many cases, you just need to tweak one or two fields and leave all the others alone. To
make life easier, there is a special syntax for this common case, the " with " keyword. You
start with the original value, followed by "with" and then the fields you want to change. Here
are some examples:
let g1 = {lat=1.1; long=2.2}
let g2 = {g1 with lat=99.9} // create a new one
let p1 = {first="Alice"; last="Jones"}
let p2 = {p1 with last="Smith"}
Record equality
Like tuples, records have an automatically defined equality operation: two records are equal
if they have the same type and the values in each slot are equal.
And records also have an automatically defined hash value based on the values in the
record, so that records can be used as dictionary keys without problems.
{first="Alice"; last="Jones"}.GetHashCode()
521
Records
Record representation
As noted in a previous post, records have a nice default string representation, and can be
serialized easily. But unlike tuples, the ToString() representation is unhelpful.
printfn "%A" {first="Alice"; last="Jones"} // nice
{first="Alice"; last="Jones"}.ToString() // ugly
printfn "%O" {first="Alice"; last="Jones"} // ugly
uses Object.ToString() , which means that if the ToString method is not overridden, %O
will give the default (and generally unhelpful) output. So in general, you should try to use
%A to %O where possible, because the core F# types do have pretty-printing by default.
But note that the F# "class" types do not have a standard pretty printed format, so %A and
%O are equally uncooperative unless you override ToString .
522
Discriminated Unions
Discriminated Unions
Tuples and records are examples of creating new types by "multiplying" existing types
together. At the beginning of the series, I mentioned that the other way of creating new types
was by "summing" existing types. What does this mean?
Well, let's say that we want to define a function that works with integers OR booleans,
maybe to convert them into strings. But we want to be strict and not accept any other type
(such as floats or strings). Here's a diagram of such as function:
In other words, a "sum" type. In this case the new type is the "sum" of the integer type plus
the boolean type.
523
Discriminated Unions
In F#, a sum type is called a "discriminated union" type. Each component type (called a
union case) must be tagged with a label (called a case identifier or tag) so that they can be
told apart ("discriminated"). The labels can be any identifier you like, but must start with an
uppercase letter.
Here's how we might define the type above:
type IntOrBool =
| I of int
| B of bool
The "I" and the "B" are just arbitrary labels; we could have used any other labels that were
meaningful.
For small types, we can put the definition on one line:
type IntOrBool = I of int | B of bool
The component types can be any other type you like, including tuples, records, other union
types, and so on.
type Person = {first:string; last:string} // define a record type
type IntOrBool = I of int | B of bool
type MixedType =
| Tup of int * int // a tuple
| P of Person // use the record type defined above
| L of int list // a list of ints
| U of IntOrBool // use the union type defined above
You can even have types that are recursive, that is, they refer to themselves. This is typically
how tree structures are defined. Recursive types will be discussed in more detail shortly.
Discriminated Unions
The tags or labels must start with an uppercase letter. So the following will give an error:
type IntOrBool = int of int| bool of bool
// error FS0053: Discriminated union cases
// must be uppercase identifiers
Other named types (such as Person or IntOrBool ) must be pre-defined outside the
union type. You can't define them "inline" and write something like this:
type MixedType =
| P of {first:string; last:string} // error
or
type MixedType =
| U of (I of int | B of bool) // error
The labels can be any identifier, including the names of the component type
themselves, which can be quite confusing if you are not expecting it. For example, if the
Int32 and Boolean types (from the System namespace) were used instead, and the
labels were named the same, we would have this perfectly valid definition:
open System
type IntOrBool = Int32 of Int32 | Boolean of Boolean
This "duplicate naming" style is actually quite common, because it documents exactly what
the component types are.
525
Discriminated Unions
To create a value of a union type, you use a "constructor" that refers to only one of the
possible union cases. The constructor then follows the form of the definition, using the case
label as if it were a function. In the IntOrBool example, you would write:
type IntOrBool = I of int | B of bool
let i = I 99 // use the "I" constructor
// val i : IntOrBool = I 99
let b = B true // use the "B" constructor
// val b : IntOrBool = B true
The resulting value is printed out with the label along with the component type:
val [value name] : [type] = [label] [print of component type]
val i : IntOrBool = I 99
val b : IntOrBool = B true
If the case constructor has more than one "parameter", you construct it in the same way that
you would call a function:
type Person = {first:string; last:string}
type MixedType =
| Tup of int * int
| P of Person
let myTup = Tup (2,99) // use the "Tup" constructor
// val myTup : MixedType = Tup (2,99)
let myP = P {first="Al"; last="Jones"} // use the "P" constructor
// val myP : MixedType = P {first = "Al";last = "Jones";}
The case constructors for union types are normal functions, so you can use them anywhere
a function is expected. For example, in List.map :
type C = Circle of int | Rectangle of int * int
[1..10]
|> List.map Circle
[1..10]
|> List.zip [21..30]
|> List.map Rectangle
526
Discriminated Unions
Naming conflicts
If a particular case has a unique name, then the type to construct will be unambiguous.
But what happens if you have two types which have cases with the same labels?
type IntOrBool1 = I of int | B of bool
type IntOrBool2 = I of int | B of bool
And if the types come from different modules, you can use the module name as well:
module Module1 =
type IntOrBool = I of int | B of bool
module Module2 =
type IntOrBool = I of int | B of bool
module Module3 =
let x = Module1.IntOrBool.I 99 // val x : Module1.IntOrBool = I 99
527
Discriminated Unions
Empty cases
The label for a union case does not have to have to have any type after it. The following are
all valid union types:
type Directory =
| Root // no need to name the root
| Subdirectory of string // other directories need to be named
type Result =
| Success // no string needed for success state
| ErrorMessage of string // error message needed
If all the cases are empty, then we have an "enum style" union:
type Size = Small | Medium | Large
type Answer = Yes | No | Maybe
528
Discriminated Unions
Note that this "enum style" union is not the same as a true C# enum type, discussed later.
To create an empty case, just use the label as a constructor without any parameters:
let myDir1 = Root
let myDir2 = Subdirectory "bin"
let myResult1 = Success
let myResult2 = ErrorMessage "not found"
let mySize1 = Small
let mySize2 = Medium
Single cases
Sometimes it is useful to create union types with only one case. This might be seem
useless, because you don't seem to be adding value. But in fact, this a very useful practice
that can enforce type safety*.
* And in a future series we'll see that, in conjuction with module signatures, single case
unions can also help with data hiding and capability based security.
For example, let's say that we have customer ids and order ids which are both represented
by integers, but that they should never be assigned to each other.
As we saw before, a type alias approach will not work, because an alias is just a synonym
and doesn't create a distinct type. Here's how you might try to do it with aliases:
type CustomerId = int // define a type alias
type OrderId = int // define another type alias
let printOrderId (orderId:OrderId) =
printfn "The orderId is %i" orderId
//try it
let custId = 1 // create a customer id
printOrderId custId // Uh-oh!
But even though I explicitly annotated the orderId parameter to be of type OrderId , I can't
ensure that customer ids are not accidentally passed in.
On the other hand, if we create simple union types, we can easily enforce the type
distinctions.
529
Discriminated Unions
This approach is feasible in C# and Java as well, but is rarely used because of the overhead
of creating and managing the special classes for each type. In F# this approach is
lightweight and therefore quite common.
A convenient thing about single case union types is you can pattern match directly against a
value without having to use a full match-with expression.
// deconstruct in the param
let printCustomerId (CustomerId customerIdInt) =
printfn "The CustomerId is %i" customerIdInt
// or deconstruct explicitly through let statement
let printCustomerId2 custId =
let (CustomerId customerIdInt) = custId // deconstruct here
printfn "The CustomerId is %i" customerIdInt
// try it
let custId = CustomerId 1 // create a customer id
printCustomerId custId
printCustomerId2 custId
But a common "gotcha" is that in some cases, the pattern match must have parens around
it, otherwise the compiler will think you are defining a function!
let custId = CustomerId 1
let (CustomerId customerIdInt) = custId // Correct pattern matching
let CustomerId customerIdInt = custId // Wrong! New function?
Similarly, if you ever do need to create an enum-style union type with a single case, you will
have to start the case with a vertical bar in the type definition; otherwise the compiler will
think you are creating an alias.
type TypeAlias = A // type alias!
type SingleCase = | A // single case union type
530
Discriminated Unions
Union equality
Like other core F# types, union types have an automatically defined equality operation: two
unions are equal if they have the same type and the same case and the values for that case
is equal.
type Contact = Email of string | Phone of int
let email1 = Email "bob@example.com"
let email2 = Email "bob@example.com"
let areEqual = (email1=email2)
Union representation
Union types have a nice default string representation, and can be serialized easily. But
unlike tuples, the ToString() representation is unhelpful.
type Contact = Email of string | Phone of int
let email = Email "bob@example.com"
printfn "%A" email // nice
printfn "%O" email // ugly!
531
Here is a definition:
type Option<'a> = // use a generic definition
| Some of 'a // valid value
| None // missing
IMPORTANT: if you evaluate this in the interactive window, be sure to reset the session
afterwards, so that the built-in type is restored.
The option type is used in the same way as any union type in construction, by specifying one
of the two cases, the Some case or the None case:
532
and when pattern matching, as with any union type, you must always match all the cases:
match validInt with
| Some x -> printfn "the valid value is %A" x
| None -> printfn "the value is None"
When defining a type that references the Option type, you must specify the generic type to
use. You can do this in an explicit way, with angle brackets, or use the built-in " option "
keyword which comes after the type. The following examples are identical:
type SearchResult1 = Option<string> // Explicit C#-style generics
type SearchResult2 = string option // built-in postfix keyword
Let's revisit the same example we used for tuples and records, and see how options might
be used instead:
533
Of these three approaches, the "option" version is generally preferred; no new types need to
be defined and for simple cases, the meaning of None is obvious from the context.
NOTE: The tryParseOption code is just an example. A similar function tryParse is built
into the .NET core libraries and should be used instead.
Option equality
Like other union types, option types have an automatically defined equality operation
let o1 = Some 42
let o2 = Some 42
let areEqual = (o1=o2)
534
Option representation
Option types have a nice default string representation, and unlike other union types, the
ToString() representation is also nice.
let o = Some 42
printfn "%A" o // nice
printfn "%O" o // nice
535
let x = Some 99
match x with
| Some i -> printfn "x is %i" i
| None -> () // what to do here?
The pattern matching approach also forces you to think about and document what happens
in the None case, which you might easily overlook when using IsSome .
Or perhaps I want to multiply the value of an option by 2 if it is valid but return 0 if it is None .
Here's the pattern matching way:
let x = Some 99
let result = match x with
| Some i -> i * 2
| None -> 0
In simple cases like the one above, the defaultArg function can be used as well.
536
let x = Some 99
defaultArg x 0
This compiles perfectly, of course. The compiler cannot tell the difference between the two
variables. The null is exactly the same type as the valid string, so all the System.String
methods and properties can be used on it, including the Length property.
Now, we know that this code will fail by just looking at it, but the compiler can't help us.
Instead, as we all know, you have to tediously test for nulls constantly.
Now let's look at the nearest F# equivalent of the C# example above. In F#, to indicate
missing data, you would use an option type and set it to None . (In this artificial example we
have to use an ugly explicitly typed None -- normally this would not be necessary.)
let s1 = "abc"
var len1 = s1.Length
// create a string option with value None
let s2 = Option<string>.None
let len2 = s2.Length
537
In the F# version, we get a compile-time error immediately. The None is not a string, it's a
different type altogether, so you can't call Length on it directly. And to be clear, Some
[string] is also not the same type as string , so you can't call Length on it either!
So if Option<string> is not a string, but you want to do something with the string it (might)
contain, you are forced to have to pattern match on it (assuming you don't do bad things as
described earlier).
let s2 = Option<string>.None
//which one is it?
let len2 = match s2 with
| Some s -> s.Length
| None -> 0
You always have to pattern match, because given a value of type Option<string> , you can't
tell whether it is Some or None.
In just the same way Option<int> is not the same type as int , Option<bool> is not the
same type as bool , and so on.
To summarize the critical points:
The type " string option " is not at all the same type as " string ". You cannot cast
from string option to string -- they do not have the same properties. A function that
works with string will not work with string option , and vice versa. So the type
system will prevent any errors.
On the other hand, a "null string" in C# is exactly the same type as "string". You cannot
tell them apart at compile time, only at run time. A "null string" appears to have all the
same properties and functions as a valid string, except that your code will blow up when
you try to use it!
538
How can that value ever be uninitialized, or become null, or even become any other value at
all?
Unfortunately, additional confusion has been caused because in some cases API designers
have used null to indicate the concept of "missing" data as well! For example, the .NET
library method StreamReader.ReadLine returns null to indicate that there is no more data in a
file.
F# and null
F# is not a pure functional language, and has to interact with the .NET languages that do
have the concept of null. Therefore, F# does include a null keyword in its design, but
makes it hard to use and treats it as an abnormal value.
As a general rule, nulls are never created in "pure" F#, but only by interacting with the .NET
libraries or other external systems.
Here are some examples:
// pure F# type is not allowed to be null (in general)
type Person = {first:string; last:string}
let p : Person = null // error!
// type defined in CLR, so is allowed to be null
let s : string = null // no error!
let line = streamReader.ReadLine() // no error if null
In these cases, it is good practice to immediately check for nulls and convert them into an
option type!
// streamReader example
let line = match streamReader.ReadLine() with
| null -> None
| line -> Some line
// environment example
let GetEnvVar var =
match System.Environment.GetEnvironmentVariable(var) with
| null -> None
| value -> Some value
// try it
GetEnvVar "PATH"
GetEnvVar "TEST"
539
And on occasion, you may need to pass a null to an external library. You can do this using
the null keyword as well.
540
Enum types
Enum types
The enum type in F# is the same as the enum type in C#. Its definition is superficially just
like that of a union type, but there are many non-obvious differences to be aware of.
Defining enums
To define an enum you use exactly the same syntax as a union type with empty cases,
except that you must specify a constant value for each case, and the constants must all be
of the same type.
type SizeUnion = Small | Medium | Large // union
type ColorEnum = Red=0 | Yellow=1 | Blue=2 // enum
Strings are not allowed, only ints or compatible types such bytes and chars:
type MyEnum = Yes = "Y" | No ="N" // Error. Strings not allowed.
type MyEnum = Yes = 'Y' | No ='N' // Ok because char was used.
Union types require that their cases start with an uppercase letter. This is not required for
enums.
type SizeUnion = Small | Medium | large // Error - "large" is invalid.
type ColorEnum = Red=0 | Yellow=1 | blue=2 // Ok
Just as with C#, you can use the FlagsAttribute for bit flags:
[<System.FlagsAttribute>]
type PermissionFlags = Read = 1 | Write = 2 | Execute = 4
let permission = PermissionFlags.Read ||| PermissionFlags.Write
Constructing enums
Unlike union types, to construct an enum you must always use a qualified name:
541
Enum types
You can also cast to and from the underlying int type:
let redInt = int ColorEnum.Red
let redAgain:ColorEnum = enum redInt // cast to a specified enum type
let yellowAgain = enum<ColorEnum>(1) // or create directly
You can even create values that are not on the enumerated list at all.
let unknownColor = enum<ColorEnum>(99) // valid
And, unlike unions, you can use the BCL Enum functions to enumerate and parse values,
just as with C#. For example:
let values = System.Enum.GetValues(typeof<ColorEnum>)
let redFromString =
System.Enum.Parse(typeof<ColorEnum>,"Red")
:?> ColorEnum // downcast needed
Matching enums
To match an enum you must again always use a qualified name:
let unqualifiedMatch x =
match x with
| Red -> printfn "red" // warning FS0049
| _ -> printfn "something else"
let qualifiedMatch x =
match x with
| ColorEnum.Red -> printfn "red" //OK. qualified name used.
| _ -> printfn "something else"
Both unions and enums will warn if you have not covered all known cases when pattern
matching:
542
Enum types
let matchUnionIncomplete x =
match x with
| Small -> printfn "small"
| Medium -> printfn "medium"
// Warning: Incomplete pattern matches
let matchEnumIncomplete x =
match x with
| ColorEnum.Red -> printfn "red"
| ColorEnum.Yellow -> printfn "yellow"
// Warning: Incomplete pattern matches
One important difference between unions and enums is that can you make the compiler
happy about exhaustive pattern matching by listing all the union types.
Not so for enums. It is possible to create an enum not on the predeclared list, and try to
match with it, and get a runtime exception, so the compiler will warn you even if you have
explicitly listed all the known enums:
// the compiler is still not happy
let matchEnumIncomplete2 x =
match x with
| ColorEnum.Red -> printfn "red"
| ColorEnum.Yellow -> printfn "yellow"
| ColorEnum.Blue -> printfn "blue"
// the value '3' may indicate a case not covered by the pattern(s).
The only way to fix this is to add a wildcard to the bottom of the cases, to handle enums
outside the predeclared range.
// the compiler is finally happy
let matchEnumComplete x =
match x with
| ColorEnum.Red -> printfn "red"
| ColorEnum.Yellow -> printfn "yellow"
| ColorEnum.Blue -> printfn "blue"
| _ -> printfn "something else"
// test with unknown case
let unknownColor = enum<ColorEnum>(99) // valid
matchEnumComplete unknownColor
Summary
543
Enum types
In general, you should prefer discriminated union types over enums, unless you really need
to have an int value associated with them, or you are writing types that need to be
exposed to other .NET languages.
544
Literals
F# uses the same syntax for literals that C# does, with a few exceptions.
I'll divide the built-in types into the following groups:
miscellaneous types ( bool , char , etc. )
string types
integer types ( int , uint and byte , etc)
float types ( float , decimal , etc)
pointer types ( IntPtr , etc)
The following tables list the primitive types, with their F# keywords, their suffixes if any, an
example, and the corresponding .NET CLR type.
Miscellaneous types
Object
Keyword
obj
Unit
unit
Bool
bool
Char
(Unicode)
Char
(Ascii)
char
byte
Suffix
Example
let o = obj()
let u = ()
true false
'a'
'a'B
.NET Type
Object
(no equivalent)
Boolean
Char
Byte
Object and unit are not really .NET primitive types, but I have included them for the sake of
completeness.
String types
545
String
(Unicode)
Keyword
Verbatim
string
(Unicode)
String
(Ascii)
string
string
string
byte[]
Example
"first\nsecond
line"
@"C:\name"
"aaa"B
.NET
Type
String
String
String
Byte[]
Suffix
The usual special characters can be used inside normal strings, such as \n , \t , \\ , etc.
Quotes must be escaped with a backslash: \' and \" .
In verbatim strings, backslashes are ignored (good for Windows filenames and regex
patterns). But quotes need to be doubled.
Triple-quoted strings are new in VS2012. They are useful because special characters do not
need to be escaped at all, and so they can handle embedded quotes nicely (great for XML).
Integer types
8 bit
(Signed)
8 bit
(Unsigned)
16 bit
(Signed)
16 bit
(Unsigned)
32 bit
(Signed)
32 bit
(Unsigned)
Keyword
sbyte
byte
int16
uint16
int
uint32
Suffix
uy
us
Example
99y
99uy
99s
99us
99
99u
.NET
Type
SByte
Byte
Int16
UInt16
Int32
UInt32
BigInteger is available in all versions of F#. From .NET 4 it is included as part of the .NET
base library.
Integer types can also be written in hex and octal.
The hex prefix is 0x . So 0xFF is hex for 255.
The octal prefix is 0o . So 0o377 is octal for 255.
546
32 bit
floating point
64 bit (default)
floating point
float, double
High precision
floating point
Keyword
float32, single
decimal
Suffix
Example
123.456f
123.456
123.456m
.NET Type
Single
Double
Decimal
Note that F# natively uses float instead of double , but both can be used.
Pointer types
Pointer/handle
(signed)
Pointer/handle
(unsigned)
Keyword
nativeint
unativeint
Suffix
un
Example
0xFFFFFFFFn
0xFFFFFFFFun
.NET Type
IntPtr
UIntPtr
In F# there are only casting functions for numeric types. In particular, there is no cast for
bool, and you must use Convert or similar.
547
The fact that result is an object, not an int, can cause type errors if you are not careful.
For example, the result cannot be directly compared with the original value:
let resultIsOne = (result = 1)
// error FS0001: This expression was expected to have type obj
// but here has type int
To work with this situation, and other similar ones, you can convert a primitive type to an
object directly, by using the box keyword:
let o = box 1
// retest the comparison example above, but with boxing
let result = objFunction 1
let resultIsOne = (result = box 1) // OK
To convert an object back to an primitive type, use the unbox keyword, but unlike box , you
must either supply a specific type to unbox to, or be sure that the compiler has enough
information to make an accurate type inference.
548
// box an int
let o = box 1
// type known for target value
let i:int = unbox o // OK
// explicit type given in unbox
let j = unbox<int> o // OK
// type inference, so no type annotation needed
let k = 1 + unbox o // OK
So the comparison example above could also be done with unbox . No explicit type
annotation is needed because it is being compared with an int.
let result = objFunction 1
let resultIsOne = (unbox result = 1) // OK
A common problem occurs if you do not specify enough type information -- you will
encounter the infamous "Value restriction" error, as shown below:
let o = box 1
// no type specified
let i = unbox o // FS0030: Value restriction error
The solution is to reorder the code to help the type inference, or when all else fails, add an
explicit type annotation. See the post on type inference for more tips.
Unfortunately, this code will fail to compile, with the following error:
549
// error FS0008: This runtime coercion or type test from type 'a to int
// involves an indeterminate type based on information prior to this program point.
// Runtime type tests are not allowed on some types. Further type annotations are need
ed.
The message tells you the problem: "runtime type tests are not allowed on some types".
The answer is to "box" the value which forces it into a reference type, and then you can type
check it:
let detectTypeBoxed v =
match box v with // used "box v"
| :? int -> printfn "this is an int"
| _ -> printfn "something else"
//test
detectTypeBoxed 1
detectTypeBoxed 3.14
550
Units of measure
Units of measure
As we mentioned earlier in the "why use F#?" series, F# has a very cool feature which
allows you to add extra unit-of-measure information to as metadata to numeric types.
The F# compiler will then make sure that only numerics with the same unit-of-measure can
be combined. This can be very useful to stop accidental mismatches and to make your code
safer.
Often you will see the whole definition written on one line instead:
[<Measure>] type cm
[<Measure>] type inch
Once you have a definition, you can associate a measure type with a numeric type by using
angle brackets with measure name inside:
let x = 1<cm> // int
let y = 1.0<cm> // float
let z = 1.0m<cm> // decimal
You can even combine measures within the angle brackets to create compound measures:
551
Units of measure
[<Measure>] type m
[<Measure>] type sec
[<Measure>] type kg
let distance = 1.0<m>
let time = 2.0<sec>
let speed = 2.0<m/sec>
let acceleration = 2.0<m/sec^2>
let force = 5.0<kg m/sec^2>
552
Units of measure
Type annotations
If you want to be explicit in specifying a unit-of-measure type annotation, you can do so in
the usual way. The numeric type must have angle brackets with the unit-of-measure.
let untypedTimesThree (ft:float) =
ft * 3.0
let footTimesThree (ft:float<foot>) =
ft * 3.0
553
Units of measure
[<Measure>] type m
[<Measure>] type sec
[<Measure>] type kg
let distance = 1.0<m>
let time = 2.0<sec>
let speed = distance/time
let acceleration = speed/time
let mass = 5.0<kg>
let force = mass * speed/time
Look at the types of the acceleration and force values above to see other examples of
how this works.
Dimensionless values
A numeric value without any specific unit of measure is called dimensionless. If you want to
be explicit that a value is dimensionless, you can use the measure called 1 .
// dimensionless
let x = 42
// also dimensionless
let x = 42<1>
554
Units of measure
It's straightforward. You first need to define a conversion value that uses both units, and then
multiply the source value by the conversion factor.
Here's an example with feet and inches:
[<Measure>] type foot
[<Measure>] type inch
//conversion factor
let inchesPerFoot = 12.0<inch/foot>
// test
let distanceInFeet = 3.0<foot>
let distanceInInches = distanceInFeet * inchesPerFoot
Note that the constant 32.0<degF> was explicitly annotated with the degF so that the result
would be in degF as well. If you leave off this annotation, the result is a plain float, and the
function signature changes to something much stranger! Try it and see:
let badConvertDegCToF c =
c * 1.8<degF/degC> + 32.0
555
Units of measure
And to convert the other way, either divide by one, or multiply with the inverse unit.
//converting from measure to non-measure
let tenAgain = tenFeet / 1.0<foot> // without measure
let tenAnotherWay = tenFeet * 1.0<1/foot> // without measure
The above methods are type safe, and will cause errors if you try to convert the wrong type.
If you don't care about type checking, you can do the conversion with the standard casting
functions instead:
let tenFeet = 10.0<foot> // with measure
let tenDimensionless = float tenFeet // without measure
What can we do? We don't want to specify a particular unit of measure, but on the other
hand we must specify something, because the simple definition above doesn't work.
The answer is to use generic units of measure, indicated with an underscore where the
measure name normally is.
556
Units of measure
Now the square function works as desired, and you can see that the function signature has
used the letter 'u to indicate a generic unit of measure. And also note that the compiler
has inferred that the return value is of type "unit squared".
val square : int<'u> -> int<'u ^ 2>
Indeed, you can specify the generic type using letters as well if you like:
// with underscores
let square (x:int<_>) = x * x
// with letters
let square (x:int<'u>) = x * x
// with underscores
let speed (distance:float<_>) (time:float<_>) =
distance / time
// with letters
let speed (distance:float<'u>) (time:float<'v>) =
distance / time
You may need to use letters sometimes to explicitly indicate that the units are the same:
let ratio (distance1:float<'u>) (distance2:float<'u>) =
distance1 / distance2
Instead, you have to use the "multiply by one" trick mentioned above:
557
Units of measure
A similar situation occurs when passing in constants to a higher order function such as
fold .
558
Units of measure
The warning message has the clue. The input parameter n has no measure, so the
measure for 1<_> will always be ignored. The add1 function does not have a unit of
measure so when you try to call it with a value that does have a measure, you get an error.
So maybe the solution is to explicitly annotate the measure type, like this:
// define a function with explicit type annotation
let add1 (n:float<'u>) : float<'u> = n + 1.0<_>
And for generic ints, you can use the same approach:
559
Units of measure
open LanguagePrimitives
let add2Int n =
n + (Int32WithMeasure 2)
add2Int 10<foot> // OK
That didn't work, so what about adding the measure as a type parameter:
type Coord<'u> =
{ X: float<'u>; Y: float<'u>; }
// error FS0702: Expected unit-of-measure parameter, not type parameter.
// Explicit unit-of-measure parameters must be marked with the [<Measure>] attribute.
That didn't work either, but the error message tells us what to do. Here is the final, correct
version, using the Measure attribute:
type Coord<[<Measure>] 'u> =
{ X: float<'u>; Y: float<'u>; }
// Test
let coord = {X=10.0<foot>; Y=2.0<foot>}
In some cases, you might need to define more than one measure. In the following example,
the currency exchange rate is defined as the ratio of two currencies, and so needs two
generic measures to be defined.
560
Units of measure
And of course, you can mix regular generic types with unit of measure types.
For example, a product price might consist of a generic product type, plus a price with a
currency:
type ProductPrice<'product, [<Measure>] 'currency> =
{ Product: 'product; Price: float<'currency>; }
561
562
And of course assignment counts as an interaction too. If x is a certain type, and y is bound
(assigned) to x, then y must be the same type as x.
let x = 1
let y = x //deduce that y is also an int
Note that the formatting codes in printf statements count as explicit type constraints too!
563
Automatic generalization
If after all this, there are no constraints found, the compiler just makes the types generic.
let inferGeneric x = x
let inferIndirectGeneric x = inferGeneric x
let inferIndirectGenericAgain x = (inferIndirectGeneric x).ToString()
The output type of outerFn has been explicitly constrained to string , therefore the
output type of action is also string .
Putting this together, we now know that the action function has signature int->string
And finally, therefore, the compiler deduces the type of outerFn as:
val outerFn: (int -> string) -> string
The compiler can do deductions worthy of Sherlock Holmes. Here's a tricky example that will
test how well you have understood everything so far.
Let's say we have a doItTwice function that takes any input function (call it " f ") and
generates a new function that simply does the original function twice in a row. Here's the
code for it:
let doItTwice f = (f >> f)
As you can see, it composes f with itself. So in other words, it means: "do f", then "do f" on
the result of that.
Now, what could the compiler possibly deduce about the signature of doItTwice ?
Well, let's look at the signature of " f " first. The output of the first call to " f " is also the
input to the second call to " f ". So therefore the output and input of " f " must be the same
type. So the signature of f must be 'a -> 'a . The type is generic (written as 'a) because
we have no other information about it.
So going back to doItTwice itself, we now know it takes a function parameter of 'a -> 'a .
But what does it return? Well, here's how we deduce it, step by step:
First, note that doItTwice generates a function, so must return a function type.
The input to the generated function is the same type as the input to first call to " f "
The output of the generated function is the same type as the output of the second call to
" f "
So the generated function must also have type 'a -> 'a
Putting it all together, doItTwice has a domain of 'a -> 'a and a range of 'a -> 'a ,
so therefore its signature must be ('a -> 'a) -> ('a -> 'a) .
Is your head spinning yet? You might want to read it again until it sinks in.
Quite a sophisticated deduction for one line of code. Luckily the compiler does all this for us.
But you will need to understand this kind of thing if you have problems and you have to
determine what the compiler is doing.
Let's test it! It's actually much simpler to understand in practice than it is in theory.
565
566
And unlike C#, in F# the order of file compilation is important, so do make sure the files are
being compiled in the right order. (In Visual Studio, you can change the order from the
context menu).
Here's the fixed version with "rec fib" added to indicate it is recursive:
let rec fib n = // LET REC rather than LET
if n <= 2 then 1
else fib (n - 1) + fib (n - 2)
A similar " let rec ? and " syntax is used for two functions that refer to each other. Here is a
very contrived example that fails if you do not have the " rec " keyword.
let rec showPositiveNumber x = // LET REC rather than LET
match x with
| x when x >= 0 -> printfn "%i is positive" x
| _ -> showNegativeNumber x
and showNegativeNumber x = // AND rather than LET
match x with
| x when x < 0 -> printfn "%i is negative" x
| _ -> showPositiveNumber x
The " and " keyword can also be used to declare simultaneous types in a similar way.
567
Fixed version:
type A = None | AUsesB of B
and B = None | BUsesA of A // use AND instead of TYPE
Occasionally there does appear to be enough information, but still the compiler doesn't
seem to recognize it. For example, it's obvious to a human that the List.map function
(below) is being applied to a list of strings, so why does x.Length cause an error?
List.map (fun x -> x.Length) ["hello"; "world"] //not ok
The reason is that the F# compiler is currently a one-pass compiler, and so information later
in the program is ignored if it hasn't been parsed yet. (The F# team have said that it is
possible to make the compiler more sophisticated, but it would work less well with
Intellisense and might produce more unfriendly and obscure error messages. So for now, we
will have to live with this limitation.)
So in cases like this, you can always explicitly annotate:
List.map (fun (x:string) -> x.Length) ["hello"; "world"] // ok
568
But another, more elegant way that will often fix the problem is to rearrange things so the
known types come first, and the compiler can digest them before it moves to the next clause.
["hello"; "world"] |> List.map (fun s -> s.Length) //ok
Functional programmers strive to avoid explicit type annotations, so this makes them much
happier!
This technique can be used more generally in other areas as well; a rule of thumb is to try to
put the things that have "known types" earlier than things that have "unknown types".
Overloaded methods
When calling an external class or method in .NET, you will often get errors due to
overloading.
In many cases, such as the concat example below, you will have to explicitly annotate the
parameters of the external function so that the compiler knows which overloaded method to
call.
let concat x = System.String.Concat(x) //fails
let concat (x:string) = System.String.Concat(x) //works
let concat x = System.String.Concat(x:string) //works
Sometimes the overloaded methods have different argument names, in which case you can
also give the compiler a clue by naming the arguments. Here is an example for the
StreamReader constructor.
569
let myNumericFn x = x * x
myNumericFn 10
myNumericFn 10.0 //fails
// error FS0001: This expression was expected to have
// type int but has type float
let myNumericFn2 x = x * x
myNumericFn2 10.0
myNumericFn2 10 //fails
// error FS0001: This expression was expected to have
// type float but has type int
There is a way round this for numeric types using the "inline" keyword and "static type
parameters". I won't discuss these concepts here, but you can look them up in the F#
reference at MSDN.
570
let myBottomLevelFn x = x
let myMidLevelFn x =
let y = myBottomLevelFn x
// some stuff
let z= y
// some stuff
printf "%s" z // this will kill your generic types!
// some more stuff
x
let myTopLevelFn x =
// some stuff
myMidLevelFn x
// some more stuff
x
In this example, we have a chain of functions. The bottom level function is definitely generic,
but what about the top level one? Well often, we might expect it be generic but instead it is
not. In this case we have:
val myTopLevelFn : string -> string
What went wrong? The answer is in the midlevel function. The %s on z forced it be a string,
which forced y and then x to be strings too.
Now this is a pretty obvious example, but with thousands of lines of code, a single line might
be buried away that causes an issue. One thing that can help is to look at all the signatures;
in this case the signatures are:
val myBottomLevelFn : 'a -> 'a // generic as expected
val myMidLevelFn : string -> string // here's the clue! Should be generic
val myTopLevelFn : string -> string
When you find a signature that is unexpected you know that it is the guilty party. You can
then drill down into it and repeat the process until you find the problem.
571
572
573
Immutable?
list
Yes
Notes
Pros:
Pattern matching available.
Complex iteration available via recursion.
Forward iteration is fast. Prepending is fast.
Cons:
Indexed access and other access styles are slow.
Alias for IEnumerable .
seq
Yes
Pros:
Lazy evaluation
Memory efficient (only one element at a time
loaded)
Can represent an infinite sequence.
Interop with .NET libraries that use IEnumerable.
Cons:
No pattern matching.
Forward only iteration.
Indexed access and other access styles are slow.
Same as BCL Array .
array
No
Pros:
Fast random access
Memory efficient and cache locality, especially
with structs.
Interop with .NET libraries that use Array.
Support for 2D, 3D and 4D arrays
Cons:
Limited pattern matching.
Not persistent.
map
Yes
set
Yes
ResizeArray
No
IDictionary
Yes
574
These are the main collection types that you will encounter in F#, and will be good enough
for all common cases.
If you need other kinds of collections though, there are lots of choices:
You can use the collection classes in .NET, either the traditional, mutable ones or the
newer ones such as those in the System.Collections.Immutable namespace.
Alternatively, you can use one of the F# collection libraries:
FSharpx.Collections, part of the FSharpx series of projects.
ExtCore. Some of these are drop-in (almost) replacements for the Map and Set
types in FSharp.Core which provide improved performance in specific scenarios
(e.g., HashMap). Others provide unique functionality to help tackle specific coding
tasks (e.g., LazyList and LruCache).
Funq: high performance, immutable data structures for .NET.
Persistent: some efficient persistent (immutable) data structures.
For the function signatures I will use list as the standard collection type. The signatures
for the seq and array versions will be similar.
Many of these functions are not yet documented on MSDN so I'm going to link directly to the
source code on GitHub, which has the up-to-date comments. Click on the function name for
the link.
Note on availability
The availability of these functions may depend on which version of F# you use.
In F# version 3 (Visual Studio 2013), there was some degree of inconsistency between
Lists, Arrays and Sequences.
In F# version 4 (Visual Studio 2015), this inconsistency has been eliminated, and almost
all functions are available for all three collection types.
If you want to know what changed between F# v3 and F# v4, please see this chart (from
here). The chart shows the new APIs in F# v4 (green), previously-existing APIs (blue), and
intentional remaining gaps (white).
575
Some of the functions documented below are not in this chart -- these are newer still! If you
are using an older version of F#, you can simply reimplement them yourself using the code
on GitHub.
With that disclaimer out of the way, you can start your adventure!
Table of contents
1. What kind of collection do you have?
2. Creating a new collection
3. Creating a new empty or one-element collection
4. Creating a new collection of known size
5. Creating a new collection of known size with each element having the same value
6. Creating a new collection of known size with each element having a different value
7. Creating a new infinite collection
8. Creating a new collection of indefinite size
9. Working with one list
10. Getting an element at a known position
11. Getting an element by searching
12. Getting a subset of elements from a collection
13. Partitioning, chunking and grouping
14. Aggregating or summarizing a collection
15. Changing the order of the elements
16. Testing the elements of a collection
17. Transforming each element to something different
18. Iterating over each element
19. Threading state through an iteration
20. Working with the index of each element
21. Transforming the whole collection to a different collection type
22. Changing the behavior of the collection as a whole
23. Working with two collections
24. Working with three collections
25. Working with more than three collections
26. Combining and uncombining collections
27. Other array-only functions
28. Using sequences with disposables
576
If you know the size of the collection in advance, it is generally more efficient to use a
different function. See section 4 below.
Usage examples
let list0 = List.empty
// list0 = []
let list1 = List.singleton "hello"
// list1 = ["hello"]
577
Usage examples
let repl = List.replicate 3 "hello"
// val repl : string list = ["hello"; "hello"; "hello"]
let arrCreate = Array.create 3 "hello"
// val arrCreate : string [] = [|"hello"; "hello"; "hello"|]
let intArr0 : int[] = Array.zeroCreate 3
// val intArr0 : int [] = [|0; 0; 0|]
let stringArr0 : string[] = Array.zeroCreate 3
// val stringArr0 : string [] = [|null; null; null|]
Note that for zeroCreate , the target type must be known to the compiler.
578
For lists and arrays and seqs, you can use the comprehension syntax for .. in .. do
.. yield .
Usage examples
// using list initializer
let listInit1 = List.init 5 (fun i-> i*i)
// val listInit1 : int list = [0; 1; 4; 9; 16]
// using list comprehension
let listInit2 = [for i in [1..5] do yield i*i]
// val listInit2 : int list = [1; 4; 9; 16; 25]
// literal
let listInit3 = [1; 4; 9; 16; 25]
// val listInit3 : int list = [1; 4; 9; 16; 25]
let arrayInit3 = [|1; 4; 9; 16; 25|]
// val arrayInit3 : int [] = [|1; 4; 9; 16; 25|]
The comprehension syntax is even more flexible because you can yield more than once:
579
580
when iterated, will return successive elements by calling the given function.
You can also use a seq comprehension with a recursive loop to generate an infinite
sequence.
Usage examples
// generator version
let seqOfSquares = Seq.initInfinite (fun i -> i*i)
let firstTenSquares = seqOfSquares |> Seq.take 10
firstTenSquares |> List.ofSeq // [0; 1; 4; 9; 16; 25; 36; 49; 64; 81]
// recursive version
let seqOfSquares_v2 =
let rec loop n = seq {
yield n * n
yield! loop (n+1)
}
loop 1
let firstTenSquares_v2 = seqOfSquares_v2 |> Seq.take 10
Returns a collection that contains the elements generated by the given computation.
Usage examples
This example reads from the console in a loop until an empty line is entered:
581
unfold requires that a state be threaded through the generator. You can ignore it (as in the
ReadLine example above), or you can use it to keep track of what you have done so far. For
582
has index 0.
NOTE: Avoid using nth and item for lists and sequences. They are not designed for
random access, and so they will be slow in general.
nth : list:'T list -> index:int -> 'T . The older version of item . NOTE: Deprecated
But what if the collection is empty? Then head and last will fail with an exception
(ArgumentException).
And if the index is not found in the collection? Then another exception again
(ArgumentException for lists, IndexOutOfRangeException for arrays).
I would therefore recommend that you avoid these functions in general and use the tryXXX
equivalents below:
tryHead : list:'T list -> 'T option . Returns the first element of the collection, or
583
Usage examples
let head = [1;2;3] |> List.head
// val head : int = 1
let badHead : int = [] |> List.head
// System.ArgumentException: The input list was empty.
let goodHeadOpt =
[1;2;3] |> List.tryHead
// val goodHeadOpt : int option = Some 1
let badHeadOpt : int option =
[] |> List.tryHead
// val badHeadOpt : int option = None
let goodItemOpt =
[1;2;3] |> List.tryItem 2
// val goodItemOpt : int option = Some 3
let badItemOpt =
[1;2;3] |> List.tryItem 99
// val badItemOpt : int option = None
As noted, the item function should be avoided for lists. For example, if you want to process
each item in a list, and you come from an imperative background, you might write a loop with
something like this:
// Don't do this!
let helloBad =
let list = ["a";"b";"c"]
let listSize = List.length list
[ for i in [0..listSize-1] do
let element = list |> List.item i
yield "hello " + element
]
// val helloBad : string list = ["hello a"; "hello b"; "hello c"]
Don't do that! Use something like map instead. It's both more concise and more efficient:
let helloGood =
let list = ["a";"b";"c"]
list |> List.map (fun element -> "hello " + element)
// val helloGood : string list = ["hello a"; "hello b"; "hello c"]
584
the last element for which the given function returns true.
But what if the item cannot be found? Then these will fail with an exception
( KeyNotFoundException ).
I would therefore recommend that, as with head and item , you avoid these functions in
general and use the tryXXX equivalents below:
tryFind : predicate:('T -> bool) -> list:'T list -> 'T option . Returns the first
element for which the given function returns true, or None if no such element exists.
tryFindBack : predicate:('T -> bool) -> list:'T list -> 'T option . Returns the last
element for which the given function returns true, or None if no such element exists.
tryFindIndex : predicate:('T -> bool) -> list:'T list -> int option . Returns the
index of the first element for which the given function returns true, or None if no such
element exists.
tryFindIndexBack : predicate:('T -> bool) -> list:'T list -> int option . Returns the
index of the last element for which the given function returns true, or None if no such
element exists.
If you are doing a map before a find you can often combine the two steps into a single
one using pick (or better, tryPick ). See below for a usage example.
pick : chooser:('T -> 'U option) -> list:'T list -> 'U . Applies the given function to
successive elements, returning the first result where the chooser function returns Some.
tryPick : chooser:('T -> 'U option) -> list:'T list -> 'U option . Applies the given
function to successive elements, returning the first result where the chooser function
returns Some, or None if no such element exists.
Usage examples
585
And now say that we want to find the first valid int in a list. The crude way would be:
map the list using tryInt
find the first one that is a Some using find
get the value from inside the option using Option.get
The code might look something like this:
586
let firstValidNumber =
["a";"2";"three"]
// map the input
|> List.map tryInt
// find the first Some
|> List.find (fun opt -> opt.IsSome)
// get the data from the option
|> Option.get
// val firstValidNumber : int = 2
But pick will do all these steps at once! So the code becomes much simpler:
let firstValidNumber =
["a";"2";"three"]
|> List.pick tryInt
If you want to return many elements in the same way as pick , consider using choose (see
section 12).
collection.
takeWhile: predicate:('T -> bool) -> list:'T list -> 'T list . Returns a collection
that contains all elements of the original collection while the given predicate returns true,
and then returns no further elements.
truncate: count:int -> list:'T list -> 'T list . Returns at most N elements in a new
collection.
To extract elements from the rear, use one of these:
skip: count:int -> list: 'T list -> 'T list . Returns the collection after removing the
first N elements.
skipWhile: predicate:('T -> bool) -> list:'T list -> 'T list . Bypasses elements in
a collection while the given predicate returns true, and then returns the remaining
587
containing only the elements of the collection for which the given function returns true.
except: itemsToExclude:seq<'T> -> list:'T list -> 'T list when 'T : equality .
Returns a new collection with the distinct elements of the input collection which do not
appear in the itemsToExclude sequence, using generic hash and equality comparisons
to compare values.
choose: chooser:('T -> 'U option) -> list:'T list -> 'U list . Applies the given
containing only the elements of the collection for which the given predicate returns true.
NOTE: "where" is a synonym for "filter".
(Array only) sub : 'T [] -> int -> int -> 'T [] . Creates an array that contains the
supplied subrange, which is specified by starting index and length.
You can also use slice syntax: myArray.[2..5] . See below for examples.
To reduce the list to distinct elements, use one of these:
distinct: list:'T list -> 'T list when 'T : equality . Returns a collection that
Returns a collection that contains no duplicate entries according to the generic hash
and equality comparisons on the keys returned by the given key-generating function.
Usage examples
Taking elements from the front:
588
589
To extract a slice:
Array.sub [|1..10|] 3 5
// [|4; 5; 6; 7; 8|]
[1..10].[3..5]
// [4; 5; 6]
[1..10].[3..]
// [4; 5; 6; 7; 8; 9; 10]
[1..10].[..5]
// [1; 2; 3; 4; 5; 6]
Note that slicing on lists can be slow, because they are not random access. Slicing on arrays
is fast however.
To extract the distinct elements:
[1;1;1;2;3;3] |> List.distinct
// [1; 2; 3]
[ (1,"a"); (1,"b"); (1,"c"); (2,"d")] |> List.distinctBy fst
// [(1, "a"); (2, "d")]
And now say that we want to find all the valid ints in a list. The crude way would be:
map the list using tryInt
filter to only include the ones that are Some
590
But choose will do all these steps at once! So the code becomes much simpler:
let allValidNumbers =
["a";"2";"three"; "4"]
|> List.choose tryInt
If you already have a list of options, you can filter and return the "Some" in one step by
passing id into choose :
let reduceOptions =
[None; Some 1; None; Some 2]
|> List.choose id
// val reduceOptions : int list = [1; 2]
If you want to return the first element in the same way as choose , consider using pick
(see section 11).
If you want to do a similar action as choose but for other wrapper types (such as a
Success/Failure result), there is a discussion here.
591
a list of unique keys. Each unique key contains a list of all elements that match to this
key.
pairwise: list:'T list -> ('T * 'T) list . Returns a collection of each element in the
input collection and its predecessor, with the exception of the first element which is only
returned as the predecessor of the second element.
(Except Seq) partition: predicate:('T -> bool) -> list:'T list -> ('T list * 'T
list) . Splits the collection into two collections, containing the elements for which the
windows containing elements drawn from the input collection. Each window is returned
as a fresh collection. Unlike pairwise the windows are collections, not tuples.
Usage examples
[1..10] |> List.chunkBySize 3
// [[1; 2; 3]; [4; 5; 6]; [7; 8; 9]; [10]]
// note that the last chunk has one element
[1..10] |> List.splitInto 3
// [[1; 2; 3; 4]; [5; 6; 7]; [8; 9; 10]]
// note that the first chunk has four elements
['a'..'i'] |> List.splitAt 3
// (['a'; 'b'; 'c'], ['d'; 'e'; 'f'; 'g'; 'h'; 'i'])
['a'..'e'] |> List.pairwise
// [('a', 'b'); ('b', 'c'); ('c', 'd'); ('d', 'e')]
['a'..'e'] |> List.windowed 3
// [['a'; 'b'; 'c']; ['b'; 'c'; 'd']; ['c'; 'd'; 'e']]
let isEven i = (i%2 = 0)
[1..10] |> List.partition isEven
// ([2; 4; 6; 8; 10], [1; 3; 5; 7; 9])
let firstLetter (str:string) = str.[0]
["apple"; "alice"; "bob"; "carrot"] |> List.groupBy firstLetter
// [('a', ["apple"; "alice"]); ('b', ["bob"]); ('c', ["carrot"])]
All the functions other than splitAt and pairwise handle edge cases gracefully:
592
each element of the collection, starting from the end, threading an accumulator
argument through the computation.
and there are specific versions of reduce for frequently used aggregations:
max : list:'T list -> 'T when 'T : comparison . Return the greatest of all elements of
the greatest of all elements of the collection, compared via Operators.max on the
function result.
min : list:'T list -> 'T when 'T : comparison . Returns the lowest of all elements of
the lowest of all elements of the collection, compared via Operators.min on the function
593
result.
sum : list:'T list -> 'T when 'T has static members (+) and Zero . Returns the sum of
Returns the average of the elements in the collection. Note that a list of ints cannot be
averaged -- they must be cast to floats or decimals.
averageBy : projection:('T -> 'U) -> list:'T list -> 'U when 'U has static members
(+) and Zero and DivideByInt . Returns the average of the results generated by applying
yielding unique keys and their number of occurrences in the original collection.
Usage examples
reduce is a variant of fold without an initial state -- see section 19 for more on fold .
is the same as
"a" + "b" + "c"
Some ways of combining elements depend on the order of combining, and so there are two
variants of "reduce":
594
Using the same combining function with reduceBack produces a different result! It looks like
this:
[1;2;3;4] |> List.reduceBack (fun x state -> x + 10*(state))
// built up from // state at each step
4 // 4
3 + 10*(4) // 43
2 + 10*(3 + 10*(4)) // 432
1 + 10*(2 + 10*(3 + 10*(4))) // 4321
// Final result is 4321
Again, see section 19 for a more detailed discussion of the related functions fold and
foldBack .
595
Most of the aggregation functions do not like empty lists! You might consider using one of
the fold functions to be safe -- see section 19.
596
order.
sort: list:'T list -> 'T list when 'T : comparison . Sorts the given collection using
Operators.compare.
sortDescending: list:'T list -> 'T list when 'T : comparison . Sorts the given
Sorts the given collection using keys given by the given projection. Keys are compared
using Operators.compare.
sortByDescending: projection:('T -> 'Key) -> list:'T list -> 'T list when 'Key :
comparison . Sorts the given collection in descending order using keys given by the
597
And there are also some array-only functions that sort in place:
(Array only) sortInPlace: array:'T[] -> unit when 'T : comparison . Sorts the elements
of an array by mutating the array in-place. Elements are compared using
Operators.compare.
(Array only) sortInPlaceBy: projection:('T -> 'Key) -> array:'T[] -> unit when 'Key :
comparison . Sorts the elements of an array by mutating the array in-place, using the
given projection for the keys. Keys are compared using Operators.compare.
(Array only) sortInPlaceWith: comparer:('T -> 'T -> int) -> array:'T[] -> unit . Sorts
the elements of an array by mutating the array in-place, using the given comparison
function as the order.
Usage examples
[1..5] |> List.rev
// [5; 4; 3; 2; 1]
[2;4;1;3;5] |> List.sort
// [1; 2; 3; 4; 5]
[2;4;1;3;5] |> List.sortDescending
// [5; 4; 3; 2; 1]
[ ("b","2"); ("a","3"); ("c","1") ] |> List.sortBy fst
// [("a", "3"); ("b", "2"); ("c", "1")]
[ ("b","2"); ("a","3"); ("c","1") ] |> List.sortBy snd
// [("c", "1"); ("b", "2"); ("a", "3")]
// example of a comparer
let tupleComparer tuple1 tuple2 =
if tuple1 < tuple2 then
-1
elif tuple1 > tuple2 then
1
else
0
[ ("b","2"); ("a","3"); ("c","1") ] |> List.sortWith tupleComparer
// [("a", "3"); ("b", "2"); ("c", "1")]
[1..10] |> List.permute (fun i -> (i + 3) % 10)
// [8; 9; 10; 1; 2; 3; 4; 5; 6; 7]
[1..10] |> List.permute (fun i -> 9 - i)
// [10; 9; 8; 7; 6; 5; 4; 3; 2; 1]
598
false otherwise.
Usage examples
[1..10] |> List.contains 5
// true
[1..10] |> List.contains 42
// false
[1..10] |> List.exists (fun i -> i > 3 && i < 5)
// true
[1..10] |> List.exists (fun i -> i > 5 && i < 3)
// false
[1..10] |> List.forall (fun i -> i > 0)
// true
[1..10] |> List.forall (fun i -> i > 5)
// false
[1..10] |> List.isEmpty
// false
599
map: mapping:('T -> 'U) -> list:'T list -> 'U list . Builds a new collection whose
elements are the results of applying the given function to each of the elements of the
collection.
Sometimes each element maps to a list, and you want to flatten out all the lists. For this
case, use collect (aka SelectMany in LINQ).
collect: mapping:('T -> 'U list) -> list:'T list -> 'U list . For each element of the
list, applies the given function. Concatenates all the results and return the combined list.
Other transformation functions include:
choose in section 12 is a map and option filter combined.
Usage examples
Here are some examples of using map in the conventional way, as a function that accepts a
list and a mapping function and returns a new transformed list:
let add1 x = x + 1
// map as a list transformer
[1..5] |> List.map add1
// [2; 3; 4; 5; 6]
// the list being mapped over can contain anything!
let times2 x = x * 2
[ add1; times2] |> List.map (fun f -> f 5)
// [6; 10]
You can also think of map as a function transformer. It turns an element-to-element function
into a list-to-list function.
let add1ToEachElement = List.map add1
// "add1ToEachElement" transforms lists to lists rather than ints to ints
// val add1ToEachElement : (int list -> int list)
// now use it
[1..5] |> add1ToEachElement
// [2; 3; 4; 5; 6]
collect works to flatten lists. If you already have a list of lists, you can use collect with
id to flatten them.
600
Seq.cast
Finally, Seq.cast is useful when working with older parts of the BCL that have specialized
collection classes rather than generics.
For example, the Regex library has this problem, and so the code below won't compile
because MatchCollection is not an IEnumerable<T>
open System.Text.RegularExpressions
let matches =
let pattern = "\d\d\d"
let matchCollection = Regex.Matches("123 456 789",pattern)
matchCollection
|> Seq.map (fun m -> m.Value) // ERROR
// ERROR: The type 'MatchCollection' is not compatible with the type 'seq<'a>'
|> Seq.toList
The fix is to cast MatchCollection to a Seq<Match> and then the code will work nicely:
let matches =
let pattern = "\d\d\d"
let matchCollection = Regex.Matches("123 456 789",pattern)
matchCollection
|> Seq.cast<Match>
|> Seq.map (fun m -> m.Value)
|> Seq.toList
// output = ["123"; "456"; "789"]
601
iter: action:('T -> unit) -> list:'T list -> unit . Applies the given function to each
Usage examples
The most common examples of unit functions are all about side-effects: printing to the
console, updating a database, putting a message on a queue, etc. For the examples below, I
will just use printfn as my unit function.
[1..3] |> List.iter (fun i -> printfn "i is %i" i)
(*
i is 1
i is 2
i is 3
*)
// or using partial application
[1..3] |> List.iter (printfn "i is %i")
// or using a for loop
for i = 1 to 3 do
printfn "i is %i" i
// or using a for-in loop
for i in [1..3] do
printfn "i is %i" i
As noted above, the expression inside an iter or for-loop must return unit. In the following
examples, we try to add 1 to the element, and get a compiler error:
[1..3] |> List.iter (fun i -> i + 1)
// ~~~
// ERROR error FS0001: The type 'unit' does not match the type 'int'
// a for-loop expression *must* return unit
for i in [1..3] do
i + 1 // ERROR
// This expression should have type 'unit',
// but has type 'int'. Use 'ignore' ...
If you are sure that this is not a logic bug in your code, and you want to get rid of this error,
you can pipe the results into ignore :
602
threading an accumulator argument through the computation. WARNING: Watch out for
using Seq.foldBack on infinite lists! The runtime will laugh at you ha ha ha and then go
very quiet.
The fold function is often called "fold left" and foldBack is often called "fold right".
The scan function is like fold but returns the intermediate results and thus can be used to
trace or monitor the iteration.
scan<'T,'State> : folder:('State -> 'T -> 'State) -> state:'State -> list:'T list ->
'State list . Like fold , but returns both the intermediary and final results.
scanBack<'T,'State> : folder:('T -> 'State -> 'State) -> list:'T list -> state:'State
-> 'State list . Like foldBack , but returns both the intermediary and final results.
Just like the fold twins, scan is often called "scan left" and scanBack is often called "scan
right".
Finally, mapFold combines map and fold into one awesome superpower. More
complicated than using map and fold separately but also more efficient.
mapFold<'T,'State,'Result> : mapping:('State -> 'T -> 'Result * 'State) ->
state:'State -> list:'T list -> 'Result list * 'State . Combines map and fold. Builds
a new collection whose elements are the results of applying the given function to each
of the elements of the input collection. The function is also used to accumulate a final
value.
mapFoldBack<'T,'State,'Result> : mapping:('T -> 'State -> 'Result * 'State) ->
603
list:'T list -> state:'State -> 'Result list * 'State . Combines map and foldBack.
Builds a new collection whose elements are the results of applying the given function to
each of the elements of the input collection. The function is also used to accumulate a
final value.
fold examples
One way of thinking about fold is that it is like reduce but with an extra parameter for the
initial state:
["a";"b";"c"] |> List.fold (+) "hello: "
// "hello: abc"
// "hello: " + "a" + "b" + "c"
[1;2;3] |> List.fold (+) 10
// 16
// 10 + 1 + 2 + 3
As with reduce , fold and foldBack can give very different answers.
[1;2;3;4] |> List.fold (fun state x -> (state)*10 + x) 0
// state at each step
1 // 1
(1)*10 + 2 // 12
((1)*10 + 2)*10 + 3 // 123
(((1)*10 + 2)*10 + 3)*10 + 4 // 1234
// Final result is 1234
Note that foldBack has a different parameter order to fold : the list is second last, and the
initial state is last, which means that piping is not as convenient.
Recursing vs iterating
604
It's easy to get confused between fold vs. foldBack . I find it helpful to think of fold as
being about iteration while foldBack is about recursion.
Let's say we want to calculate the sum of a list. The iterative way would be to use a for-loop.
You start with a (mutable) accumulator and thread it through each iteration, updating it as
you go.
let iterativeSum list =
let mutable total = 0
for e in list do
total <- total + e
total // return sum
On the other hand, the recursive approach says that if the list has a head and tail, calculate
the sum of the tail (a smaller list) first, and then add the head to it.
Each time the tail gets smaller and smaller until it is empty, at which point you're done.
let rec recursiveSum list =
match list with
| [] ->
0
| head::tail ->
head + (recursiveSum tail)
605
On the other hand, a big advantage of fold is that it is easier to use "inline" because it
plays better with piping.
Luckily, you can use fold (for list construction at least) just like foldBack as long as you
reverse the list at the end.
// inline version of "foldToString"
[1..3]
|> List.fold (fun state head -> head.ToString() :: state) []
|> List.rev
// ["1"; "2"; "3"]
606
And of course, you can emulate the other functions in a similar way.
scan examples
Earlier, I showed an example of the intermediate steps of fold :
[1;2;3;4] |> List.fold (fun state x -> (state)*10 + x) 0
// state at each step
1 // 1
(1)*10 + 2 // 12
((1)*10 + 2)*10 + 3 // 123
(((1)*10 + 2)*10 + 3)*10 + 4 // 1234
// Final result is 1234
607
Just as with foldBack the parameter order for "scan right" is inverted compared with "scan
left".
608
Note that I'm using Seq.scan rather than Array.scan . This does a lazy scan and avoids
having to create fragments that are not needed.
Finally, here is the complete logic as a utility function:
// the whole thing as a function
let truncText max (text:string) =
if text.Length <= max then
text
else
text.Split(' ')
|> Seq.scan (fun frag word -> frag + " " + word) ""
|> Seq.takeWhile (fun s -> s.Length <= max-3)
|> Seq.last
|> (fun s -> s.[1..] + "...")
"a small headline" |> truncText 50
// "a small headline"
text |> truncText 50
// "Lorem ipsum dolor sit amet, consectetur..."
Yes, I know that there is a more efficient implementation than this, but I hope that this little
example shows off the power of scan .
mapFold examples
The mapFold function can do a map and a fold in one step, which can be convenient on
occasion.
Here's an example of combining an addition and a sum in one step using mapFold :
let add1 x = x + 1
// add1 using map
[1..5] |> List.map (add1)
// Result => [2; 3; 4; 5; 6]
// sum using fold
[1..5] |> List.fold (fun state x -> state + x) 0
// Result => 15
// map and sum using mapFold
[1..5] |> List.mapFold (fun state x -> add1 x, (state + x)) 0
// Result => ([2; 3; 4; 5; 6], 15)
609
integer index passed to the function as well. See section 17 for more on map .
iteri: action:(int -> 'T -> unit) -> list:'T list -> unit . Like iter , but with the
integer index passed to the function as well. See section 18 for more on iter .
indexed: list:'T list -> (int * 'T) list . Returns a new list whose elements are the
corresponding elements of the input list paired with the index (from 0) of each element.
Usage examples
['a'..'c'] |> List.mapi (fun index ch -> sprintf "the %ith element is '%c'" index ch)
// ["the 0th element is 'a'"; "the 1th element is 'b'"; "the 2th element is 'c'"]
// with partial application
['a'..'c'] |> List.mapi (sprintf "the %ith element is '%c'")
// ["the 0th element is 'a'"; "the 1th element is 'b'"; "the 2th element is 'c'"]
['a'..'c'] |> List.iteri (printfn "the %ith element is '%c'")
(*
the 0th element is 'a'
the 1th element is 'b'
the 2th element is 'c'
*)
indexed generates a tuple with the index -- a shortcut for a specific use of mapi :
610
The ofXXX functions are used to convert from XXX to the module type. For example,
List.ofArray will turn an array into a list.
(Except Array) ofArray : array:'T[] -> 'T list . Builds a new collection from the given
array.
(Except Seq) ofSeq: source:seq<'T> -> 'T list . Builds a new collection from the given
enumerable object.
(Except List) ofList: source:'T list -> seq<'T> . Builds a new collection from the given
list.
The toXXX are used to convert from the module type to the type XXX . For example,
List.toArray will turn an list into an array.
(Except Array) toArray: list:'T list -> 'T[] . Builds an array from the given
collection.
(Except Seq) toSeq: list:'T list -> seq<'T> . Views the given collection as a
sequence.
(Except List) toList: source:seq<'T> -> 'T list . Builds a list from the given collection.
Usage examples
[1..5] |> List.toArray // [|1; 2; 3; 4; 5|]
[1..5] |> Array.ofList // [|1; 2; 3; 4; 5|]
// etc
611
a cached version of the input sequence. This result sequence will have the same
elements as the input sequence. The result can be enumerated multiple times. The
input sequence will be enumerated at most once and only as far as is necessary.
(Seq only) readonly : source:seq<'T> -> seq<'T> . Builds a new sequence object that
delegates to the given sequence object. This ensures the original sequence cannot be
rediscovered and mutated by a type cast.
(Seq only) delay : generator:(unit -> seq<'T>) -> seq<'T> . Returns a sequence that is
built from the given delayed specification of a sequence.
cache example
Here's an example of cache in use:
let uncachedSeq = seq {
for i = 1 to 3 do
printfn "Calculating %i" i
yield i
}
// iterate twice
uncachedSeq |> Seq.iter ignore
uncachedSeq |> Seq.iter ignore
The result of iterating over the sequence twice is as you would expect:
Calculating 1
Calculating 2
Calculating 3
Calculating 1
Calculating 2
Calculating 3
612
Calculating 1
Calculating 2
Calculating 3
readonly example
Here's an example of readonly being used to hide the underlying type of the sequence:
// print the underlying type of the sequence
let printUnderlyingType (s:seq<_>) =
let typeName = s.GetType().Name
printfn "%s" typeName
[|1;2;3|] |> printUnderlyingType
// Int32[]
[|1;2;3|] |> Seq.readonly |> printUnderlyingType
// mkSeq@589 // a temporary type
delay example
Here's an example of delay .
let makeNumbers max =
[ for i = 1 to max do
printfn "Evaluating %d." i
yield i ]
let eagerList =
printfn "Started creating eagerList"
let list = makeNumbers 5
printfn "Finished creating eagerList"
list
let delayedSeq =
printfn "Started creating delayedSeq"
let list = Seq.delay (fun () -> makeNumbers 5 |> Seq.ofList)
printfn "Finished creating delayedSeq"
list
If we run the code above, we find that just by creating eagerList , we print all the
"Evaluating" messages. But creating delayedSeq does not trigger the list iteration.
613
Only when the sequence is iterated over does the list creation happen:
eagerList |> Seq.take 3 // list already created
delayedSeq |> Seq.take 3 // list creation triggered
An alternative to using delay is just to embed the list in a seq like this:
let embeddedList = seq {
printfn "Started creating embeddedList"
yield! makeNumbers 5
printfn "Finished creating embeddedList"
}
As with delayedSeq , the makeNumbers function will not be called until the sequence is
iterated over.
Builds a new collection whose elements are the results of applying the given function to
the corresponding elements of the two collections pairwise.
mapi2: mapping:(int -> 'T1 -> 'T2 -> 'U) -> list1:'T1 list -> list2:'T2 list -> 'U
list . Like mapi , but mapping corresponding elements from two lists of equal length.
iter2: action:('T1 -> 'T2 -> unit) -> list1:'T1 list -> list2:'T2 list -> unit .
Applies the given function to two collections simultaneously. The collections must have
identical size.
iteri2: action:(int -> 'T1 -> 'T2 -> unit) -> list1:'T1 list -> list2:'T2 list ->
614
unit . Like iteri , but mapping corresponding elements from two lists of equal length.
forall2: predicate:('T1 -> 'T2 -> bool) -> list1:'T1 list -> list2:'T2 list -> bool .
The predicate is applied to matching elements in the two collections up to the lesser of
the two lengths of the collections. If any application returns false then the overall result
is false, else true.
exists2: predicate:('T1 -> 'T2 -> bool) -> list1:'T1 list -> list2:'T2 list -> bool .
The predicate is applied to matching elements in the two collections up to the lesser of
the two lengths of the collections. If any application returns true then the overall result is
true, else false.
fold2<'T1,'T2,'State> : folder:('State -> 'T1 -> 'T2 -> 'State) -> state:'State ->
list1:'T1 list -> list2:'T2 list -> 'State . Applies a function to corresponding
Compares two collections using the given comparison function, element by element.
Returns the first non-zero result from the comparison function. If the end of a collection
is reached it returns a -1 if the first collection is shorter and a 1 if the second collection is
shorter.
See also append , concat , and zip in section 26: combining and uncombining
collections.
Usage examples
These functions are straightforward to use:
615
616
applying the given function to the corresponding elements of the three collections
simultaneously.
See also append , concat , and zip3 in section 26: combining and uncombining
collections.
617
Alternatively you can "lift" your function to the world of "zip lists" using applicatives.
let (<*>) fList xList =
List.map2 (fun f x -> f x) fList xList
let (<!>) = List.map
let addFourParams x y z w =
x + y + z + w
// lift "addFourParams" to List world and pass lists as parameters rather than ints
addFourParams <!> [1;2;3] <*> [1;2;3] <*> [1;2;3] <*> [1;2;3]
// Result = [4; 8; 12]
If that seems like magic, see this series for a explanation of what this code is doing.
contains the elements of the first collection followed by elements of the second.
@ is an infix version of append for lists.
concat: lists:seq<'T list> -> 'T list . Builds a new collection whose elements are
the results of applying the given function to the corresponding elements of the
collections simultaneously.
zip: list1:'T1 list -> list2:'T2 list -> ('T1 * 'T2) list . Combines two collections
into a list of pairs. The two collections must have equal lengths.
zip3: list1:'T1 list -> list2:'T2 list -> list3:'T3 list -> ('T1 * 'T2 * 'T3) list .
Combines three collections into a list of triples. The collections must have equal lengths.
(Except Seq) unzip: list:('T1 * 'T2) list -> ('T1 list * 'T2 list) . Splits a
collection of pairs into two collections.
(Except Seq) unzip3: list:('T1 * 'T2 * 'T3) list -> ('T1 list * 'T2 list * 'T3
618
Usage examples
These functions are straightforward to use:
List.append [1;2;3] [4;5;6]
// [1; 2; 3; 4; 5; 6]
[1;2;3] @ [4;5;6]
// [1; 2; 3; 4; 5; 6]
List.concat [ [1]; [2;3]; [4;5;6] ]
// [1; 2; 3; 4; 5; 6]
List.zip [1;2] [10;20]
// [(1, 10); (2, 20)]
List.zip3 [1;2] [10;20] [100;200]
// [(1, 10, 100); (2, 20, 200)]
List.unzip [(1, 10); (2, 20)]
// ([1; 2], [10; 20])
List.unzip3 [(1, 10, 100); (2, 20, 200)]
// ([1; 2], [10; 20], [100; 200])
Note that the zip functions require the lengths to be the same.
List.zip [1;2] [10]
// ArgumentException: The lists had different lengths.
the second.
Array.copy: array:'T[] -> 'T[] . Builds a new array that contains the elements of the
given array.
619
Array.fill: target:'T[] -> targetIndex:int -> count:int -> value:'T -> unit . Fills a
In addition to these, all the other BCL array functions are available as well.
I won't give examples. See the MSDN documentation.
A naive implementation will cause the sequence to be evaluated after the connection is
closed:
620
let readCustomersFromDb() =
use dbConnection = DbConnection()
let results = readNCustomersFromDb dbConnection 2
results
let customers = readCustomersFromDb()
customers |> showCustomersinUI
The output is below. You can see that the connection is closed and only then is the
sequence evaluated.
Opening connection
Disposing connection
Loading Customer 1 from db // error! connection closed!
Showing Customer 1 in UI
Loading Customer 2 from db
Showing Customer 2 in UI
A better implementation will convert the sequence to a list while the connection is open,
causing the sequence to be evaluated immediately:
let readCustomersFromDb() =
use dbConnection = DbConnection()
let results = readNCustomersFromDb dbConnection 2
results |> List.ofSeq
// Convert to list while connection is open
let customers = readCustomersFromDb()
customers |> showCustomersinUI
The result is much better. All the records are loaded before the connection is disposed:
Opening connection
Loading Customer 1 from db
Loading Customer 2 from db
Disposing connection
Showing Customer 1 in UI
Showing Customer 2 in UI
621
let readCustomersFromDb() =
seq {
// put disposable inside the sequence
use dbConnection = DbConnection()
yield! readNCustomersFromDb dbConnection 2
}
let customers = readCustomersFromDb()
customers |> showCustomersinUI
The output shows that now the UI display is also done while the connection is open:
Opening connection
Loading Customer 1 from db
Showing Customer 1 in UI
Loading Customer 2 from db
Showing Customer 2 in UI
Disposing connection
This may be a bad thing (longer time for the connection to stay open) or a good thing
(minimal memory use), depending on the context.
622
623
If you do decide to use the object-oriented features of F#, the following series of posts
should cover everything you need to know to be productive with classes and methods in F#.
First up, how to create classes!
625
Classes
Classes
This post and the next will cover the basics of creating and using classes and methods in
F#.
Defining a class
Just like all other data types in F#, class definitions start with the type keyword.
The thing that distinguishes them from other types is that classes always have some
parameters passed in when they are created -- the constructor -- and so there are always
parentheses after the class name.
Also, unlike other types, classes must have functions attached to them as members. This
post will explain how you do this for classes, but for a general discussion of attaching
functions to other types see the post on type extensions.
So, for example, if we want to have a class called CustomerName that requires three
parameters to construct it, it would be written like this:
type CustomerName(firstName, middleInitial, lastName) =
member this.FirstName = firstName
member this.MiddleInitial = middleInitial
member this.LastName = lastName
626
Classes
You can see that in the F# version, the primary constructor is embedded into the class
declaration itself --- it is not a separate method. That is, the class declaration has the same
parameters as the constructor, and the parameters automatically become immutable private
fields that store the original values that were passed in.
So in the above example, because we declared the CustomerName class as:
type CustomerName(firstName, middleInitial, lastName)
One little quirk about F# is that if you ever need to pass a tuple as a parameter to a
constructor, you will have to annotate it explicitly, because the call to the constructor will look
identical:
type NonTupledConstructor(x:int,y: int) =
do printfn "x=%i y=%i" x y
type TupledConstructor(tuple:int * int) =
let x,y = tuple
do printfn "x=%i y=%i" x y
// calls look identical
let myNTC = new NonTupledConstructor(1,2)
let myTC = new TupledConstructor(1,2)
Class members
627
Classes
The example class above has three read-only instance properties. In F#, both properties and
methods use the member keyword.
Also, in the example above, you see the word " this " in front of each member name. This is
a "self-identifier" that can be used to refer to the current instance of the class. Every nonstatic member must have a self-identifier, even it is not used (as in the properties above).
There is no requirement to use a particular word, just as long as it is consistent. You could
use "this" or "self" or "me" or any other word that commonly indicates a self reference.
The class signature contains the signatures for all the constructors, methods and properties
in the class. It is worth understanding what these signatures mean, because, just as with
functions, you can understand what the class does by looking at them. It is also important
because you will need to write these signatures when creating abstract methods and
interfaces.
Method signatures
Method signatures such as are very similar to the signatures for standalone functions,
except that the parameter names are part of the signature itself.
So in this case, the method signature is:
member Square : x:int -> int
628
Classes
And for comparison, the corresponding signature for a standalone function would be:
val Square : int -> int
Constructor signatures
Constructor signatures are always called new , but other than that, they look like a method
signature.
Constructor signatures always take tuple values as their only parameter. In this case the
tuple type is int * string , as you would expect. The return type is the class itself, again as
you would expect.
Again, we can compare the constructor signature with a similar standalone function:
// class constructor signature
new : intParam:int * strParam:string -> MyClass
// standalone function signature
val new : int * string -> MyClass
Property signatures
Finally, property signatures such as member Two : int are very similar to the signatures for
standalone simple values, except that no explicit value is given.
// member property
member Two : int
// standalone value
val Two : int = 2
629
Classes
type PrivateValueExample(seed) =
// private immutable value
let privateValue = seed + 1
// private mutable value
let mutable mutableValue = 42
// private function definition
let privateAddToSeed input =
seed + input
// public wrapper for private function
member this.AddToSeed x =
privateAddToSeed x
// public wrapper for mutable value
member this.SetMutableValue x =
mutableValue <- x
// test
let instance = new PrivateValueExample(42)
printf "%i" (instance.AddToSeed 2)
instance.SetMutableValue 43
630
Classes
type MutableConstructorParameter(seed) =
let mutable mutableSeed = seed
// public wrapper for mutable value
member this.SetSeed x =
mutableSeed <- x
In cases, like this, it is quite common to give the mutable value the same name as the
parameter itself, like this:
type MutableConstructorParameter2(seed) =
let mutable seed = seed // shadow the parameter
// public wrapper for mutable value
member this.SetSeed x =
seed <- x
The "do" code can also call any let-bound functions defined before it, as shown in this
example:
631
Classes
type DoPrivateFunctionExample(seed) =
let privateValue = seed + 1
// some code to be done at construction time
do printfn "hello world"
// must come BEFORE the do block that calls it
let printPrivateValue() =
do printfn "the privateValue is now %i" privateValue
// more code to be done at construction time
do printPrivateValue()
// test
new DoPrivateFunctionExample(42)
In general though, it is not best practice to call members from constructors unless you have
to (e.g. calling a virtual method). Better to call private let-bound functions, and if necessary,
have the public members call those same private functions.
632
Classes
Methods
A method definition is very like a function definition, except that it has the member keyword
and the self-identifier instead of just the let keyword.
Here are some examples:
type MethodExample() =
// standalone method
member this.AddOne x =
x + 1
// calls another method
member this.AddTwo x =
this.AddOne x |> this.AddOne
// parameterless method
member this.Pi() =
3.14159
// test
let me = new MethodExample()
printfn "%i" <| me.AddOne 42
printfn "%i" <| me.AddTwo 42
printfn "%f" <| me.Pi()
You can see that, just like normal functions, methods can have parameters, call other
methods, and be parameterless (or to be precise, take a unit parameter)
633
Classes
type TupleAndCurriedMethodExample() =
// curried form
member this.CurriedAdd x y =
x + y
// tuple form
member this.TupleAdd(x,y) =
x + y
// test
let tc = new TupleAndCurriedMethodExample()
printfn "%i" <| tc.CurriedAdd 1 2
printfn "%i" <| tc.TupleAdd(1,2)
// use partial application
let addOne = tc.CurriedAdd 1
printfn "%i" <| addOne 99
634
Classes
type LetBoundFunctions() =
let listReduce reducer list =
list |> List.reduce reducer
let reduceWithSum sum elem =
sum + elem
let sum list =
list |> listReduce reduceWithSum
// finally a public wrapper
member this.Sum = sum
// test
let lbf = new LetBoundFunctions()
printfn "Sum is %i" <| lbf.Sum [1..10]
Recursive methods
Unlike normal let-bound functions, methods that are recursive do not need the special rec
keyword. Here's the boringly familiar Fibonacci function as a method:
type MethodExample() =
// recursive method without "rec" keyword
member this.Fib x =
match x with
| 0 | 1 -> 1
| _ -> this.Fib (x-1) + this.Fib (x-2)
// test
let me = new MethodExample()
printfn "%i" <| me.Fib 10
635
Classes
type MethodExample() =
// explicit type annotation
member this.AddThree (x:int) :int =
x + 3
Properties
Properties can be divided into three groups:
Immutable properties, where there is a "get" but no "set".
Mutable properties, where there is a "get" and also a (possibly private) "set".
Write-only properties, where there is a "set" but no "get". These are so unusual that I
won't discuss them here, but the MSDN documentation describes the syntax if you ever
need it.
The syntax for immutable and mutable properties is slightly different.
For immutable properties, the syntax is simple. There is a "get" member that is similar to a
standard "let" value binding. The expression on the right-hand side of the binding can be any
standard expression, typically a combination of the constructor parameters, private let-bound
fields, and private functions.
Here's an example:
type PropertyExample(seed) =
// immutable property
// using a constructor parameter
member this.Seed = seed
For mutable properties however, the syntax is more complicated. You need to provide two
functions, one to get and one to set. This is done by using the syntax:
with get() = ...
and set(value) = ...
Here's an example:
636
Classes
type PropertyExample(seed) =
// private mutable value
let mutable myProp = seed
// mutable property
// changing a private mutable value
member this.MyProp
with get() = myProp
and set(value) = myProp <- value
To make the set function private, use the keywords private set instead.
Automatic properties
Starting in VS2012, F# supports automatic properties, which remove the requirement to
create a separate backing store for them.
To create an immutable auto property, use the syntax:
member val MyProp = initialValue
Note that in this syntax there is a new keyword val and the self-identifier has gone.
637
Classes
type PropertyExample(seed) =
// private mutable value
let mutable myProp = seed
// private function
let square x = x * x
// immutable property
// using a constructor parameter
member this.Seed = seed
// immutable property
// using a private function
member this.SeedSquared = square seed
// mutable property
// changing a private mutable value
member this.MyProp
with get() = myProp
and set(value) = myProp <- value
// mutable property with private set
member this.MyProp2
with get() = myProp
and private set(value) = myProp <- value
// automatic immutable property (in VS2012)
member val ReadOnlyAuto = 1
// automatic mutable property (in VS2012)
member val ReadWriteAuto = 1 with get,set
// test
let pe = new PropertyExample(42)
printfn "%i" <| pe.Seed
printfn "%i" <| pe.SeedSquared
printfn "%i" <| pe.MyProp
printfn "%i" <| pe.MyProp2
// try calling set
pe.MyProp <- 43 // Ok
printfn "%i" <| pe.MyProp
// try calling private set
pe.MyProp2 <- 43 // Error
638
Classes
At this point you might be confused by the difference between properties and parameterless
methods. They look identical at first glance, but there is a subtle difference -"parameterless" methods are not really parameterless; they always have a unit parameter.
Here's an example of the difference in both definition and usage:
type ParameterlessMethodExample() =
member this.MyProp = 1 // No parens!
member this.MyFunc() = 1 // Note the ()
// in use
let x = new ParameterlessMethodExample()
printfn "%i" <| x.MyProp // No parens!
printfn "%i" <| x.MyFunc() // Note the ()
You can also tell the difference by looking at the signature of the class definition
The class definition looks like this:
type ParameterlessMethodExample =
class
new : unit -> ParameterlessMethodExample
member MyFunc : unit -> int
member MyProp : int
end
The method has signature MyFunc : unit -> int and the property has signature MyProp :
int .
This is very similar to what the signatures would be if the function and property were
declared standalone, outside of any class:
let MyFunc2() = 1
let MyProp2 = 1
639
Classes
Secondary constructors
In addition to the primary constructor embedded in its declaration, a class can have
additional constructors. These are indicated by the new keyword and must call the primary
constructor as their last expression.
type MultipleConstructors(param1, param2) =
do printfn "Param1=%i Param12=%i" param1 param2
// secondary constructor
new(param1) =
MultipleConstructors(param1,-1)
// secondary constructor
new() =
printfn "Constructing..."
MultipleConstructors(13,17)
// test
let mc1 = new MultipleConstructors(1,2)
let mc2 = new MultipleConstructors(42)
let mc3 = new MultipleConstructors()
Static members
Just as in C#, classes can have static members, and this is indicated with the static
keyword. The static modifier comes before the member keyword.
Members which are static cannot have a self-identifier such as "this" because there is no
instance for them to refer to.
type StaticExample() =
member this.InstanceValue = 1
static member StaticValue = 2 // no "this"
// test
let instance = new StaticExample()
printf "%i" instance.InstanceValue
printf "%i" StaticExample.StaticValue
Static constructors
640
Classes
There is no direct equivalent of a static constructor in F#, but you can create static let-bound
values and static do-blocks that are executed when the class is first used.
type StaticConstructor() =
// static field
static let rand = new System.Random()
// static do
static do printfn "Class initialization!"
// instance member accessing static field
member this.GetRand() = rand.Next()
Accessibility of members
You can control the accessibility of a member with the standard .NET keywords public ,
private and internal . The accessibility modifiers come after the member keyword and
For properties, if the set and get have different accessibilities, you can tag each part with a
separate accessibility modifier.
641
Classes
type AccessibilityExample2() =
let mutable privateValue = 42
member this.PrivateSetProperty
with get() =
privateValue
and private set(value) =
privateValue <- value
// test
let a2 = new AccessibilityExample2();
printf "%i" a2.PrivateSetProperty // ok to read
a2.PrivateSetProperty <- 43 // not ok to write
In practice, the "public get, private set" combination that is so common in C# is not generally
needed in F#, because immutable properties can be defined more elegantly, as described
earlier.
642
Classes
However, in F#, the constructor is considered to be just another function, so you can
normally eliminate the new and call the constructor function on its own, like this:
let myInstance2 = MyClass(1,"hello")
let point = System.Drawing.Point(1,2) // works with .NET classes too!
In the case when you are creating a class that implements IDisposible , you will get a
compiler warning if you do not use new .
let sr1 = System.IO.StringReader("") // Warning
let sr2 = new System.IO.StringReader("") // OK
643
Classes
This can be a useful reminder to use the use keyword instead of the let keyword for
disposables. See the post on use for more.
We have seen many examples of member usage in the above discussion, and there's not
too much to say about it.
Remember that, as discussed above, tuple-style methods and curried-style methods can be
called in distinct ways:
type TupleAndCurriedMethodExample() =
member this.TupleAdd(x,y) = x + y
member this.CurriedAdd x y = x + y
let tc = TupleAndCurriedMethodExample()
tc.TupleAdd(1,2) // called with parens
tc.CurriedAdd 1 2 // called without parens
2 |> tc.CurriedAdd 1 // partial application
644
Inheritance
To declare that a class inherits from another class, use the syntax:
type DerivedClass(param1, param2) =
inherit BaseClass(param1)
The inherit keyword signals that DerivedClass inherits from BaseClass . In addition,
some BaseClass constructor must be called at the same time.
It might be useful to compare F# with C# at this point. Here is some C# code for a very
simple pair of classes.
public class MyBaseClass
{
public MyBaseClass(int param1)
{
this.Param1 = param1;
}
public int Param1 { get; private set; }
}
public class MyDerivedClass: MyBaseClass
{
public MyDerivedClass(int param1,int param2): base(param1)
{
this.Param2 = param2;
}
public int Param2 { get; private set; }
}
Note that the inheritance declaration class MyDerivedClass: MyBaseClass is distinct from the
constructor which calls base(param1) .
Now here is the F# version:
645
type BaseClass(param1) =
member this.Param1 = param1
type DerivedClass(param1, param2) =
inherit BaseClass(param1)
member this.Param2 = param2
// test
let derived = new DerivedClass(1,2)
printfn "param1=%O" derived.Param1
printfn "param2=%O" derived.Param2
Unlike C#, the inheritance part of the declaration, inherit BaseClass(param1) , contains both
the class to inherit from and its constructor.
So to define an abstract method, we use the signature syntax, along with the abstract
member keywords:
type BaseClass() =
abstract member Add: int -> int -> int
Notice that the equals sign has been replaced with a colon. This is what you would expect,
as the equals sign is used for binding values, while the colon is used for type annotation.
Now, if you try to compile the code above, you will get an error! The compiler will complain
that there is no implementation for the method. To fix this, you need to:
646
You can see that the default method is defined in the usual way, except for the use of
default instead of member .
One major difference between F# and C# is that in C# you can combine the abstract
definition and the default implementation into a single method, using the virtual keyword.
In F#, you cannot. You must declare the abstract method and the default implementation
separately. The abstract member has the signature, and the default has the
implementation.
647
Abstract classes
If at least one abstract method does not have a default implementation, then the entire class
is abstract, and you must indicate this by annotating it with the AbstractClass attribute.
[<AbstractClass>]
type AbstractBaseClass() =
// abstract method
abstract member Add: int -> int -> int
// abstract immutable property
abstract member Pi : float
// abstract read/write property
abstract member Area : float with get,set
If this is done, then the compiler will no longer complain about a missing implementation.
And to call a base method, use the base keyword, just as in C#.
648
type Vehicle() =
abstract member TopSpeed: unit -> int
default this.TopSpeed() = 60
type Rocket() =
inherit Vehicle()
override this.TopSpeed() = base.TopSpeed() * 10
// test
let vehicle = new Vehicle()
printfn "vehicle.TopSpeed = %i" <| vehicle.TopSpeed()
let rocket = new Rocket()
printfn "rocket.TopSpeed = %i" <| rocket.TopSpeed()
649
Interfaces
Interfaces
Interfaces are available and fully supported in F#, but there are number of important ways in
which their usage differs from what you might be used to in C#.
Defining interfaces
Defining an interface is similar to defining an abstract class. So similar, in fact, that you might
easily get them confused.
Here's an interface definition:
type MyInterface =
// abstract method
abstract member Add: int -> int -> int
// abstract immutable property
abstract member Pi : float
// abstract read/write property
abstract member Area : float with get,set
And here's the definition for the equivalent abstract base class:
[<AbstractClass>]
type AbstractBaseClass() =
// abstract method
abstract member Add: int -> int -> int
// abstract immutable property
abstract member Pi : float
// abstract read/write property
abstract member Area : float with get,set
So what's the difference? As usual, all abstract members are defined by signatures only. The
only difference seems to be the lack of the [<AbstractClass>] attribute.
But in the earlier discussion on abstract methods, we stressed that the [<AbstractClass>]
attribute was required; the compiler would complain that the methods have no
implementation otherwise. So how does the interface definition get away with it?
650
Interfaces
The answer is trivial, but subtle. The interface has no constructor. That is, it does not have
any parentheses after the interface name:
type MyInterface = // <- no parens!
That's it. Removing the parens will convert a class definition into an interface!
Implementing interfaces in F#
So, how do you implement an interface in F#? You cannot just "inherit" from it, as you would
an abstract base class. You have to provide an explicit implementation for each interface
member using the syntax interface XXX with , as shown below:
type IAddingService =
abstract member Add: int -> int -> int
type MyAddingService() =
interface IAddingService with
member this.Add x y =
x + y
interface System.IDisposable with
member this.Dispose() =
printfn "disposed"
The above code shows how the class MyAddingService explicitly implements the
IAddingService and the IDisposable interfaces. After the required interface XXX with
651
Interfaces
(As an aside, note again that MyAddingService() has a constructor, while IAddingService
does not.)
Using interfaces
So now let's try to use the adding service interface:
let mas = new MyAddingService()
mas.Add 1 2 // error
Immediately, we run into an error. It appears that the instance does not implement the Add
method at all. Of course, what this really means is that we must cast it to the interface first
using the :> operator:
// cast to the interface
let mas = new MyAddingService()
let adder = mas :> IAddingService
adder.Add 1 2 // ok
This might seem incredibly awkward, but in practice it is not a problem as in most cases the
casting is done implicitly for you.
For example, you will typically be passing an instance to a function that specifies an
interface parameter. In this case, the casting is done automatically:
// function that requires an interface
let testAddingService (adder:IAddingService) =
printfn "1+2=%i" <| adder.Add 1 2 // ok
let mas = new MyAddingService()
testAddingService mas // cast automatically
And in the special case of IDisposable , the use keyword will also automatically cast the
instance as needed:
let testDispose =
use mas = new MyAddingService()
printfn "testing"
// Dispose() is called here
652
Object expressions
Object expressions
So as we saw in the previous post, implementing interfaces in F# is a bit more awkward than
in C#. But F# has a trick up its sleeve, called "object expressions".
With object expressions, you can implement an interface on-the-fly, without having to create
a class.
If you execute this code, you will see the output below. You can see that Dispose() is
indeed being called when the objects go out of scope.
653
Object expressions
We can take the same approach with the IAddingService and create one on the fly as well.
let makeAdder id =
{ new IAddingService with
member this.Add x y =
printfn "Adder%i is adding" id
let result = x + y
printfn "%i + %i = %i" x y result
result
}
let testAdders =
for i in [1..3] do
let adder = makeAdder i
let result = adder.Add i i
() //ignore result
Object expressions are extremely convenient, and can greatly reduce the number of classes
you need to create if you are interacting with an interface heavy library.
654
In this series, you'll learn what computation expressions are, some common patterns, and
how to make your own. In the process, we'll also look at continuations, the bind function,
wrapper types, and more.
Computation expressions: Introduction. Unwrapping the enigma....
Understanding continuations. How 'let' works behind the scenes.
Introducing 'bind'. Steps towards creating our own 'let!' .
Computation expressions and wrapper types. Using types to assist the workflow.
More on wrapper types. We discover that even lists can be wrapper types.
Implementing a builder: Zero and Yield. Getting started with the basic builder methods.
Implementing a builder: Combine. How to return multiple values at once.
Implementing a builder: Delay and Run. Controlling when functions execute.
Implementing a builder: Overloading. Stupid method tricks.
Implementing a builder: Adding laziness. Delaying a workflow externally.
Implementing a builder: The rest of the standard methods. Implementing While, Using,
and exception handling.
655
Background
Computation expressions seem to have a reputation for being abstruse and difficult to
understand.
On one hand, they're easy enough to use. Anyone who has written much F# code has
certainly used standard ones like seq{...} or async{...} .
But how do you make a new one of these things? How do they work behind the scenes?
Unfortunately, many explanations seem to make things even more confusing. There seems
to be some sort of mental bridge that you have to cross. Once you are on the other side, it is
all obvious, but to someone on this side, it is baffling.
If we turn for guidance to the official MSDN documention, it is explicit, but quite unhelpful to
a beginner.
For example, it says that when you see the following code within a computation expression:
{| let! pattern = expr in cexpr |}
656
Simple enough.
But it is annoying to have to explicitly write all the log statements each time. Is there a way to
hide them?
Funny you should ask... A computation expression can do that. Here's one that does exactly
the same thing.
First we define a new type called LoggingBuilder :
type LoggingBuilder() =
let log p = printfn "expression is %A" p
member this.Bind(x, f) =
log x
f x
member this.Return(x) =
x
657
Don't worry about what the mysterious Bind and Return are for yet -- they will be
explained soon.
Next we create an instance of the type, logger in this case.
let logger = new LoggingBuilder()
So with this logger value, we can rewrite the original logging example like this:
let loggedWorkflow =
logger
{
let! x = 42
let! y = 43
let! z = x + y
return z
}
If you run this, you get exactly the same output, but you can see that the use of the
logger{...} workflow has allowed us to hide the repetitive code.
Safe division
Now let's look at an old chestnut.
Say that we want to divide a series of numbers, one after another, but one of them might be
zero. How can we handle it? Throwing an exception is ugly. Sounds like a good match for
the option type though.
First we need to create a helper function that does the division and gives us back an int
option . If everything is OK, we get a Some and if the division fails, we get a None .
Then we can chain the divisions together, and after each division we need to test whether it
failed or not, and keep going only if it was successful.
Here's the helper function first, and then the main workflow:
let divideBy bottom top =
if bottom = 0
then None
else Some(top/bottom)
Note that I have put the divisor first in the parameter list. This is so we can write an
expression like 12 |> divideBy 3 , which makes chaining easier.
658
Let's put it to use. Here is a workflow that attempts to divide a starting number three times:
let divideByWorkflow init x y z =
let a = init |> divideBy x
match a with
| None -> None // give up
| Some a' -> // keep going
let b = a' |> divideBy y
match b with
| None -> None // give up
| Some b' -> // keep going
let c = b' |> divideBy z
match c with
| None -> None // give up
| Some c' -> // keep going
//return
Some c'
The bad workflow fails on the third step and returns None for the whole thing.
It is very important to note that the entire workflow has to return an int option as well. It
can't just return an int because what would it evaluate to in the bad case? And can you
see how the type that we used "inside" the workflow, the option type, has to be the same
type that comes out finally at the end. Remember this point -- it will crop up again later.
Anyway, this continual testing and branching is really ugly! Does turning it into a computation
expression help?
Once more we define a new type ( MaybeBuilder ) and make an instance of the type
( maybe ).
type MaybeBuilder() =
member this.Bind(x, f) =
match x with
| None -> None
| Some a -> f a
member this.Return(x) =
Some x
let maybe = new MaybeBuilder()
659
I have called this one MaybeBuilder rather than divideByBuilder because the issue of
dealing with option types this way, using a computation expression, is quite common, and
maybe is the standard name for this thing.
So now that we have defined the maybe workflow, let's rewrite the original code to use it.
let divideByWorkflow init x y z =
maybe
{
let! a = init |> divideBy x
let! b = a |> divideBy y
let! c = b |> divideBy z
return c
}
Much, much nicer. The maybe expression has completely hidden the branching logic!
And if we test it we get the same result as before:
let good = divideByWorkflow 12 3 2 1
let bad = divideByWorkflow 12 3 0 1
660
661
662
Lots of calls to BeginGetResponse and EndGetResponse , and the use of nested lambdas,
makes this quite complicated to understand. The important code (in this case, just print
statements) is obscured by the callback logic.
In fact, managing this cascading approach is always a problem in code that requires a chain
of callbacks; it has even been called the "Pyramid of Doom" (although none of the solutions
are very elegant, IMO).
Of course, we would never write that kind of code in F#, because F# has the async
computation expression built in, which both simplifies the logic and flattens the code.
open System.Net
let req1 = HttpWebRequest.Create("http://tryfsharp.org")
let req2 = HttpWebRequest.Create("http://google.com")
let req3 = HttpWebRequest.Create("http://bing.com")
async {
use! resp1 = req1.AsyncGetResponse()
printfn "Downloaded %O" resp1.ResponseUri
use! resp2 = req2.AsyncGetResponse()
printfn "Downloaded %O" resp2.ResponseUri
use! resp3 = req3.AsyncGetResponse()
printfn "Downloaded %O" resp3.ResponseUri
} |> Async.RunSynchronously
We'll see exactly how the async workflow is implemented later in this series.
Summary
So we've seen some very simple examples of computation expressions, both "before" and
"after", and they are quite representative of the kinds of problems that computation
expressions are useful for.
In the logging example, we wanted to perform some side-effect between each step.
In the safe division example, we wanted to handle errors elegantly so that we could
focus on the happy path.
In the multiple dictionary lookup example, we wanted to return early with the first
success.
And finally, in the async example, we wanted to hide the use of callbacks and avoid the
"pyramid of doom".
663
What all the cases have in common is that the computation expression is "doing something
behind the scenes" between each expression.
If you want a bad analogy, you can think of a computation expression as somewhat like a
post-commit hook for SVN or git, or a database trigger that gets called on every update. And
really, that's all that a computation expression is: something that allows you to sneak your
own code in to be called in the background, which in turn allows you to focus on the
important code in the foreground.
Why are they called "computation expressions"? Well, it's obviously some kind of
expression, so that bit is obvious. I believe that the F# team did originally want to call it
"expression-that-does-something-in-the-background-between-each-let" but for some reason,
people thought that was a bit unwieldy, so they settled on the shorter name "computation
expression" instead.
And as to the difference between a "computation expression" and a "workflow", I use
"computation expression" to mean the {...} and let! syntax, and reserve "workflow" for
particular implementations where appropriate. Not all computation expression
implementations are workflows. For example, it is appropriate to talk about the "async
workflow" or the "maybe workflow", but the "seq workflow" doesn't sound right.
In other words, in the following code, I would say that maybe is the workflow we are using,
and the particular chunk of code { let! a = .... return c } is the computation expression.
maybe
{
let! a = x |> divideBy y
let! b = a |> divideBy w
let! c = b |> divideBy z
return c
}
You probably want to start creating your own computation expressions now, but first we
need to take a short detour into continuations. That's up next.
Update on 2015-01-11: I have removed the counting example that used a "state"
computation expression. It was too confusing and distracted from the main concepts.
664
Understanding continuations
Understanding continuations
In the previous post we saw how some complex code could be condensed using
computation expressions.
Here's the code before using a computation expression:
The use of let! rather than a normal let is important. Can we emulate this ourselves so
we can understand what is going on? Yes, but we need to understand continuations first.
Continuations
In imperative programming, we have the concept of "returning" from a function. When you
call a function, you "go in", and then you "come out", just like pushing and popping a stack.
Here is some typical C# code which works like this. Notice the use of the return keyword.
665
Understanding continuations
You've seen this a million times, but there is a subtle point about this approach that you
might not have considered: the called function always decides what to do.
For example, the implemention of Divide has decided that it is going to throw an exception.
But what if I don't want an exception? Maybe I want a nullable<int> , or maybe I am going
to display it on a screen as "#DIV/0". Why throw an exception that I am immediately going to
have to catch? In other words, why not let the caller decide what should happen, rather the
callee.
Similarly in the IsEven example, what am I going to do with the boolean return value?
Branch on it? Or maybe print it in a report? I don't know, but again, rather than returning a
boolean that the caller has to deal with, why not let the caller tell the callee what to do next?
So this is what continuations are. A continuation is simply a function that you pass into
another function to tell it what to do next.
Here's the same C# code rewritten to allow the caller to pass in functions which the callee
uses to handle each case. If it helps, you can think of this as somewhat analogous to a
visitor pattern. Or maybe not.
666
Understanding continuations
Note that the C# functions have been changed to return a generic T now, and both
continuations are a Func that returns a T .
Well, passing in lots of Func parameters always looks pretty ugly in C#, so it is not done
very often. But passing functions is easy in F#, so let's see how this code ports over.
Here's the "before" code:
let divide top bottom =
if (bottom=0)
then invalidOp "div by 0"
else (top/bottom)
let isEven aNumber =
aNumber % 2 = 0
667
Understanding continuations
A few things to note. First, you can see that I have put the extra functions ( ifZero , etc) first
in the parameter list, rather than last, as in the C# example. Why? Because I am probably
going to want to use partial application.
And also, in the isEven example, I wrote aNumber |> ifEven and aNumber |> ifOdd . This
makes it clear that we are piping the current value into the continuation and the continuation
is always the very last step to be evaluated. We will be using this exact same pattern later in
this post, so make sure you understand what is going on here.
Continuation examples
With the power of continuations at our disposal, we can use the same divide function in
three completely different ways, depending on what the caller wants.
Here are three scenarios we can create quickly:
pipe the result into a message and print it,
convert the result to an option using None for the bad case and Some for the good
case,
or throw an exception in the bad case and just return the result in the good case.
668
Understanding continuations
Notice that with this approach, the caller never has to catch an exception from divide
anywhere. The caller decides whether an exception will be thrown, not the callee. So not
only has the divide function become much more reusable in different contexts, but the
cyclomatic complexity has just dropped a level as well.
The same three scenarios can be applied to the isEven implementation:
669
Understanding continuations
In this case, the benefits are subtler, but the same: the caller never had to handle booleans
with an if/then/else anywhere. There is less complexity and less chance of error.
It might seem like a trivial difference, but by passing functions around like this, we can use
all our favorite functional techniques such as composition, partial application, and so on.
We have also met continuations before, in the series on designing with types. We saw that
their use enabled the caller to decide what would happen in case of possible validation
errors in a constructor, rather than just throwing an exception.
670
Understanding continuations
The success function takes the email as a parameter and the error function takes a string.
Both functions must return the same type, but the type is up to you.
And here is a simple example of the continuations in use. Both functions do a printf, and
return nothing (i.e. unit).
// setup the functions
let success (EmailAddress s) = printfn "success creating email %s" s
let failure msg = printfn "error creating email: %s" msg
let createEmail = CreateEmailAddressWithContinuations success failure
// test
let goodEmail = createEmail "x@example.com"
let badEmail = createEmail "example.com"
In continuation passing style, on the other hand, you end up with a chain of functions, like
this:
671
Understanding continuations
really means:
let x = someExpression in [an expression involving x]
And then every time you see the x in the second expression (the body expression),
substitute it with the first expression ( someExpression ).
So for example, the expression:
672
Understanding continuations
let x = 42
let y = 43
let z = x + y
Doesn't this look awfully like a let to you? Here is a let and a lambda side by side:
// let
let x = someExpression in [an expression involving x]
// pipe a value into a lambda
someExpression |> (fun x -> [an expression involving x] )
They both have an x , and a someExpression , and everywhere you see x in the body of
the lambda you replace it with someExpression . Yes, the x and the someExpression are
reversed in the lambda case, but otherwise it is basically the same thing as a let .
So, using this technique, we can rewrite the original example in this style:
42 |> (fun x ->
43 |> (fun y ->
x + y |> (fun z ->
z)))
When it is written this way, you can see that we have transformed the let style into a
continuation passing style!
In the first line we have a value 42 -- what do we want to do with it? Let's pass it into a
continuation, just as we did with the isEven function earlier. And in the context of the
673
Understanding continuations
Note that we are passing both parameters in at once using a tuple rather than as two distinct
parameters separated by whitespace. They will always come as a pair.
So, with this pipeInto function we can then rewrite the example once more as:
pipeInto (42, fun x ->
pipeInto (43, fun y ->
pipeInto (x + y, fun z ->
z)))
You might be thinking: so what? Why bother to wrap the pipe into a function?
The answer is that we can add extra code in the pipeInto function to do stuff "behine the
scenes", just as in a computation expression.
674
Understanding continuations
This is exactly the same output as we had in the earlier implementations. We have created
our own little computation expression workflow!
If we compare this side by side with the computation expression version, we can see that
our homebrew version is very similar to the let! , except that we have the parameters
reversed, and we have the explicit arrow for the continuation.
675
Understanding continuations
You should see now that this "stepped" style is an obvious clue that we really should be
using continuations.
Let's see if we can add extra code to pipeInto to do the matching for us. The logic we want
is:
If the someExpression parameter is None , then don't call the continuation lambda.
If the someExpression parameter is Some , then do call the continuation lambda,
passing in the contents of the Some .
Here it is:
let pipeInto (someExpression,lambda) =
match someExpression with
| None ->
None
| Some x ->
x |> lambda
With this new version of pipeInto we can rewrite the original code like this:
676
Understanding continuations
let divideByWorkflow x y w z =
let a = x |> divideBy y
pipeInto (a, fun a' ->
let b = a' |> divideBy w
pipeInto (b, fun b' ->
let c = b' |> divideBy z
pipeInto (c, fun c' ->
Some c' //return
)))
Now we can relabel a' as just a , and so on, and we can also remove the stepped
indentation, so that we get this:
let divideByResult x y w z =
pipeInto (x |> divideBy y, fun a ->
pipeInto (a |> divideBy w, fun b ->
pipeInto (b |> divideBy z, fun c ->
Some c //return
)))
Finally, we'll create a little helper function called return' to wrap the result in an option.
Putting it all together, the code looks like this:
677
Understanding continuations
Again, if we compare this side by side with the computation expression version, we can see
that our homebrew version is identical in meaning. Only the syntax is different.
Summary
In this post, we talked about continuations and continuation passing style, and how we can
think of let as a nice syntax for doing continuations behind scenes.
So now we have everything we need to start creating our own version of let . In the next
post, we'll put this knowledge into practice.
678
Understanding continuations
679
Introducing 'bind'
Introducing 'bind'
In the last post we talked about how we can think of let as a nice syntax for doing
continuations behind scenes. And we introduced a pipeInto function that allowed us to add
hooks into the continuation pipeline.
Now we are ready to look at our first builder method, Bind , which formalizes this approach
and is the core of any computation expression.
And here's the Bind method documentation, along with a real example:
// documentation
builder.Bind(expr, (fun pattern -> {| cexpr |}))
// real example
builder.Bind(43, (fun x -> some expression))
The parameter of the lambda ( x ) is bound to the expression passed in as the first
parameter. (In this case at least. More on this later.)
The parameters of Bind are reversed from the order they are in let! .
So in other words, if we chain a number of let! expressions together like this:
let! x = 1
let! y = 2
let! z = x + y
680
Introducing 'bind'
I think you can see where we are going with this by now.
Indeed, our pipeInto function is exactly the same as the Bind method.
This is a key insight: computation expressions are just a way to create nice syntax for
something that we could do ourselves.
By the way, this symbol ">>=" is the standard way of writing bind as an infix operator. If you
ever see it used in other F# code, that is probably what it represents.
Going back to the safe divide example, we can now write the workflow on one line, like this:
let divideByWorkflow x y w z =
x |> divideBy y >>= divideBy w >>= divideBy z
You might be wondering exactly how this is different from normal piping or composition? It's
not immediately obvious.
The answer is twofold:
First, the bind function has extra customized behavior for each situation. It is not a
generic function, like pipe or composition.
681
Introducing 'bind'
Second, the input type of the value parameter ( m above) is not necessarily the same
as the output type of the function parameter ( f above), and so one of the things that
bind does is handle this mismatch elegantly so that functions can be chained.
As we will see in the next post, bind generally works with some "wrapper" type. The value
parameter might be of WrapperType<TypeA> , and then the signature of the function parameter
of bind function is always TypeA -> WrapperType<TypeB> .
In the particular case of the bind for safe divide, the wrapper type is Option . The type of
the value parameter ( m above) is Option<int> and the signature of the function parameter
( f above) is int -> Option<int> .
To see bind used in a different context, here is an example of the logging workflow
expressed using a infix bind function:
let (>>=) m f =
printfn "expression is %A" m
f m
let loggingWorkflow =
1 >>= (+) 2 >>= (*) 42 >>= id
In this case, there is no wrapper type. Everything is an int . But even so, bind has the
special behavior that performs the logging behind the scenes.
682
Introducing 'bind'
There is a moral in this -- don't be too hasty to write your own functions. There may well be
library functions that you can reuse.
Here is the "maybe" workflow, rewritten to use Option.bind :
type MaybeBuilder() =
member this.Bind(m, f) = Option.bind f m
member this.Return(x) = Some x
683
Introducing 'bind'
module DivideByExplicit =
let divideBy bottom top =
if bottom = 0
then None
else Some(top/bottom)
let divideByWorkflow x y w z =
let a = x |> divideBy y
match a with
| None -> None // give up
| Some a' -> // keep going
let b = a' |> divideBy w
match b with
| None -> None // give up
| Some b' -> // keep going
let c = b' |> divideBy z
match c with
| None -> None // give up
| Some c' -> // keep going
//return
Some c'
// test
let good = divideByWorkflow 12 3 2 1
let bad = divideByWorkflow 12 3 0 1
684
Introducing 'bind'
685
Introducing 'bind'
Bind functions turn out to be very powerful. In the next post we'll see that combining bind
with wrapper types creates an elegant way of passing extra information around in the
background.
and then create your own computation expression builder class so that you can use it in a
workflow, as shown below.
let stringAddWorkflow x y z =
yourWorkflow
{
let! a = strToInt x
let! b = strToInt y
let! c = strToInt z
return a + b + c
}
// test
let good = stringAddWorkflow "12" "3" "2"
let bad = stringAddWorkflow "12" "xyz" "2"
And then with these functions, you should be able to write code like this:
let good = strToInt "1" >>= strAdd "2" >>= strAdd "3"
let bad = strToInt "1" >>= strAdd "xyz" >>= strAdd "3"
686
Introducing 'bind'
Summary
Here's a summary of the points covered in this post:
Computation expressions provide a nice syntax for continuation passing, hiding the
chaining logic for us.
bind is the key function that links the output of one step to the input of the next step.
The symbol >>= is the standard way of writing bind as an infix operator.
687
Another example
Let's look at another example. Say that we are accessing a database, and we want to
capture the result in a Success/Error union type, like this:
type DbResult<'a> =
| Success of 'a
| Error of string
688
We then use this type in our database access methods. Here are some very simple stubs to
give you an idea of how the DbResult type might be used:
let getCustomerId name =
if (name = "")
then Error "getCustomerId failed"
else Success "Cust42"
let getLastOrderForCustomer custId =
if (custId = "")
then Error "getLastOrderForCustomer failed"
else Success "Order123"
let getLastProductForOrder orderId =
if (orderId = "")
then Error "getLastProductForOrder failed"
else Success "Product456"
Now let's say we want to chain these calls together. First get the customer id from the name,
and then get the order for the customer id, and then get the product from the order.
Here's the most explicit way of doing it. As you can see, we have to have pattern matching
at each step.
let product =
let r1 = getCustomerId "Alice"
match r1 with
| Error _ -> r1
| Success custId ->
let r2 = getLastOrderForCustomer custId
match r2 with
| Error _ -> r2
| Success orderId ->
let r3 = getLastProductForOrder orderId
match r3 with
| Error _ -> r3
| Success productId ->
printfn "Product is %s" productId
r3
Really ugly code. And the top-level flow has been submerged in the error handling logic.
Computation expressions to the rescue! We can write one that handles the branching of
Success/Error behind the scenes:
689
type DbResultBuilder() =
member this.Bind(m, f) =
match m with
| Error _ -> m
| Success a ->
printfn "\tSuccessful: %s" a
f a
member this.Return(x) =
Success x
let dbresult = new DbResultBuilder()
And with this workflow, we can focus on the big picture and write much cleaner code:
let product' =
dbresult {
let! custId = getCustomerId "Alice"
let! orderId = getLastOrderForCustomer custId
let! productId = getLastProductForOrder orderId
printfn "Product is %s" productId
return productId
}
printfn "%A" product'
And if there are errors, the workflow traps them nicely and tells us where the error was, as in
this example below:
let product'' =
dbresult {
let! custId = getCustomerId "Alice"
let! orderId = getLastOrderForCustomer "" // error!
let! productId = getLastProductForOrder orderId
printfn "Product is %s" productId
return productId
}
printfn "%A" product''
690
These are not just special cases. In fact, every computation expression must have an
associated wrapper type. And the wrapper type is often designed specifically to go hand-inhand with the workflow that we want to manage.
The example above demonstrates this clearly. The DbResult type we created is more than
just a simple type for return values; it actually has a critical role in the workflow by "storing"
the current state of the workflow, and whether it is succeeding or failing at each step. By
using the various cases of the type itself, the dbresult workflow can manage the transitions
for us, hiding them from view and enabling us to focus on the big picture.
We'll learn how to design a good wrapper type later in the series, but first let's look at how
they are manipulated.
In other words, for some type T , the Return method just wraps it in the wrapper type.
Note: In signatures, the wrapper type is normally called M , so M<int> is the wrapper type
applied to int and M<string> is the wrapper type applied to string , and so on.
And we've seen two examples of this usage. The maybe workflow returns a Some , which is
an option type, and the dbresult workflow returns Success , which is part of the DbResult
type.
// return for the maybe workflow
member this.Return(x) =
Some x
// return for the dbresult workflow
member this.Return(x) =
Success x
691
It looks complicated, so let's break it down. It takes a tuple M<'T> * ('T -> M<'U>) and
returns a M<'U> , where M<'U> means the wrapper type applied to type U .
The tuple in turn has two parts:
M<'T> is a wrapper around type T , and
'T -> M<'U> is a function that takes a unwrapped T and creates a wrapped U .
Look over this code and make sure that you understand why these methods do indeed
follow the pattern described above.
Finally, a picture is always useful. Here is a diagram of the various types and functions:
For Bind , we start with a wrapped value ( m here), unwrap it to a raw value of type
692
T , and then (maybe) apply the function f to it to get a wrapped value of type U .
For Return , we start with a value ( x here), and simply wrap it.
To see this, we can revisit the example above, but rather than using strings everywhere, we
will create special types for the customer id, order id, and product id. This means that each
step in the chain will be using a different type.
We'll start with the types again, this time defining CustomerId , etc.
type DbResult<'a> =
| Success of 'a
| Error of string
type CustomerId = CustomerId of string
type OrderId = OrderId of int
type ProductId = ProductId of string
The code is almost identical, except for the use of the new types in the Success line.
let getCustomerId name =
if (name = "")
then Error "getCustomerId failed"
else Success (CustomerId "Cust42")
let getLastOrderForCustomer (CustomerId custId) =
if (custId = "")
then Error "getLastOrderForCustomer failed"
else Success (OrderId 123)
let getLastProductForOrder (OrderId orderId) =
if (orderId = 0)
then Error "getLastProductForOrder failed"
else Success (ProductId "Product456")
693
let product =
let r1 = getCustomerId "Alice"
match r1 with
| Error e -> Error e
| Success custId ->
let r2 = getLastOrderForCustomer custId
match r2 with
| Error e -> Error e
| Success orderId ->
let r3 = getLastProductForOrder orderId
match r3 with
| Error e -> Error e
| Success productId ->
printfn "Product is %A" productId
r3
694
let product' =
dbresult {
let! custId = getCustomerId "Alice"
let! orderId = getLastOrderForCustomer custId
let! productId = getLastProductForOrder orderId
printfn "Product is %A" productId
return productId
}
printfn "%A" product'
695
If you have used the async workflow, you probably have done this already, because an
async workflow typically contains other asyncs embedded in it:
let a =
async {
let! x = doAsyncThing // nested workflow
let! y = doNextAsyncThing x // nested workflow
return x + y
}
Introducing "ReturnFrom"
We have been using return as a way of easily wrapping up an unwrapped return value.
But sometimes we have a function that already returns a wrapped value, and we want to
return it directly. return is no good for this, because it requires an unwrapped type as input.
The solution is a variant on return called return! , which takes a wrapped type as input
and returns it.
696
The corresponding method in the "builder" class is called ReturnFrom . Typically the
implementation just returns the wrapped type "as is" (although of course, you can always
add extra logic behind the scenes).
Here is a variant on the "maybe" workflow to show how it can be used:
type MaybeBuilder() =
member this.Bind(m, f) = Option.bind f m
member this.Return(x) =
printfn "Wrapping a raw value into an option"
Some x
member this.ReturnFrom(m) =
printfn "Returning an option directly"
m
let maybe = new MaybeBuilder()
For a more realistic example, here is return! used in conjunction with divideBy :
// using return
maybe
{
let! x = 12 |> divideBy 3
let! y = x |> divideBy 2
return y // return an int
}
// using return!
maybe
{
let! x = 12 |> divideBy 3
return! x |> divideBy 2 // return an Option
}
Summary
This post introduced wrapper types and how they related to Bind , Return and
ReturnFrom , the core methods of any builder class.
697
In the next post, we'll continue to look at wrapper types, including using lists as wrapper
types.
698
But what about other generic types like List<T> or IEnumerable<T> ? Surely they can't be
used? Actually, yes, they can be used! We'll see how shortly.
case), and the second part of the tuple is a function that takes an unwrapped type and
converts it to a wrapped type. In this case, that would be int -> string .
Return takes an unwrapped type ( int in this case) and converts it to a wrapped type.
699
The implementation of the "rewrapping" function, int -> string , is easy. It is just
"toString" on an int.
The bind function has to unwrap a string to an int, and then pass it to the function. We
can use int.Parse for that.
But what happens if the bind function can't unwrap a string, because it is not a valid
number? In this case, the bind function must still return a wrapped type (a string), so we
can just return a string such as "error".
Here's the implementation of the builder class:
type StringIntBuilder() =
member this.Bind(m, f) =
let b,i = System.Int32.TryParse(m)
match b,i with
| false,_ -> "error"
| true,i -> f i
member this.Return(x) =
sprintf "%i" x
let stringint = new StringIntBuilder()
That looks really good -- we can treat strings as ints inside our workflow!
But hold on, there is a problem.
700
Let's say we give the workflow an input, unwrap it (with let! ) and then immediately rewrap
it (with return ) without doing anything else. What should happen?
let g1 = "99"
let g2 = stringint {
let! i = g1
return i
}
printfn "g1=%s g2=%s" g1 g2
No problem. The input g1 and the output g2 are the same value, as we would expect.
But what about the error case?
let b1 = "xxx"
let b2 = stringint {
let! i = b1
return i
}
printfn "b1=%s b2=%s" b1 b2
In this case we have got some unexpected behavior. The input b1 and the output b2 are
not the same value. We have introduced an inconsistency.
Would this be a problem in practice? I don't know. But I would avoid it and use a different
approach, like options, that are consistent in all cases.
701
The answer is no, they should not behave differently. The only difference is that in the
second example, the unwrapped value has been refactored away and the wrapped value is
returned directly.
But as we just saw in the previous section, you can get inconsistencies if you are not careful.
So, any implementation you create should be sure to follow some standard rules, which are:
Rule 1: If you start with an unwrapped value, and then you wrap it (using return ),
then unwrap it (using bind ), you should always get back the original unwrapped
value.
This rule and the next are about not losing information as you wrap and unwrap the values.
Obviously, a sensible thing to ask, and required for refactoring to work as expected.
In code, this would be expressed as something like this:
myworkflow {
let originalUnwrapped = something
// wrap it
let wrapped = myworkflow { return originalUnwrapped }
// unwrap it
let! newUnwrapped = wrapped
// assert they are the same
assertEqual newUnwrapped originalUnwrapped
}
Rule 2: If you start with a wrapped value, and then you unwrap it (using bind ), then
wrap it (using return ), you should always get back the original wrapped value.
This is the rule that the stringInt workflow broke above. As with rule 1, this should
obviously be a requirement.
In code, this would be expressed as something like this:
702
myworkflow {
let originalWrapped = something
let newWrapped = myworkflow {
// unwrap it
let! unwrapped = originalWrapped
// wrap it
return unwrapped
}
// assert they are the same
assertEqual newWrapped originalWrapped
}
Rule 3: If you create a child workflow, it must produce the same result as if you had
"inlined" the logic in the main workflow.
This rule is required for composition to behave properly, and again, "extraction" refactoring
will only work correctly if this is true.
In general, you will get this for free if you follow some guidelines (which will be explained in a
later post).
In code, this would be expressed as something like this:
// inlined
let result1 = myworkflow {
let! x = originalWrapped
let! y = f x // some function on x
return! g y // some function on y
}
// using a child workflow ("extraction" refactoring)
let result2 = myworkflow {
let! y = myworkflow {
let! x = originalWrapped
return! f x // some function on x
}
return! g y // some function on y
}
// rule
assertEqual result1 result2
I said earlier that types like List<T> or IEnumerable<T> can be used as wrapper types. But
how can this be? There is no one-to-one correspondence between the wrapper type and the
unwrapped type!
This is where the "wrapper type" analogy becomes a bit misleading. Instead, let's go back to
thinking of bind as a way of connecting the output of one expression with the input of
another.
As we have seen, the bind function "unwraps" the type, and applies the continuation
function to the unwrapped value. But there is nothing in the definition that says that there
has to be only one unwrapped value. There is no reason that we can't apply the continuation
function to each item of the list in turn.
In other words, we should be able to write a bind that takes a list and a continuation
function, where the continuation function processes one element at a time, like this:
bind( [1;2;3], fun elem -> // expression using a single element )
And with this concept, we should be able to chain some binds together like this:
let add =
bind( [1;2;3], fun elem1 ->
bind( [10;11;12], fun elem2 ->
elem1 + elem2
))
But we've missed something important. The continuation function passed into bind is
required to have a certain signature. It takes an unwrapped type, but it produces a wrapped
type.
In other words, the continuation function must always create a new list as its result.
bind( [1;2;3], fun elem -> // expression using a single element, returning a list )
And the chained example would have to be written like this, with the elem1 + elem2 result
turned into a list:
let add =
bind( [1;2;3], fun elem1 ->
bind( [10;11;12], fun elem2 ->
[elem1 + elem2] // a list!
))
704
So the logic for our bind method now looks like this:
let bind(list,f) =
// 1) for each element in list, apply f
// 2) f will return a list (as required by its signature)
// 3) the result is a list of lists
We have another issue now. Bind itself must produce a wrapped type, which means that
the "list of lists" is no good. We need to turn them back into a simple "one-level" list.
But that is easy enough -- there is a list module function that does just that, called concat .
So putting it together, we have this:
let bind(list,f) =
list
|> List.map f
|> List.concat
let added =
bind( [1;2;3], fun elem1 ->
bind( [10;11;12], fun elem2 ->
// elem1 + elem2 // error.
[elem1 + elem2] // correctly returns a list.
))
Now that we understand how the bind works on its own, we can create a "list workflow".
Bind applies the continuation function to each element of the passed in list, and then
flattens the resulting list of lists into a one-level list. List.collect is a library function
that does exactly that.
Return converts from unwrapped to wrapped. In this case, that just means wrapping a
705
let added =
listWorkflow {
let! i = [1;2;3]
let! j = [10;11;12]
return i+j
}
printfn "added=%A" added
let multiplied =
listWorkflow {
let! i = [1;2;3]
let! j = [10;11;12]
return i*j
}
printfn "multiplied=%A" multiplied
And the results show that every element in the first collection has been combined with every
element in the second collection:
val added : int list = [11; 12; 13; 12; 13; 14; 13; 14; 15]
val multiplied : int list = [10; 11; 12; 20; 22; 24; 30; 33; 36]
That's quite amazing really. We have completely hidden the list enumeration logic, leaving
just the workflow itself.
Both variants mean exactly the same thing, they just look different.
To enable the F# compiler to do this, we need to add a For method to our builder class. It
generally has exactly the same implementation as the normal Bind method, but is required
to accept a sequence type.
706
type ListWorkflowBuilder() =
member this.Bind(list, f) =
list |> List.collect f
member this.Return(x) =
[x]
member this.For(list, f) =
this.Bind(list, f)
let listWorkflow = new ListWorkflowBuilder()
to convert from a query expression syntax like from element in collection ... to actual
method calls behine the scenes.
In F#, as we saw, the bind uses the List.collect function. The equivalent of
List.collect in LINQ is the SelectMany extension method. And once you understand how
SelectMany works, you can implement the same kinds of queries yourself. Jon Skeet has
707
The short answer to this is that you can treat any type as its own "wrapper". But there is
another, deeper way to understand this.
Let's step back and consider what a wrapper type definition like List<T> really means.
If you have a type such as List<T> , it is in fact not a "real" type at all. List<int> is a real
type, and List<string> is a real type. But List<T> on its own is incomplete. It is missing
the parameter it needs to become a real type.
One way to think about List<T> is that it is a function, not a type. It is a function in the
abstract world of types, rather than the concrete world of normal values, but just like any
function it maps values to other values, except in this case, the input values are types (say
int or string ) and the output values are other types ( List<int> and List<string> ).
And like any function it takes a parameter, in this case a "type parameter". Which is why the
concept that .NET developers call "generics" is known as "parametric polymorphism" in
computer science terminology.
Once we grasp the concept of functions that generate one type from another type (called
"type constructors"), we can see that what we really mean by a "wrapper type" is just a type
constructor.
But if a "wrapper type" is just a function that maps one type to another type, surely a function
that maps a type to the same type fits into this category? And indeed it does. The "identity"
function for types fits our definition and can be used as a wrapper type for computation
expressions.
Going back to some real code then, we can define the "identity workflow" as the simplest
possible implementation of a workflow builder.
type IdentityBuilder() =
member this.Bind(m, f) = f m
member this.Return(x) = x
member this.ReturnFrom(x) = x
let identity = new IdentityBuilder()
let result = identity {
let! x = 1
let! y = 2
return x + y
}
With this in place, you can see that the logging example discussed earlier is just the identity
workflow with some logging added in.
708
Summary
Another long post, and we covered a lot of topics, but I hope that the role of wrapper types is
now clearer. We will see how the wrapper types can be used in practice when we come to
look at common workflows such as the "writer workflow" and the "state workflow" later in this
series.
Here's a summary of the points covered in this post:
A major use of computation expressions is to unwrap and rewrap values that are stored
in some sort of wrapper type.
You can easily compose computation expressions, because the output of a Return can
be fed to the input of a Bind .
Every computation expression must have an associated wrapper type.
Any type with a generic parameter can be used as a wrapper type, even lists.
When creating workflows, you should ensure that your implementation conforms to the
three sensible rules about wrapping and unwrapping and composition.
709
710
used to implement some features that might not be obvious if you work from the
documentation alone. We will show an example of this later.
If you want more detailed documentation, there are two sources I can recommend. For an
detailed overview of the concepts behind computation expressions, a great resource is the
paper "The F# Expression Zoo" by Tomas Petricek and Don Syme. And for the most
accurate up-to-date technical documentation, you should read the F# language specification,
which has a section on computation expressions.
As I have in the earlier posts in this series, I will continue to use "unwrapped" and "wrapped"
to describe the relationship between these types, but as we move forward these terms will
be stretched to the breaking point, so I will also start using other terminology, such as
"computation type" instead of "wrapped type". I hope that when we reach this point, the
reason for the change will be clear and understandable.
Also, in my examples, I will generally try to keep things simple by using code such as:
let! x = ...wrapped type value...
But this is actually an oversimplification. To be precise, the "x" can be any pattern not just a
single value, and the "wrapped type" value can, of course, be an expression that evaluates
to a wrapped type. The MSDN documentation uses this more precise approach. It uses
"pattern" and "expression" in the definitions, such as let! pattern = expr in cexpr .
Here are some examples of using patterns and expressions in a maybe computation
expression, where Option is the wrapped type, and the right hand side expressions are
options :
711
Having said this, I will continue to use the oversimplified examples, so as not to add extra
complication to an already complicated topic!
To see what happens if you have not implemented a method, let's try to use the
for..in..do syntax in our maybe workflow like this:
Sometimes you get will errors that might be cryptic unless you know what is going on behind
the scenes. For example, if you forget to put return in your workflow, like this:
maybe { 1 }
You might be asking: what is the Zero method? And why do I need it? The answer to that is
coming right up.
712
Obviously, many of the special operations come in pairs, with and without a "!" symbol. For
example: let and let! (pronounced "let-bang"), return and return! , yield and
yield! and so on.
The difference is easy to remember when you realize that the operations without a "!" always
have unwrapped types on the right hand side, while the ones with a "!" always have wrapped
types.
So for example, using the maybe workflow, where Option is the wrapped type, we can
compare the different syntaxes:
let x = 1 // 1 is an "unwrapped" type
let! x = (Some 1) // Some 1 is a "wrapped" type
return 1 // 1 is an "unwrapped" type
return! (Some 1) // Some 1 is a "wrapped" type
yield 1 // 1 is an "unwrapped" type
yield! (Some 1) // Some 1 is a "wrapped" type
The "!" versions are particularly important for composition, because the wrapped type can be
the result of another computation expression of the same type.
let! x = maybe {...) // "maybe" returns a "wrapped" type
// bind another workflow of the same type using let!
let! aMaybe = maybe {...) // create a "wrapped" type
return! aMaybe // return it
// bind two child asyncs inside a parent async using let!
let processUri uri = async {
let! html = webClient.AsyncDownloadString(uri)
let! links = extractLinks html
... etc ...
}
713
type TraceBuilder() =
member this.Bind(m, f) =
match m with
| None ->
printfn "Binding with None. Exiting."
| Some a ->
printfn "Binding with Some(%A). Continuing" a
Option.bind f m
member this.Return(x) =
printfn "Returning a unwrapped %A as an option" x
Some x
member this.ReturnFrom(m) =
printfn "Returning an option (%A) directly" m
m
// make an instance of the workflow
let trace = new TraceBuilder()
Nothing new here, I hope. We have already seen all these methods before.
Now let's run some sample code through it:
trace {
return 1
} |> printfn "Result 1: %A"
trace {
return! Some 2
} |> printfn "Result 2: %A"
trace {
let! x = Some 1
let! y = Some 2
return x + y
} |> printfn "Result 3: %A"
trace {
let! x = None
let! y = Some 1
return x + y
} |> printfn "Result 4: %A"
Everything should work as expected, in particular, you should be able to see that the use of
None in the 4th example caused the next two lines ( let! y = ... return x+y ) to be
714
Introducing "do!"
Our expression supports let! , but what about do! ?
In normal F#, do is just like let , except that the expression doesn't return anything useful
(namely, a unit value).
Inside a computation expression, do! is very similar. Just as let! passes a wrapped
result to the Bind method, so does do! , except that in the case of do! the "result" is the
unit value, and so a wrapped version of unit is passed to the bind method.
Here is a simple demonstration using the trace workflow:
trace {
do! Some (printfn "...expression that returns unit")
do! Some (printfn "...another expression that returns unit")
let! x = Some (1)
return x
} |> printfn "Result from do: %A"
You can verify for yourself that a unit option is being passed to Bind as a result of each
do! .
Introducing "Zero"
What is the smallest computation expression you can get away with? Let's try nothing at all:
trace {
} |> printfn "Result for empty: %A"
715
Fair enough. If you think about it, it doesn't make sense to have nothing at all in a
computation expression. After all, it's purpose is to chain expressions together.
Next, what about a simple expression with no let! or return ?
trace {
printfn "hello world"
} |> printfn "Result for simple expression: %A"
So why is the Zero method needed now but we haven't needed it before? The answer is
that in this particular case we haven't returned anything explicitly, yet the computation
expression as a whole must return a wrapped value. So what value should it return?
In fact, this situation will occur any time the return value of the computation expression has
not been explicitly given. The same thing happens if you have an if..then expression
without an else clause.
trace {
if false then return 1
} |> printfn "Result for if without else: %A"
In normal F# code, an "if..then" without an "else" would result in a unit value, but in a
computation expression, the particular return value must be a member of the wrapped type,
and the compiler does not know what value this is.
The fix is to tell the compiler what to use -- and that is the purpose of the Zero method.
716
A Zero implementation
So now let's extend our testbed class with a Zero method that returns None , and try again.
type TraceBuilder() =
// other members as before
member this.Zero() =
printfn "Zero"
None
// make a new instance
let trace = new TraceBuilder()
// test
trace {
printfn "hello world"
} |> printfn "Result for simple expression: %A"
trace {
if false then return 1
} |> printfn "Result for if without else: %A"
The test code makes it clear that Zero is being called behind the scenes. And None is the
return value for the expression as whole. Note: None may print out as <null> . You can
ignore this.
717
Introducing "Yield"
In C#, there is a "yield" statement that, within an iterator, is used to return early and then
picks up where you left off when you come back.
And looking at the docs, there is a "yield" available in F# computation expressions as well.
What does it do? Let's try it and see.
trace {
yield 1
} |> printfn "Result for yield: %A"
No surprise there. So what should the implementation of "yield" method look like? The
MSDN documentation says that it has the signature 'T -> M<'T> , which is exactly the same
as the signature for the Return method. It must take an unwrapped value and wrap it.
So let's implement it the same way as Return and retry the test expression.
type TraceBuilder() =
// other members as before
member this.Yield(x) =
printfn "Yield an unwrapped %A as an option" x
Some x
// make a new instance
let trace = new TraceBuilder()
// test
trace {
yield 1
} |> printfn "Result for yield: %A"
This works now, and it seems that it can be used as an exact substitute for return .
718
There is a also a YieldFrom method that parallels the ReturnFrom method. And it behaves
the same way, allowing you to yield a wrapped value rather than a unwrapped one.
So let's add that to our list of builder methods as well:
type TraceBuilder() =
// other members as before
member this.YieldFrom(m) =
printfn "Yield an option (%A) directly" m
m
// make a new instance
let trace = new TraceBuilder()
// test
trace {
yield! Some 1
} |> printfn "Result for yield!: %A"
At this point you might be wondering: if return and yield are basically the same thing,
why are there two different keywords? The answer is mainly so that you can enforce
appropriate syntax by implementing one but not the other. For example, the seq expression
does allow yield but doesn't allow return , while the async does allow return , but does
not allow yield , as you can see from the snippets below.
let s = seq {yield 1} // OK
let s = seq {return 1} // error
let a = async {return 1} // OK
let a = async {yield 1} // error
In fact, you could create slightly different behavior for return vs. yield , so that, for
example, using return stops the rest of the computation expression from being evaluated,
while yield doesn't.
More generally, of course, yield should be used for sequence/enumeration semantics,
while return is normally used once per expression. (We'll see how yield can be used
multiple times in the next post.)
Revisiting "For"
719
We talked about the for..in..do syntax in the last post. So now let's revisit the "list builder"
that we discussed earlier and add the extra methods. We already saw how to define Bind
and Return for a list in a previous post, so we just need to implement the additional
methods.
The Zero method just returns an empty list.
The Yield method can be implemented in the same way as Return .
The For method can be implemented the same as Bind .
type ListBuilder() =
member this.Bind(m, f) =
m |> List.collect f
member this.Zero() =
printfn "Zero"
[]
member this.Return(x) =
printfn "Return an unwrapped %A as a list" x
[x]
member this.Yield(x) =
printfn "Yield an unwrapped %A as a list" x
[x]
member this.For(m,f) =
printfn "For %A" m
this.Bind(m,f)
// make an instance of the workflow
let listbuilder = new ListBuilder()
720
You can see that both approaches give the same result.
Summary
In this post, we've seen how to implement the basic methods for a simple computation
expression.
Some points to reiterate:
For simple expressions you don't need to implement all the methods.
Things with bangs have wrapped types on the right hand side.
Things without bangs have unwrapped types on the right hand side.
You need to implement Zero if you want a workflow that doesn't explicitly return a
value.
Yield is basically equivalent to Return , but Yield should be used for
sequence/enumeration semantics.
For is basically equivalent to Bind in simple cases.
In the next post, we'll look at what happens when we need to combine multiple values.
721
And this class has worked fine so far. But we are about to run into a problem...
Previously, we saw how yield could be used to return values just like return .
Normally, yield is not used just once, of course, but multiple times in order to return values
at different stages of a process such as an enumeration. So let's try that:
trace {
yield 1
yield 2
} |> printfn "Result for yield then yield: %A"
And if you use return instead of yield , you get the same error.
trace {
return 1
return 2
} |> printfn "Result for return then return: %A"
And this problem occurs in other contexts too. For example, if we want to do something and
then return, like this:
trace {
if true then printfn "hello"
return 1
} |> printfn "Result for if then return: %A"
723
Bind(1,fun x ->
Bind(2,fun y ->
Bind(x + y,fun z ->
Return(z) // or Yield
You can think of return (or yield ) as "resetting" the indentation, if you like. So when we
return/yield and then return/yield again, we are generating code like this:
Bind(1,fun x ->
Bind(2,fun y ->
Bind(x + y,fun z ->
Yield(z)
// start a new expression
Bind(3,fun w ->
Bind(4,fun u ->
Bind(w + u,fun v ->
Yield(v)
In other words, we now have two values in our computation expression. And then the
obvious question is, how should these two values be combined to give a single result for the
computation expression as a whole?
This is a very important point. Return and yield do not generate an early return from a
computation expression. No, the entire computation expression, all the way to the last
curly brace, is always evaluated and results in a single value. Let me repeat that. Every part
of the computation expression is always evaluated -- there is no short circuiting going on. If
we want to short circuit and return early, we have to write our own code to do that (and we'll
see how to do that later).
So, back to the pressing question. We have two expressions resulting in two values: how
should those multiple values be combined into one?
Introducing "Combine"
The answer is by using the Combine method, which takes two wrapped values and
combines them to make another wrapped value. Exactly how this works is up to us.
724
In our case, we are dealing specifically with int options , so one simple implementation
that leaps to mind it just to add the numbers together. Each parameter is an option of
course (the wrapped type), so we need to pick them apart and handle the four possible
cases:
type TraceBuilder() =
// other members as before
member this.Combine (a,b) =
match a,b with
| Some a', Some b' ->
printfn "combining %A and %A" a' b'
Some (a' + b')
| Some a', None ->
printfn "combining %A with None" a'
Some a'
| None, Some b' ->
printfn "combining None with %A" b'
Some b'
| None, None ->
printfn "combining None with None"
None
// make a new instance
let trace = new TraceBuilder()
The Delay method is a hook that allows you to delay evaluation of a computation
expression until needed -- we'll discuss this in detail very soon; but for now, let's create a
default implementation:
725
type TraceBuilder() =
// other members as before
member this.Delay(f) =
printfn "Delay"
f()
// make a new instance
let trace = new TraceBuilder()
The result of the entire workflow is the sum of all the yields, namely Some 3 .
If we have a "failure" in the workflow (e.g. a None ), the second yield doesn't occur and the
overall result is Some 1 instead.
trace {
yield 1
let! x = None
yield 2
} |> printfn "Result for yield then None: %A"
726
methods.
The Combine method is just list concatenation.
The Delay method can use a default implementation for now.
Here's the full class:
727
type ListBuilder() =
member this.Bind(m, f) =
m |> List.collect f
member this.Zero() =
printfn "Zero"
[]
member this.Yield(x) =
printfn "Yield an unwrapped %A as a list" x
[x]
member this.YieldFrom(m) =
printfn "Yield a list (%A) directly" m
m
member this.For(m,f) =
printfn "For %A" m
this.Bind(m,f)
member this.Combine (a,b) =
printfn "combining %A and %A" a b
List.concat [a;b]
member this.Delay(f) =
printfn "Delay"
f()
// make an instance of the workflow
let listbuilder = new ListBuilder()
And here's a more complicated example with a for loop and some yield s.
728
listbuilder {
for i in ["red";"blue"] do
yield i
for j in ["hat";"tie"] do
yield! [i + " " + j;"-"]
} |> printfn "Result for for..in..do : %A"
You can see that by combining for..in..do with yield , we are not too far away from the
built-in seq expression syntax (except that seq is lazy, of course).
I would strongly encourage you to play around with this a bit until you are clear on what is
going on behind the scenes. As you can see from the example above, you can use yield
in creative ways to generate all sorts of irregular lists, not just simple ones.
Note: If you're wondering about While , we're going to hold off on it for a bit, until after we
have looked at Delay in an upcoming post.
If you look at the output you can see that the values are combined pair-wise, as you might
expect.
combining [3] and [4]
combining [2] and [3; 4]
combining [1] and [2; 3; 4]
Result for yield x 4: [1; 2; 3; 4]
729
A subtle but important point is that they are combined "backwards", starting from the last
value. First "3" is combined with "4", and the result of that is then combined with "2", and so
on.
730
For example, for the maybe workflow, it is common to return the first expression if it is
Some , but otherwise the second expression, like this:
type TraceBuilder() =
// other members as before
member this.Zero() =
printfn "Zero"
None // failure
member this.Combine (a,b) =
printfn "Combining %A with %A" a b
match a with
| Some _ -> a // a succeeds -- use it
| None -> b // a fails -- use b instead
// make a new instance
let trace = new TraceBuilder()
Example: Parsing
Let's try a parsing example with this implementation:
type IntOrBool = I of int | B of bool
let parseInt s =
match System.Int32.TryParse(s) with
| true,i -> Some (I i)
| false,_ -> None
let parseBool s =
match System.Boolean.TryParse(s) with
| true,i -> Some (B i)
| false,_ -> None
trace {
return! parseBool "42" // fails
return! parseInt "42"
} |> printfn "Result for parsing: %A"
731
You can see that the first return! expression is None , and ignored. So the overall result is
the second expression, Some (I 42) .
Example: Dictionary lookup
In this example, we'll try looking up the same key in a number of dictionaries, and return
when we find a value:
let map1 = [ ("1","One"); ("2","Two") ] |> Map.ofList
let map2 = [ ("A","Alice"); ("B","Bob") ] |> Map.ofList
trace {
return! map1.TryFind "A"
return! map2.TryFind "A"
} |> printfn "Result for map lookup: %A"
You can see that the first lookup is None , and ignored. So the overall result is the second
lookup.
As you can see, this technique is very convenient when doing parsing or evaluating a
sequence of (possibly unsuccessful) operations.
In normal F#, each expression (other than the last) evaluates to the unit value.
732
The equivalent approach for a computation expression is to treat each expression (other
than the last) as a wrapped unit value, and "pass it into" the next expression, and so on, until
you reach the last expression.
This is exactly what bind does, of course, and so the easiest implementation is just to reuse
the Bind method itself. Also, for this approach to work it is important that Zero is the
wrapped unit value.
type TraceBuilder() =
// other members as before
member this.Zero() =
printfn "Zero"
this.Return () // unit not None
member this.Combine (a,b) =
printfn "Combining %A with %A" a b
this.Bind( a, fun ()-> b )
// make a new instance
let trace = new TraceBuilder()
The difference from a normal bind is that the continuation has a unit parameter, and
evaluates to b . This in turn forces a to be of type WrapperType<unit> in general, or unit
option in our case.
Here's an example of sequential processing that works with this implementation of Combine :
trace {
if true then printfn "hello......."
if false then printfn ".......world"
return 1
} |> printfn "Result for sequential combine: %A"
Here's the following trace. Note that the result of the whole expression was the result of the
last expression in the sequence, just like normal F# code.
hello.......
Zero
Returning a unwrapped <null> as an option
Zero
Returning a unwrapped <null> as an option
Returning a unwrapped 1 as an option
Combining Some null with Some 1
Combining Some null with Some 1
Result for sequential combine: Some 1
733
In the "list builder" example above, we used exactly this approach. Combine was just list
concatenation and Zero was the empty list.
On the other hand, if we had used the "bind" implementation of Combine but left Zero
defined as None , it would not have obeyed the addition rule, which would be a clue that we
had got something wrong.
734
Similarly, if you have a data-structure oriented workflow, you could just implement Combine
and some other helpers. For example, here is a minimal implementation of our list builder
class:
735
type ListBuilder() =
member this.Yield(x) = [x]
member this.For(m,f) =
m |> List.collect f
member this.Combine (a,b) =
List.concat [a;b]
member this.Delay(f) = f()
// make an instance of the workflow
let listbuilder = new ListBuilder()
And even with the minimal implementation, we can write code like this:
listbuilder {
yield 1
yield 2
} |> printfn "Result: %A"
listbuilder {
for i in [1..5] do yield i + 2
yield 42
} |> printfn "Result: %A"
736
module StandaloneCombine =
let combine a b =
match a with
| Some _ -> a // a succeeds -- use it
| None -> b // a fails -- use b instead
// create an infix version
let ( <++ ) = combine
let map1 = [ ("1","One"); ("2","Two") ] |> Map.ofList
let map2 = [ ("A","Alice"); ("B","Bob") ] |> Map.ofList
let result =
(map1.TryFind "A")
<++ (map1.TryFind "B")
<++ (map2.TryFind "A")
<++ (map2.TryFind "B")
|> printfn "Result of adding options is: %A"
Summary
What have we learned about Combine in this post?
You need to implement Combine (and Delay ) if you need to combine or "add" more
than one wrapped value in a computation expression.
Combine combines values pairwise, from last to first.
In the next post, we'll add logic to control exactly when the internal expressions get
evaluated, and introduce true short circuiting and lazy evaluation.
737
738
Let's see how it works by printing something, returning, and then printing something else:
trace {
printfn "Part 1: about to return 1"
return 1
printfn "Part 2: after return has happened"
} |> printfn "Result for Part1 without Part2: %A"
The debugging output should look something like the following, which I have annotated:
// first expression, up to "return"
Delay
Part 1: about to return 1
Return an unwrapped 1 as an option
// second expression, up to last curly brace.
Delay
Part 2: after return has happened
Zero // zero here because no explicit return was given for this part
// combining the two expressions
Returning early with Some 1. Ignoring second part: <null>
// final result
Result for Part1 without Part2: Some 1
We can see a problem here. The "Part 2: after return" was printed, even though we were
trying to return early.
Why? Well I'll repeat what I said in the last post: return and yield do not generate an early
return from a computation expression. The entire computation expression, all the way to
the last curly brace, is always evaluated and results in a single value.
This is a problem, because you might get unwanted side effects (such as printing a message
in this case) and your code is doing something unnecessary, which might cause
performance problems.
So, how can we avoid evaluating the second part until we need it?
Introducing "Delay"
The answer to the question is straightforward -- simply wrap part 2 of the expression in a
function and only call this function when needed, like this.
739
let part2 =
fun () ->
printfn "Part 2: after return has happened"
// do other stuff
// return Zero
// only evaluate if needed
if needed then
let result = part2()
Using this technique, part 2 of the computation expression can be processed completely, but
because the expression returns a function, nothing actually happens until the function is
called.
But the Combine method will never call it, and so the code inside it does not run at all.
And this is exactly what the Delay method is for. Any result from Return or Yield is
immediately wrapped in a "delay" function like this, and then you can choose whether to run
it or not.
Let's change the builder to implement a delay:
type TraceBuilder() =
// other members as before
member this.Delay(funcToDelay) =
let delayed = fun () ->
printfn "%A - Starting Delayed Fn." funcToDelay
let delayedResult = funcToDelay()
printfn "%A - Finished Delayed Fn. Result is %A" funcToDelay delayedResult
delayedResult // return the result
printfn "%A - Delaying using %A" funcToDelay delayed
delayed // return the new function
As you can see, the Delay method is given a function to execute. Previously, we executed
it immediately. What we're doing now is wrapping this function in another function and
returning the delayed function instead. I have added a number of trace statements before
and after the function is wrapped.
If you compile this code, you can see that the signature of Delay has changed. Before the
change, it returned a concrete value (an option in this case), but now it returns a function.
740
By the way, we could have implemented Delay in a much simpler way, without any tracing,
just by returning the same function that was passed in, like this:
member this.Delay(f) =
f
Much more concise! But in this case, I wanted to add some detailed tracing information as
well.
Now let's try again:
trace {
printfn "Part 1: about to return 1"
return 1
printfn "Part 2: after return has happened"
} |> printfn "Result for Part1 without Part2: %A"
Hmmm. The output of the whole trace expression is now a function, not an option. Why?
Because we created all these delays, but we never "undelayed" them by actually calling the
function!
One way to do this is to assign the output of the computation expression to a function value,
say f , and then evaluate it.
let f = trace {
printfn "Part 1: about to return 1"
return 1
printfn "Part 2: after return has happened"
}
f() |> printfn "Result for Part1 without Part2: %A"
This works as expected, but is there a way to do this from inside the computation expression
itself? Of course there is!
741
Introducing "Run"
The Run method exists for exactly this reason. It is called as the final step in the process of
evaluating a computation expression, and can be used to undo the delay.
Here's an implementation:
type TraceBuilder() =
// other members as before
member this.Run(funcToRun) =
printfn "%A - Run Start." funcToRun
let runResult = funcToRun()
printfn "%A - Run End. Result is %A" funcToRun runResult
runResult // return the result of running the delayed function
And the result is exactly what we wanted. The first part is evaluated, but the second part is
not. And the result of the entire computation expression is an option, not a function.
742
If we look at the debug trace for the example above, we can see in detail what happened.
It's a little confusing, so I have annotated it. Also, it helps to remember that working down
this trace is the same as working up from the bottom of the diagram above, because the
outermost code is run first.
743
When we originally wrote Combine we were expecting it to handle options . But now it is
handling the output of Delay , which is a function.
We can see this if we hard-code the types that Combine expects, with int option type
annotations like this:
member this.Combine (a: int option,b: int option) =
printfn "Returning early with %A. Ignoring %A" a b
a
744
In other words, the Combine is being passed a delayed function ( unit -> 'a ), which
doesn't match our explicit signature.
So what happens when we do want to combine the parameters, but they are passed in as a
function instead of as a simple value?
The answer is straightforward: just call the function that was passed in to get the underlying
value.
Let's demonstrate that using the adding example from the previous post.
type TraceBuilder() =
// other members as before
member this.Combine (m,f) =
printfn "Combine. Starting second param %A" f
let y = f()
printfn "Combine. Finished second param %A. Result is %A" f y
match m,y with
| Some a, Some b ->
printfn "combining %A and %A" a b
Some (a + b)
| Some a, None ->
printfn "combining %A with None" a
Some a
| None, Some b ->
printfn "combining None with %A" b
Some b
| None, None ->
printfn "combining None with None"
None
In this new version of Combine , the second parameter is now a function, not an int
option . So to combine them, we must first evaluate the function before doing the
combination logic.
If we test this out:
745
trace {
return 1
return 2
} |> printfn "Result for return then return: %A"
But in fact we can use other types if we like, subject to certain constraints. In fact,
understanding exactly what the type constraints are in a computation expression can clarify
how everything fits together.
For example, we have seen that:
The output of Return is passed into Delay , so they must have compatible types.
The output of Delay is passed into the second parameter of Combine .
The output of Delay is also passed into Run .
But the output of Return does not have to be our "public" wrapped type. It could be an
internally defined type instead.
Similarly, the delayed type does not have to be a simple function, it could be any type that
satisfies the constraints.
So, given a simple set of return expressions, like this:
trace {
return 1
return 2
return 3
} |> printfn "Result for return x 3: %A"
Then a diagram that represents the various types and their flow would look like this:
747
And to prove that this is valid, here is an implementation with distinct types for Internal
and Delayed :
type Internal = Internal of int option
type Delayed = Delayed of (unit -> Internal)
type TraceBuilder() =
member this.Bind(m, f) =
match m with
| None ->
printfn "Binding with None. Exiting."
| Some a ->
printfn "Binding with Some(%A). Continuing" a
Option.bind f m
member this.Return(x) =
printfn "Returning a unwrapped %A as an option" x
Internal (Some x)
748
member this.ReturnFrom(m) =
printfn "Returning an option (%A) directly" m
Internal m
member this.Zero() =
printfn "Zero"
Internal None
member this.Combine (Internal x, Delayed g) : Internal =
printfn "Combine. Starting %A" g
let (Internal y) = g()
printfn "Combine. Finished %A. Result is %A" g y
let o =
match x,y with
| Some a, Some b ->
printfn "Combining %A and %A" a b
Some (a + b)
| Some a, None ->
printfn "combining %A with None" a
Some a
| None, Some b ->
printfn "combining None with %A" b
Some b
| None, None ->
printfn "combining None with None"
None
// return the new value wrapped in a Internal
Internal o
member this.Delay(funcToDelay) =
let delayed = fun () ->
printfn "%A - Starting Delayed Fn." funcToDelay
let delayedResult = funcToDelay()
printfn "%A - Finished Delayed Fn. Result is %A" funcToDelay delayedResult
delayedResult // return the result
printfn "%A - Delaying using %A" funcToDelay delayed
Delayed delayed // return the new function wrapped in a Delay
member this.Run(Delayed funcToRun) =
printfn "%A - Run Start." funcToRun
let (Internal runResult) = funcToRun()
printfn "%A - Run End. Result is %A" funcToRun runResult
runResult // return the result of running the delayed function
// make an instance of the workflow
let trace = new TraceBuilder()
And the method signatures in the builder class methods look like this:
749
Creating this artifical builder is overkill of course, but the signatures clearly show how the
various methods fit together.
Summary
In this post, we've seen that:
You need to implement Delay and Run if you want to delay execution within a
computation expression.
Using Delay changes the signature of Combine .
Delay and Combine can use internal types that are not exposed to clients of the
computation expression.
The next logical step is wanting to delay execution outside a computation expression until
you are ready, and that will be the topic on the next but one post. But first, we'll take a little
detour to discuss method overloads.
750
Overloading "return"
Say that you have a union type. You might consider overloading Return or Yield with
multiple implementations for each union case.
For example, here's a very simple example where Return has two overloads:
751
type SuccessOrError =
| Success of int
| Error of string
type SuccessOrErrorBuilder() =
member this.Bind(m, f) =
match m with
| Success s -> f s
| Error _ -> m
/// overloaded to accept ints
member this.Return(x:int) =
printfn "Return a success %i" x
Success x
/// overloaded to accept strings
member this.Return(x:string) =
printfn "Return an error %s" x
Error x
// make an instance of the workflow
let successOrError = new SuccessOrErrorBuilder()
752
But as a consequence of the generics, the Return method can't be overloaded any more!
Second, it's probably not a good idea to expose the internals of the type inside the
expression like this anyway. The concept of "success" and "failure" cases is useful, but a
better way would be to hide the "failure" case and handle it automatically inside Bind , like
this:
type SuccessOrError<'a,'b> =
| Success of 'a
| Error of 'b
type SuccessOrErrorBuilder() =
member this.Bind(m, f) =
match m with
| Success s ->
try
f s
with
| e -> Error e.Message
| Error _ -> m
member this.Return(x) =
Success x
// make an instance of the workflow
let successOrError = new SuccessOrErrorBuilder()
In this approach, Return is only used for success, and the failure cases are hidden.
successOrError {
return 42
} |> printfn "Result for success: %A"
successOrError {
let! x = Success 1
return x/0
} |> printfn "Result for error: %A"
753
Let's revisit the Combine method for the trace workflow. If you remember, in the previous
implementation of Combine , we just added the numbers together.
But what if we change our requirements, and say that:
if we yield multiple values in the trace workflow, then we want to combine them into a
list.
A first attempt using combine might look this:
member this.Combine (a,b) =
match a,b with
| Some a', Some b' ->
printfn "combining %A and %A" a' b'
Some [a';b']
| Some a', None ->
printfn "combining %A with None" a'
Some [a']
| None, Some b' ->
printfn "combining None with %A" b'
Some [b']
| None, None ->
printfn "combining None with None"
None
In the Combine method, we unwrap the value from the passed-in option and combine them
into a list wrapped in a Some (e.g. Some [a';b'] ).
For two yields it works as expected:
trace {
yield 1
yield 2
} |> printfn "Result for yield then yield: %A"
// Result for yield then yield: Some [1; 2]
But what happens if there are three values to combine? Like this:
754
trace {
yield 1
yield 2
yield 3
} |> printfn "Result for yield x 3: %A"
755
Unfortunately, this trick has broken some previous code! If you try yielding a None now, you
will get a compiler error.
756
trace {
yield 1
yield! None
} |> printfn "Result for yield then None: %A"
But hold on, before you get too annoyed, try thinking like the compiler. If you were the
compiler, and you were given a None , which method would you call?
There is no correct answer, because a None could be passed as the second parameter to
either method. The compiler does not know where this is a None of type int list option
(the first method) or a None of type int option (the second method).
As the compiler reminds us, a type annotation will help, so let's give it one. We'll force the
None to be an int option .
trace {
yield 1
let x:int option = None
yield! x
} |> printfn "Result for yield then None: %A"
This is ugly, of course, but in practice might not happen very often.
More importantly, this is a clue that we have a bad design. Sometimes the computation
expression returns an 'a option and sometimes it returns an 'a list option . We should
be consistent in our design, so that the computation expression always returns the same
type, no matter how many yield s are in it.
That is, if we do want to allow multiple yield s, then we should use 'a list option as the
wrapper type to begin with rather than just a plain option. In this case the Yield method
would create the list option, and the Combine method could be collapsed to a single method
again.
Here's the code for our third version:
757
type TraceBuilder() =
member this.Bind(m, f) =
match m with
| None ->
printfn "Binding with None. Exiting."
| Some a ->
printfn "Binding with Some(%A). Continuing" a
Option.bind f m
member this.Zero() =
printfn "Zero"
None
member this.Yield(x) =
printfn "Yield an unwrapped %A as a list option" x
Some [x]
member this.YieldFrom(m) =
printfn "Yield an option (%A) directly" m
m
member this.Combine (a, b) =
match a,b with
| Some a', Some b' ->
printfn "combining %A and %A" a' b'
Some (a' @ b')
| Some a', None ->
printfn "combining %A with None" a'
Some a'
| None, Some b' ->
printfn "combining None with %A" b'
Some b'
| None, None ->
printfn "combining None with None"
None
member this.Delay(f) =
printfn "Delay"
f()
// make an instance of the workflow
let trace = new TraceBuilder()
And now the examples work as expected without any special tricks:
758
trace {
yield 1
yield 2
} |> printfn "Result for yield then yield: %A"
// Result for yield then yield: Some [1; 2]
trace {
yield 1
yield 2
yield 3
} |> printfn "Result for yield x 3: %A"
// Result for yield x 3: Some [1; 2; 3]
trace {
yield 1
yield! None
} |> printfn "Result for yield then None: %A"
// Result for yield then None: Some [1]
Not only is the code cleaner, but as in the Return example, we have made our code more
generic as well, having gone from a specific type ( int option ) to a more generic type ( 'a
option ).
Overloading "For"
One legitimate case where overloading might be needed is the For method. Some possible
reasons:
You might want to support different kinds of collections (e.g. list and IEnumerable )
You might have a more efficient looping implementation for certain kinds of collections.
You might have a "wrapped" version of a list (e.g. LazyList) and you want support
looping for both unwrapped and wrapped values.
Here's an example of our list builder that has been extended to support sequences as well
as lists:
759
type ListBuilder() =
member this.Bind(m, f) =
m |> List.collect f
member this.Yield(x) =
printfn "Yield an unwrapped %A as a list" x
[x]
member this.For(m,f) =
printfn "For %A" m
this.Bind(m,f)
member this.For(m:_ seq,f) =
printfn "For %A using seq" m
let m2 = List.ofSeq m
this.Bind(m2,f)
// make an instance of the workflow
let listbuilder = new ListBuilder()
If you comment out the second For method, you will see the "sequence` example will
indeed fail to compile. So the overload is needed.
Summary
So we've seen that methods can be overloaded if needed, but be careful at jumping to this
solution immediately, because having to doing this may be a sign of a weak design.
In the next post, we'll go back to controlling exactly when the expressions get evaluated, this
time using a delay outside the builder.
760
The problem
Here is the code from our "maybe" builder class. This code is based on the trace builder
from the earlier post, but with all the tracing taken out, so that it is nice and clean.
type MaybeBuilder() =
member this.Bind(m, f) =
Option.bind f m
member this.Return(x) =
Some x
member this.ReturnFrom(x) =
x
member this.Zero() =
None
member this.Combine (a,b) =
match a with
| Some _ -> a // if a is good, skip b
| None -> b() // if a is bad, run b
member this.Delay(f) =
f
member this.Run(f) =
f()
// make an instance of the workflow
let maybe = new MaybeBuilder()
Before moving on, make sure that you understand how this works. If we analyze this using
the terminology of the earlier post, we can see that the types used are:
761
But what happens if we refactor the code into a child workflow, like this:
let childWorkflow =
maybe {printfn "Child workflow"}
maybe {
printfn "Part 1: about to return 1"
return 1
return! childWorkflow
} |> printfn "Result for Part1 but not childWorkflow: %A"
The output shows that the child workflow was evaluated even though it wasn't needed in the
end. This might not be a problem in this case, but in many cases, we may not want this to
happen.
So, how to avoid it?
762
We've replaced a simple option with a function that evaluates to an option, and then
wrapped that function in a single case union for good measure.
And now we need to change the Run method as well. Previously, it evaluated the delay
function that was passed in to it, but now it should leave it unevaluated and wrap it in our
new wrapper type:
// before
member this.Run(f) =
f()
// after
member this.Run(f) =
Maybe f
I've forgotten to fix up another method -- do you know which one? We'll bump into it soon!
One more thing -- we'll need a way to "run" the result now.
let run (Maybe f) = f()
763
Oops! We forgot to fix up ReturnFrom ! As we know, that method takes a wrapped type, and
we have redefined the wrapped type now.
Here's the fix:
member this.ReturnFrom(Maybe f) =
f()
We are going to accept a Maybe from outside, and then immediately run it to get at the
option.
But now we have another problem -- we can't return an explicit None anymore in return!
None , we have to return a Maybe type instead. How are we going to create one of these?
Well, we could create a helper function that constructs one for us. But there is a much
simpler answer: you can create a new Maybe type by using a maybe expression!
let m2 = maybe {
return! maybe {printfn "Part 1: about to return None"}
printfn "Part 2: after None, keep going"
}
This is why the Zero method is useful. With Zero and the builder instance, you can create
new instances of the type even if they don't do anything.
But now we have one more error -- the dreaded "value restriction":
Value restriction. The value 'm2' has been inferred to have generic type
764
The reason why this has happened is that both expressions are returning None . But the
compiler does not know what type None is. The code is using None of type Option<obj>
(presumably because of implicit boxing) yet the compiler knows that the type can be more
generic than that.
There are two fixes. One is to make the type explicit:
let m2_int: Maybe<int> = maybe {
return! maybe {printfn "Part 1: about to return None"}
printfn "Part 2: after None, keep going;"
}
765
If we analyze this new builder using the terminology of the earlier post, we can see that the
types used are:
766
True laziness
Let's look at a variant of the last example:
let child_twice: Maybe<unit> = maybe {
let workflow = maybe {printfn "Child workflow"}
return! maybe {printfn "Part 1: about to return None"}
return! workflow
return! workflow
}
run child_twice |> printfn "Result for childWorkflow twice: %A"
What should happen? How many times should the child workflow be run?
The delayed implementation above does ensure that the child workflow is only be evaluated
on demand, but it does not stop it being run twice.
In some situations, you might require that the workflow is guaranteed to only run at most
once, and then cached ("memoized"). This is easy enough to do using the Lazy type that is
built into F#.
The changes we need to make are:
Change Maybe to wrap a Lazy instead of a delay
Change ReturnFrom and run to force the evaluation of the lazy value
Change Run to run the delay from inside a lazy
Here is the new class with the changes:
767
from which it is clear that the child workflow only ran once.
768
769
Remember, as always, that not all methods need to be implemented. If While is not
relevant to you, don't bother with it.
One important note before we get started: all the methods discussed here rely on delays
being used. If you are not using delay functions, then none of the methods will give the
expected results.
Implementing "While"
We all know what "while" means in normal code, but what does it mean in the context of a
computation expression? To understand, we have to revisit the concept of continuations
again.
In previous posts, we saw that a series of expressions is converted into a chain of
continuations like this:
Bind(1,fun x ->
Bind(2,fun y ->
Bind(x + y,fun z ->
Return(z) // or Yield
And this is the key to understanding a "while" loop -- it can be expanded in the same way.
First, some terminology. A while loop has two parts:
There is a test at the top of the "while" loop which is evaluated each time to determine
whether the body should be run. When it evaluates to false, the while loop is "exited". In
computation expressions, the test part is known as the "guard". The test function has
no parameters, and returns a bool, so its signature is unit -> bool , of course.
770
And there is the body of the "while" loop, evaluated each time until the "while" test fails.
In computation expressions, this is a delay function that evaluates to a wrapped value.
Since the body of the while loop is always the same, the same function is evaluated
each time. The body function has no parameters, and returns nothing, and so its
signature is just unit -> wrapped unit .
With this in place, we can create pseudo-code for a while loop using continuations:
// evaluate test function
let bool = guard()
if not bool
then
// exit loop
return what??
else
// evaluate the body function
body()
// back to the top of the while loop
// evaluate test function again
let bool' = guard()
if not bool'
then
// exit loop
return what??
else
// evaluate the body function again
body()
// back to the top of the while loop
// evaluate test function a third time
let bool'' = guard()
if not bool''
then
// exit loop
return what??
else
// evaluate the body function a third time
body()
// etc
One question that is immediately apparent is: what should be returned when the while loop
test fails? Well, we have seen this before with if..then.. , and the answer is of course to
use the Zero value.
771
The next thing is that the body() result is being discarded. Yes, it is a unit function, so there
is no value to return, but even so, in our expressions, we want to be able to hook into this so
we can add behavior behind the scenes. And of course, this calls for using the Bind
function.
So here is a revised version of the pseudo-code, using Zero and Bind :
// evaluate test function
let bool = guard()
if not bool
then
// exit loop
return Zero
else
// evaluate the body function
Bind( body(), fun () ->
// evaluate test function again
let bool' = guard()
if not bool'
then
// exit loop
return Zero
else
// evaluate the body function again
Bind( body(), fun () ->
// evaluate test function a third time
let bool'' = guard()
if not bool''
then
// exit loop
return Zero
else
// evaluate the body function again
Bind( body(), fun () ->
// etc
In this case, the continuation function passed into Bind has a unit parameter, because the
body function does not have a value.
Finally, the pseudo-code can be simplified by collapsing it into a recursive function like this:
772
And indeed, this is the standard "boiler-plate" implementation for While in almost all builder
classes.
It is a subtle but important point that the value of Zero must be chosen properly. In previous
posts, we saw that we could set the value for Zero to be None or Some () depending on
the workflow. For While to work however, the Zero must be set to Some () and not
None , because passing None into Bind will cause the whole thing to aborted early.
Also note that, although this is a recursive function, we didn't need the rec keyword. It is
only needed for standalone functions that are recursive, not methods.
"While" in use
Let's look at it being used in the trace builder. Here's the complete builder class, with the
While method:
773
type TraceBuilder() =
member this.Bind(m, f) =
match m with
| None ->
printfn "Binding with None. Exiting."
| Some a ->
printfn "Binding with Some(%A). Continuing" a
Option.bind f m
member this.Return(x) =
Some x
member this.ReturnFrom(x) =
x
member this.Zero() =
printfn "Zero"
this.Return ()
member this.Delay(f) =
printfn "Delay"
f
member this.Run(f) =
f()
member this.While(guard, body) =
printfn "While: test"
if not (guard())
then
printfn "While: zero"
this.Zero()
else
printfn "While: body"
this.Bind( body(), fun () ->
this.While(guard, body))
// make an instance of the workflow
let trace = new TraceBuilder()
If you look at the signature for While , you will see that the body parameter is unit -> unit
option , that is, a delayed function. As noted above, if you don't implement Delay properly,
774
And here is a simple loop using a mutable value that is incremented each time round.
let mutable i = 1
let test() = i < 5
let inc() = i <- i + 1
let m = trace {
while test() do
printfn "i is %i" i
inc()
}
As you can see, it is common to use pass the returned value through ReturnFrom so that it
gets the same treatment as other wrapped values.
775
Implementing "try..finally"
try..finally is very similar to try..with .
There is the body of the "try", evaluated once. The body function has no parameters,
and so its signature is unit -> wrapped type .
The "finally" part is always called. It has no parameters, and returns a unit, so its
signature is unit -> unit .
Just as with try..with , the standard implementation is obvious.
member this.TryFinally(body, compensation) =
try
printfn "TryFinally Body"
this.ReturnFrom(body())
finally
printfn "TryFinally compensation"
compensation()
Implementing "using"
The final method to implement is Using . This is the builder method for implementing the
use! keyword.
776
is translated to:
builder.Bind(expr, (fun value -> builder.Using(value, (fun value -> {| cexpr |} ))))
In other words, the use! keyword triggers both a Bind and a Using . First a Bind is done
to unpack the wrapped value, and then the unwrapped disposable is passed into Using to
ensure disposal, with the continuation function as the second parameter.
Implementing this is straightforward. Similar to the other methods, we have a body, or
continuation part, of the "using" expression, which is evaluated once. This body function has
a "disposable" parameter, and so its signature is #IDisposable -> wrapped type .
Of course we want to ensure that the disposable value is always disposed no matter what,
so we need to wrap the call to the body function in a TryFinally .
Here's a standard implementation:
member this.Using(disposable:#System.IDisposable, body) =
let body' = fun () -> body disposable
this.TryFinally(body', fun () ->
match disposable with
| null -> ()
| disp -> disp.Dispose())
Notes:
The parameter to TryFinally is a unit -> wrapped , with a unit as the first parameter,
so we created a delayed version of the body that is passed in.
Disposable is a class, so it could be null , and we have to handle that case specially.
Otherwise we just dispose it in the "finally" continuation.
Here's a demonstration of Using in action. Note that the makeResource makes a wrapped
disposable. If it wasn't wrapped, we wouldn't need the special use! and could just use a
normal use instead.
777
"For" revisited
Finally, we can revisit how For is implemented. In the previous examples, For took a
simple list parameter. But with Using and While under our belts, we can change it to
accept any IEnumerable<_> or sequence.
Here's the standard implementation for For now:
member this.For(sequence:seq<_>, body) =
this.Using(sequence.GetEnumerator(),fun enum ->
this.While(enum.MoveNext,
this.Delay(fun () -> body enum.Current)))
{% endhighlight fsharp %}
As you can see, it is quite different from the previous implementation, in order to ha
ndle a generic `IEnumerable<_>`.
* We explicitly iterate using an `IEnumerator<_>`.
* `IEnumerator<_>` implements `IDisposable`, so we wrap the enumerator in a `Using`.
* We use `While .. MoveNext` to iterate.
* Next, we pass the `enum.Current` into the body function
* Finally, we delay the call to the body function using `Delay`
## Complete code without tracing
Up to now, all the builder methods have been made more complex than necessary by the a
dding of tracing and printing expressions. The tracing is helpful to understand what i
s going on,
but it can obscure the simplicity of the methods.
So as a final step, let's have a look at the complete code for the "trace" builder cla
ss, but this time without any extraneous code at all. Even though the code is cryptic
, the purpose and implementation of each method should now be familiar to you.
```fsharp
type TraceBuilder() =
778
member this.Bind(m, f) =
Option.bind f m
member this.Return(x) = Some x
member this.ReturnFrom(x) = x
member this.Yield(x) = Some x
member this.YieldFrom(x) = x
member this.Zero() = this.Return ()
member this.Delay(f) = f
member this.Run(f) = f()
member this.While(guard, body) =
if not (guard())
then this.Zero()
else this.Bind( body(), fun () ->
this.While(guard, body))
member this.TryWith(body, handler) =
try this.ReturnFrom(body())
with e -> handler e
member this.TryFinally(body, compensation) =
try this.ReturnFrom(body())
finally compensation()
member this.Using(disposable:#System.IDisposable, body) =
let body' = fun () -> body disposable
this.TryFinally(body', fun () ->
match disposable with
| null -> ()
| disp -> disp.Dispose())
member this.For(sequence:seq<_>, body) =
this.Using(sequence.GetEnumerator(),fun enum ->
this.While(enum.MoveNext,
this.Delay(fun () -> body enum.Current)))
After all this discussion, the code seems quite tiny now. And yet this builder implements
every standard method, uses delayed functions. A lot of functionality in a just a few lines!
779
How not to do it
A newcomer to F# might be tempted to organize code in classes just like in C#. One class
per file, in alphabetical order. After all, F# supports the same object-oriented features that C#
does, right? So surely the F# code can be organized the same way as C# code?
After a while, this is often followed by the discovery that F# requires files (and code within a
file) to be in dependency order. That is, you cannot use forward references to code that
hasn't been seen by the compiler yet**.
This is followed by general annoyance and swearing. How can F# be so stupid? Surely it
impossible to write any kind of large project!
In this post, we'll look at one simple way to organize your code so that this doesn't happen.
** The and keyword can be used in some cases to allow mutual recursion, but is
discouraged.
Each layer contains only the code that is relevant to that layer.
780
But in practice, it is not that simple, as there are dependencies between each layer. The
domain layer depends on the infrastructure, and the presentation layer depends on the
domain.
And most importantly, the domain layer should not depend on the persistence layer. That is,
it should be "persistence agnostic".
We therefore need to tweak the layer diagram to look more like this (where each arrow
represents a dependency):
And ideally this reorganization would be made even more fine grained, with a separate
"Service Layer", containing application services, domain services, etc. And when we are
finished, the core domain classes are "pure" and have no dependencies on anything else
outside the domain. This is often called a "hexagonal architecture" or "onion architecture".
But this post is not about the subtleties of OO design, so for now, let's just work with the
simpler model.
781
In fact, in OOD, not having enough behavior around a data type is considered a Bad Thing,
and even has a name: the "anemic domain model".
In functional design though, having "dumb data" with transparency is preferred. It is normally
fine for the data to be exposed without being encapsulated. The data is immutable, so it
can't get "damaged" by a misbehaving function. And it turns out that the focus on
transparent data allows for more code that is more flexible and generic.
If you haven't seen it, I highly recommend Rich Hickey's excellent talk on "The Value of
Values", which explains the benefits of this approach.
Notice though, that we might have some backwards references (shown by the red arrow).
For example, a function in the domain layer might depend on a persistence-related type,
such as IRepository .
In an OO design, we would add more layers (e.g. application services) to handle this. But in
a functional design, we don't need to -- we can just move the persistence-related types to a
different place in the hierarchy, underneath the domain functions, like this:
782
In this design, we have now eliminated all cyclic references between layers. All the arrows
point down.
And this without having to create any extra layers or overhead.
Finally, we can translate this layered design into F# files by turning it upside down.
The first file in the project should contain code which has no dependencies. This
represents the functionality at the bottom of the layer diagram. It is generally a set of
types, such the infrastructure or domain types.
The next file depends only on the first file. It would represents the functionality at the
next-to-bottom layer.
And so on. Each file depends only on the previous ones.
So, if we refer back to the use case example discussed in Part 1:
783
then the corresponding code in an F# project might look something like this:
At the very bottom of the list is the main file, called "main" or "program", which contains the
entry point for the program.
And just above it is the code for the use cases in the application. The code in this file is
where all the functions from all the other modules are "glued together" into a single function
that represents a particular use case or service request. (The nearest equivalent of this in an
OO design are the "application services", which serve roughly the same purpose.)
784
And then just above that is the "UI layer" and then the "DB layer" and so on, until you get to
the top.
What's nice about this approach is that, if you are a newcomer to a code base, you always
know where to start. The first few files will always be the "bottom layer" of an application and
the last few files will always be the "top layer". No folders needed!
785
So the functions are accessed with names like Person.create and Person.fullName while
the type itself is accessed with the name Person.T .
In the second approach, types are declared in the same file, but outside any module:
namespace Example
// declare the type outside the module
type PersonType = {First:string; Last:string}
// declare a module for functions that work on the type
module Person =
// constructor
let create first last =
{First=first; Last=last}
// method that works on the type
let fullName {First=first; Last=last} =
first + " " + last
In this case, the functions are accessed with the same names ( Person.create and
Person.fullName ) while the type itself is accessed with the name such as PersonType .
And finally, here's the third approach. The type is declared in a special "types-only" module
(typically in a different file):
// =========================
// File: DomainTypes.fs
// =========================
namespace Example
// "types-only" module
[<AutoOpen>]
module DomainTypes =
type Person = {First:string; Last:string}
type OtherDomainType = ...
type ThirdDomainType = ...
In this particular case, the AutoOpen attribute has been used to make the types in this
module automatically visible to all the other modules in the project -- making them "global".
And then a different module contains all the functions that work on, say, the Person type.
786
// =========================
// File: Person.fs
// =========================
namespace Example
// declare a module for functions that work on the type
module Person =
// constructor
let create first last =
{First=first; Last=last}
// method that works on the type
let fullName {First=first; Last=last} =
first + " " + last
Note that in this example, both the type and the module are called Person . This is not
normally a problem in practice, as the compiler can normally figure out what you want.
So, if you write this:
let f (p:Person) = p.First
Then the compiler will understand that you are referring to the Person type.
On the other hand, if you write this:
let g () = Person.create "Alice" "Smith"
Then the compiler will understand that you are referring to the Person module.
For more on modules, see the post on organizing functions.
787
then I would put it in a module called UITypes , which would come just before the other
UI modules in the compilation order.
If a type is private to a module (or two) then put it in the same module as its related
functions.
For example, a type that was used only for validation would be put in the Validation
module. A type used only for database access would be put in the Database module,
and so on.
Of course, there are many ways to organize types, but these guidelines act as a good
default starting point.
788
It's not that hard, but it does requires some more explanation, so I have devoted another
whole post to dealing with cyclic dependencies.
Example code
Let's revisit at the code we have so far, but this time organized into modules.
Each module below would typically become a separate file.
Be aware that this is still a skeleton. Some of the modules are missing, and some of the
modules are almost empty.
This kind of organization would be overkill for a small project, but there will be lots more
code to come!
/// ===========================================
/// Common types and functions shared across multiple projects
/// ===========================================
module CommonLibrary =
// the two-track type
type Result<'TSuccess,'TFailure> =
| Success of 'TSuccess
| Failure of 'TFailure
// convert a single value into a two-track result
let succeed x =
Success x
// convert a single value into a two-track result
let fail x =
Failure x
// appy either a success function or failure function
let either successFunc failureFunc twoTrackInput =
match twoTrackInput with
| Success s -> successFunc s
| Failure f -> failureFunc f
789
let (>=>) s1 s2 =
s1 >> bind s2
// convert a one-track function into a switch
let switch f =
f >> succeed
// convert a one-track function into a two-track function
let map f =
either (f >> succeed) fail
// convert a dead-end function into a one-track function
let tee f x =
f x; x
// convert a one-track function into a switch with exception handling
let tryCatch f exnHandler x =
try
f x |> succeed
with
| ex -> exnHandler ex |> fail
// convert two one-track functions into a two-track function
let doubleMap successFunc failureFunc =
either (successFunc >> succeed) (failureFunc >> fail)
// add two switches in parallel
let plus addSuccess addFailure switch1 switch2 x =
match (switch1 x),(switch2 x) with
| Success s1,Success s2 -> Success (addSuccess s1 s2)
| Failure f1,Success _ -> Failure f1
| Success _ ,Failure f2 -> Failure f2
| Failure f1,Failure f2 -> Failure (addFailure f1 f2)
/// ===========================================
/// Global types for this project
/// ===========================================
module DomainTypes =
open CommonLibrary
/// The DTO for the request
type Request = {name:string; email:string}
// Many more types coming soon!
/// ===========================================
/// Logging functions
/// ===========================================
module Logger =
open CommonLibrary
790
open DomainTypes
let log twoTrackInput =
let success x = printfn "DEBUG. Success so far: %A" x; x
let failure x = printfn "ERROR. %A" x; x
doubleMap success failure twoTrackInput
/// ===========================================
/// Validation functions
/// ===========================================
module Validation =
open CommonLibrary
open DomainTypes
let validate1 input =
if input.name = "" then Failure "Name must not be blank"
else Success input
let validate2 input =
if input.name.Length > 50 then Failure "Name must not be longer than 50 chars"
else Success input
let validate3 input =
if input.email = "" then Failure "Email must not be blank"
else Success input
// create a "plus" function for validation functions
let (&&&) v1 v2 =
let addSuccess r1 r2 = r1 // return first
let addFailure s1 s2 = s1 + "; " + s2 // concat
plus addSuccess addFailure v1 v2
let combinedValidation =
validate1
&&& validate2
&&& validate3
let canonicalizeEmail input =
{ input with email = input.email.Trim().ToLower() }
/// ===========================================
/// Database functions
/// ===========================================
module CustomerRepository =
open CommonLibrary
open DomainTypes
let updateDatabase input =
() // dummy dead-end function for now
// new function to handle exceptions
791
let updateDatebaseStep =
tryCatch (tee updateDatabase) (fun ex -> ex.Message)
/// ===========================================
/// All the use cases or services in one place
/// ===========================================
module UseCases =
open CommonLibrary
open DomainTypes
let handleUpdateRequest =
Validation.combinedValidation
>> map Validation.canonicalizeEmail
>> bind CustomerRepository.updateDatebaseStep
>> Logger.log
Summary
In this post, we looked at organizing code into modules. In the next post in this series, we'll
finally start doing some real coding!
Meanwhile, you can read more on cyclic dependencies in the follow up posts:
Cyclic dependencies are evil.
Refactoring to remove cyclic dependencies.
Cycles and modularity in the wild, which compares some real-world metrics for C# and
F# projects.
792
One of the most common complaints about F# is that it requires code to be in dependency
order. That is, you cannot use forward references to code that hasn't been seen by the
compiler yet.
In this series, I discuss dependency cycles, why they are bad, and how to get rid of them.
Cyclic dependencies are evil. Cyclic dependencies: Part 1.
Refactoring to remove cyclic dependencies. Cyclic dependencies: Part 2.
Cycles and modularity in the wild. Comparing some real-world metrics of C# and F#
projects.
793
794
795
You are very familiar with this, I'm sure. Here's a diagram of some simple layers:
But now what happens when you introduce a dependency from the bottom layer to the top
layer, like this?
By having a dependency from the bottom to the top, we have introduced the evil "circular
dependency".
Why is it evil? Because any alternative layering method is now valid!
For example, we could put the bottom layer on top instead, like this:
From a logical point of view, this alternative layering is just the same as the original layering.
Or how about we put the middle layer on top?
796
Something has gone badly wrong! It's clear that we've really messed things up.
In fact, as soon as you have any kind of circular dependency between components, the only
thing you can do is to put them all into the same layer.
In other words, the circular dependency has completely destroyed our "divide and conquer"
approach, the whole reason for having components in the first place. Rather than having
three components, we now have just one "super component", which is three times bigger
and more complicated than it needed to be.
797
798
I'm evangelizing an unpopular position here, but my experience is that everything in the
world is better when you're forced to consider and manage "dependency order among
software components" at every level of the system. The specific UI/tooling for F# may
not yet be ideal, but I think the principle is right. This is a burden you want. It is more
work. "Unit testing" is also more work, but we've gotten to the point where the
consensus is that work is "worth it" in that it saves you time in the long run. I feel the
same way about 'ordering'. There are dependencies among the classes and methods in
your system. You ignore those dependencies at your own peril. A system that forces
you to consider this dependency graph (roughly, the topological sort of components) is
likely to steer you into developing software with cleaner architectures, better system
layering, and fewer needless dependencies.
799
800
Recognize that F# is not C#. If you are willing to work with F# using its native idioms, then it
is normally very straightforward to avoid circular dependencies by using a different style of
code organization.
Tip 2: Separate types from behavior.
Since most types in F# are immutable, it is acceptable for them to be "exposed" and
"anemic", even. So in a functional design it is common to separate the types themselves
from the functions that act on them. This approach will often help to clean up dependencies,
as we'll see below.
Tip 3: Parameterize, parameterize, parameterize.
Dependencies can only happen when a specific type is referenced. If you use generic types,
you cannot have a dependency!
And rather than hard coding behavior for a type, why not parameterize it by passing in
functions instead? The List module is a great example of this approach, and I'll show
some examples below as well.
801
mutual dependency.
But and has a number of problems, and using it is generally discouraged except as a last
resort.
First, it only works for types declared in the same module. You can't use it across module
boundaries.
Second, it should really only be used for tiny types. If you have 500 lines of code between
the type and the and , then you are doing something very wrong.
type Something
// 500 lines of code
and SomethingElse
// 500 more lines of code
Introducing parameterization
So, instead of using and , let's see what we can do using parameterization, as mentioned in
the third tip.
802
If we think about the example code, do we really need a special CustomerObserver class?
Why have we restricted it to Customer only? Can't we have a more generic observer class?
So why don't we create a INameObserver<'T> interface instead, with the same
OnNameChanged method, but the method (and interface) parameterized to accept any class?
In this revised version, the dependency has been broken! No and is needed at all. In fact,
you could even put the types in different projects or assemblies now!
The code is almost identical to the first version, except that the Customer constructor
accepts a interface, and CustomerObserver now implements the same interface. In fact, I
would argue that introducing the interface has actually made the code better than before.
But we don't have to stop there. Now that we have an interface, do we really need to create
a whole class just to implement it? F# has a great feature called object expressions which
allows you to instantiate an interface directly.
Here is the same code again, but this time the CustomerObserver class has been eliminated
completely and the INameObserver created directly.
803
module MethodDependency_ParameterizedInterface =
// code as above
// test
let observer2 = {
new INameObserver<Customer> with
member this.OnNameChanged c =
printfn "Customer name changed to '%s' " c.Name
}
let customer2 = Customer("Alice", observer2)
customer2.Name <- "Bob"
This technique will obviously work for more complex interfaces as well, such as that shown
below, where there are two methods:
module MethodDependency_ParameterizedInterface2 =
type ICustomerObserver<'T> =
abstract OnNameChanged : 'T -> unit
abstract OnEmailChanged : 'T -> unit
type Customer(name, email, observer:ICustomerObserver<Customer>) =
let mutable name = name
let mutable email = email
member this.Name
with get() = name
and set(value) =
name <- value
observer.OnNameChanged(this)
member this.Email
with get() = email
and set(value) =
email <- value
observer.OnEmailChanged(this)
// test
let observer2 = {
new ICustomerObserver<Customer> with
member this.OnNameChanged c =
printfn "Customer name changed to '%s' " c.Name
member this.OnEmailChanged c =
printfn "Customer email changed to '%s' " c.Email
}
let customer2 = Customer("Alice", "x@example.com",observer2)
customer2.Name <- "Bob"
customer2.Email <- "y@example.com"
804
I think you'll agree that this snippet is "lower ceremony" than either of the previous versions.
The observer is now defined inline as needed, very simply:
let observer(c:Customer) =
printfn "Customer name changed to '%s' " c.Name
True, it only works when the interface being replaced is simple, but even so, this approach
can be used more often than you might think.
805
module MethodDependencyExample_SeparateTypes =
module DomainTypes =
type Customer = { name:string; observer:NameChangedObserver }
and NameChangedObserver = Customer -> unit
module Customer =
open DomainTypes
let changeName customer newName =
let newCustomer = {customer with name=newName}
customer.observer newCustomer
newCustomer // return the new customer
module Observer =
open DomainTypes
let printNameChanged customer =
printfn "Customer name changed to '%s' " customer.name
// test
module Test =
open DomainTypes
let observer = Observer.printNameChanged
let customer = {name="Alice"; observer=observer}
Customer.changeName customer "Bob"
In the example above, we now have three modules: one for the types, and one each for the
functions. Obviously, in a real application, there will be a lot more Customer related functions
in the Customer module than just this one!
In this code, though, we still have the mutual dependency between Customer and
CustomerObserver . The type definitions are more compact, so it is not such a problem, but
806
module MethodDependency_SeparateTypes2 =
module DomainTypes =
type Customer = { name:string; observer:Customer -> unit}
module Customer =
open DomainTypes
let changeName customer newName =
let newCustomer = {customer with name=newName}
customer.observer newCustomer
newCustomer // return the new customer
module Observer =
open DomainTypes
let printNameChanged customer =
printfn "Customer name changed to '%s' " customer.name
module Test =
open DomainTypes
let observer = Observer.printNameChanged
let customer = {name="Alice"; observer=observer}
Customer.changeName customer "Bob"
807
module MethodDependency_SeparateTypes3 =
module DomainTypes =
type Customer = {name:string}
module Customer =
open DomainTypes
let changeName observer customer newName =
let newCustomer = {customer with name=newName}
observer newCustomer // call the observer with the new customer
newCustomer // return the new customer
module Observer =
open DomainTypes
let printNameChanged customer =
printfn "Customer name changed to '%s' " customer.name
module Test =
open DomainTypes
let observer = Observer.printNameChanged
let customer = {name="Alice"}
Customer.changeName observer customer "Bob"
You might be thinking that I have made things more complicated now -- I have to specify the
observer function everywhere I call changeName in my code. Surely this is worse than
before? At least in the OO version, the observer was part of the customer object and I didn't
have to keep passing it in.
Ah, but, you're forgetting the magic of partial application! You can set up a function with the
observer "baked in", and then use that function everywhere, without needing to pass in an
observer every time you use it. Clever!
808
module MethodDependency_SeparateTypes3 =
// code as above
module TestWithPartialApplication =
open DomainTypes
let observer = Observer.printNameChanged
// set up this partial application only once (at the top of your module, say)
let changeName = Customer.changeName observer
// then call changeName without needing an observer
let customer = {name="Alice"}
changeName customer "Bob"
Actually, I called it hook2 because the function f being "hooked into" has two parameters.
I could make another version for functions that have one parameter, like this:
809
If you have read the railway oriented programming post, you might notice that this is quite
similar to what I called a "dead-end" function. I won't go into more details here, but this is
indeed a common pattern.
Ok, back to the code -- how do we use this generic hook function?
Customer.changeName is the function being hooked into, and it has two parameters, so
we use hook2 .
The observer function is just as before
So, again, we create a partially applied changeName function, but this time we create it by
passing the observer and the hooked function to hook2 , like this:
let observer = Observer.printNameChanged
let changeName = hook2 observer Customer.changeName
Note that the resulting changeName has exactly the same signature as the original
Customer.changeName function, so it can be used interchangably with it anywhere.
810
module MethodDependency_SeparateTypes_WithHookFunction =
[<AutoOpen>]
module MyFunctionLibrary =
let hook observer f param1 =
let y = f param1 // do something to make a result value
observer y // call the observer with the result value
y // return the result value
let hook2 observer f param1 param2 =
let y = f param1 param2 // do something to make a result value
observer y // call the observer with the result value
y // return the result value
module DomainTypes =
type Customer = { name:string}
module Customer =
open DomainTypes
let changeName customer newName =
{customer with name=newName}
module Observer =
open DomainTypes
let printNameChanged customer =
printfn "Customer name changed to '%s' " customer.name
module TestWithPartialApplication =
open DomainTypes
// set up this partial application only once (at the top of your module, say)
let observer = Observer.printNameChanged
let changeName = hook2 observer Customer.changeName
// then call changeName without needing an observer
let customer = {name="Alice"}
changeName customer "Bob"
Creating a hook function like this might seem to add extra complication initially, but it has
eliminated yet more code from the main application, and once you have built up a library of
functions like this, you will find uses for them everywhere.
By the way, if it helps you to use OO design terminology, you can think of this approach as a
"Decorator" or "Proxy" pattern.
The second of our classifications is what I am calling a "structural dependency", where each
type stores a value of the other type.
Type A stores a value of type B in a property
Type B stores a value of type A in a property
For this set of examples, consider an Employee who works at a Location . The Employee
contains the Location they work at, and the Location stores a list of Employees who work
there.
Voila -- mutual dependency!
Here is the example in code:
module StructuralDependencyExample =
type Employee(name, location:Location) =
member this.Name = name
member this.Location = location
and Location(name, employees: Employee list) =
member this.Name = name
member this.Employees = employees
Before we get on to refactoring, let's consider how awkward this design is. How can we
initialize an Employee value without having a Location value, and vice versa.
Here's one attempt. We create a location with an empty list of employees, and then create
other employees using that location:
module StructuralDependencyExample =
// code as above
module Test =
let location = new Location("CA",[])
let alice = new Employee("Alice",location)
let bob = new Employee("Bob",location)
location.Employees // empty!
|> List.iter (fun employee ->
printfn "employee %s works at %s" employee.Name employee.Location.Name)
But this code doesn't work as we want. We have to set the list of employees for location
as empty because we can't forward reference the alice and bob values..
812
F# will sometimes allow you to use the and keyword in these situation too, for recursive
"lets". Just as with "type", the "and" keyword replaces the "let" keyword. Unlike "type", the
first "let" has to be marked as recursive with let rec .
Let's try it. We will give location a list of alice and bob even though they are not
declared yet.
module UncompilableTest =
let rec location = new Location("NY",[alice;bob])
and alice = new Employee("Alice",location )
and bob = new Employee("Bob",location )
But no, the compiler is not happy about the infinite recursion that we have created. In some
cases, and does indeed work for let definitions, but this is not one of them! And anyway,
just as for types, having to use and for "let" definitions is a clue that you might need to
refactor.
So, really, the only sensible solution is to use mutable structures, and to fix up the location
object after the individual employees have been created, like this:
module StructuralDependencyExample_Mutable =
type Employee(name, location:Location) =
member this.Name = name
member this.Location = location
and Location(name, employees: Employee list) =
let mutable employees = employees
member this.Name = name
member this.Employees = employees
member this.SetEmployees es =
employees <- es
module TestWithMutableData =
let location = new Location("CA",[])
let alice = new Employee("Alice",location)
let bob = new Employee("Bob",location)
// fixup after creation
location.SetEmployees [alice;bob]
location.Employees
|> List.iter (fun employee ->
printfn "employee %s works at %s" employee.Name employee.Location.Name)
So, a lot of trouble just to create some values. This is another reason why mutual
dependencies are a bad idea!
813
Parameterizing again
To break the dependency, we can use the parameterization trick again. We can just create a
parameterized vesion of Employee .
module StructuralDependencyExample_ParameterizedClasses =
type ParameterizedEmployee<'Location>(name, location:'Location) =
member this.Name = name
member this.Location = location
type Location(name, employees: ParameterizedEmployee<Location> list) =
let mutable employees = employees
member this.Name = name
member this.Employees = employees
member this.SetEmployees es =
employees <- es
type Employee = ParameterizedEmployee<Location>
module Test =
let location = new Location("CA",[])
let alice = new Employee("Alice",location)
let bob = new Employee("Bob",location)
location.SetEmployees [alice;bob]
location.Employees // non-empty!
|> List.iter (fun employee ->
printfn "employee %s works at %s" employee.Name employee.Location.Name)
One nice thing about creating an alias like that is that the original code for creating
employees will continue to work unchanged.
let alice = new Employee("Alice",location)
814
module StructuralDependency_WithAge =
type Employee(name, age:float, location:Location) =
member this.Name = name
member this.Age = age
member this.Location = location
// expects Name property
member this.LocationName = location.Name
and Location(name, employees: Employee list) =
let mutable employees = employees
member this.Name = name
member this.Employees = employees
member this.SetEmployees es =
employees <- es
// expects Age property
member this.AverageAge =
employees |> List.averageBy (fun e -> e.Age)
module Test =
let location = new Location("CA",[])
let alice = new Employee("Alice",20.0,location)
let bob = new Employee("Bob",30.0,location)
location.SetEmployees [alice;bob]
printfn "Average age is %g" location.AverageAge
815
One way of thinking about this is that we are providing the behavior externally, rather than as
part of the type.
To use this then, we need to pass in a function along with the type parameter. This would be
annoying to do all the time, so naturally we will wrap it in a function, like this:
module StructuralDependencyWithAge_ParameterizedCorrect =
// same code as above
// create a helper function to construct Employees
let Employee(name, age, location) =
let getLocationName (l:Location) = l.Name
new ParameterizedEmployee<Location>(name, age, location, getLocationName)
With this in place, the original test code continues to work, almost unchanged (we have to
change new Employee to just Employee ).
816
module StructuralDependencyWithAge_ParameterizedCorrect =
// same code as above
module Test =
let location = new Location("CA",[])
let alice = Employee("Alice",20.0,location)
let bob = Employee("Bob",30.0,location)
location.SetEmployees [alice;bob]
location.Employees // non-empty!
|> List.iter (fun employee ->
printfn "employee %s works at %s" employee.Name employee.LocationName)
817
module StructuralDependencyExample_SeparateTypes =
module DomainTypes =
type Employee = {name:string; age:float; location:Location}
and Location = {name:string; mutable employees: Employee list}
module Employee =
open DomainTypes
let Name (employee:Employee) = employee.name
let Age (employee:Employee) = employee.age
let Location (employee:Employee) = employee.location
let LocationName (employee:Employee) = employee.location.name
module Location =
open DomainTypes
let Name (location:Location) = location.name
let Employees (location:Location) = location.employees
let AverageAge (location:Location) =
location.employees |> List.averageBy (fun e -> e.age)
module Test =
open DomainTypes
let location = { name="NY"; employees= [] }
let alice = {name="Alice"; age=20.0; location=location }
let bob = {name="Bob"; age=30.0; location=location }
location.employees <- [alice;bob]
Location.Employees location
|> List.iter (fun e ->
printfn "employee %s works at %s" (Employee.Name e) (Employee.LocationName
e) )
Before we go any further, let's remove some unneeded code. One nice thing about using a
record type is that you don't need to define "getters", so the only functions you need in the
modules are functions that manipulate the data, such as AverageAge .
818
module StructuralDependencyExample_SeparateTypes2 =
module DomainTypes =
type Employee = {name:string; age:float; location:Location}
and Location = {name:string; mutable employees: Employee list}
module Employee =
open DomainTypes
let LocationName employee = employee.location.name
module Location =
open DomainTypes
let AverageAge location =
location.employees |> List.averageBy (fun e -> e.age)
Parameterizing again
Once again, we can remove the dependency by creating a parameterized version of the
types.
Let's step back and think about the "location" concept. Why does a location have to only
contain Employees? If we make it a bit more generic, we could consider a location as being
a "place" plus "a list of things at that place".
For example, if the things are products, then a place full of products might be a warehouse.
If the things are books, then a place full of books might be a library.
Here are these concepts expressed in code:
module LocationOfThings =
type Location<'Thing> = {name:string; mutable things: 'Thing list}
type Employee = {name:string; age:float; location:Location<Employee> }
type WorkLocation = Location<Employee>
type Product = {SKU:string; price:float }
type Warehouse = Location<Product>
type Book = {title:string; author:string}
type Library = Location<Book>
Of course, these locations are not exactly the same, but there might be something in
common that you can extract into a generic design, especially as there is no behavior
requirement attached to the things they contain.
819
So, using the "location of things" design, here is our dependency rewritten to use
parameterized types.
module StructuralDependencyExample_SeparateTypes_Parameterized =
module DomainTypes =
type Location<'Thing> = {name:string; mutable things: 'Thing list}
type Employee = {name:string; age:float; location:Location<Employee> }
module Employee =
open DomainTypes
let LocationName employee = employee.location.name
module Test =
open DomainTypes
let location = { name="NY"; things = [] }
let alice = {name="Alice"; age=20.0; location=location }
let bob = {name="Bob"; age=30.0; location=location }
location.things <- [alice;bob]
let employees = location.things
employees
|> List.iter (fun e ->
printfn "employee %s works at %s" (e.name) (Employee.LocationName e) )
let averageAge =
employees
|> List.averageBy (fun e -> e.age)
In this revised design you will see that the AverageAge function has been completely
removed from the Location module. There is really no need for it, because we can do
these kinds of calculations quite well "inline" without needing the overhead of special
functions.
And if you think about it, if we did need to have such a function pre-defined, it would
probably be more appropriate to put in the Employee module rather than the Location
module. After all, the functionality is much more related to how employees work than how
locations work.
Here's what I mean:
module Employee =
let AverageAgeAtLocation location =
location.things |> List.averageBy (fun e -> e.age)
820
This is one advantage of modules over classes; you can mix and match functions with
different types, as long as they are all related to the underlying use cases.
821
module StructuralDependencyExample_Normalized =
module DomainTypes =
type Relationship<'Left,'Right> = 'Left * 'Right
type Location= {name:string}
type Employee = {name:string; age:float }
module Employee =
open DomainTypes
let EmployeesAtLocation location relations =
relations
|> List.filter (fun (loc,empl) -> loc = location)
|> List.map (fun (loc,empl) -> empl)
let AverageAgeAtLocation location relations =
EmployeesAtLocation location relations
|> List.averageBy (fun e -> e.age)
module Test =
open DomainTypes
let location = { Location.name="NY"}
let alice = {name="Alice"; age=20.0; }
let bob = {name="Bob"; age=30.0; }
let relations = [
(location,alice)
(location,bob)
]
relations
|> List.iter (fun (loc,empl) ->
printfn "employee %s works at %s" (empl.name) (loc.name) )
Inheritance dependencies
Finally, let's look at an "inheritance dependency".
Type A stores a value of type B in a property
Type B inherits from type A
We'll consider a UI control hierarchy, where every control belongs to a top-level "Form", and
the Form itself is a Control.
822
The thing to note here is that the Form passes itself in as the form value for the Control
constructor.
This code will compile, but will cause a NullReferenceException error at runtime. This kind of
technique will work in C#, but not in F#, because the class initialization logic is done
differently.
Anyway, this is a terrible design. The form shouldn't have to pass itself in to a constructor.
A better design, which also fixes the constructor error, is to make Control an abstract class
instead, and distinguish between non-form child classes (which do take a form in their
constructor) and the Form class itself, which doesn't.
Here's some sample code:
823
module InheritanceDependencyExample2 =
[<AbstractClass>]
type Control(name) =
member this.Name = name
abstract Form : Form
and Form(name) =
inherit Control(name)
override this.Form = this
and Button(name,form) =
inherit Control(name)
override this.Form = form
// test
let form = new Form("form")
let button = new Button("button",form)
// test
let form = new Form("form")
let button = new Button("button",form)
824
A functional version
I will leave a functional design as an exercise for you to do yourself.
If we were going for truly functional design, we probably would not be using inheritance at
all. Instead, we would use composition in conjunction with parameterization.
But that's a big topic, so I'll save it for another day.
Summary
I hope that this post has given you some useful tips on removing dependency cycles. With
these various approaches in hand, any problems with module organization should be able to
be resolved easily.
In the next post in this series, I'll look at dependency cycles "in the wild", by comparing some
real world C# and F# projects.
As we have seen, F# is a very opinionated language! It wants us to use modules instead of
classes and it prohibits dependency cycles. Are these just annoyances, or do they really
make a difference to the way that code is organized? Read on and find out!
825
The plan
My plan was to take ten or so projects written in C# and ten or so projects written in F#, and
somehow compare them.
I didn't want to spend too much time on this, and so rather than trying to analyze the source
files, I thought I would cheat a little and analyze the compiled assemblies, using the
Mono.Cecil library.
This also meant that I could get the binaries directly, using NuGet.
The projects I picked were:
C# projects
Mono.Cecil, which inspects programs and libraries in the ECMA CIL format.
NUnit
SignalR for real-time web functionality.
NancyFx, a web framework
YamlDotNet, for parsing and emitting YAML.
SpecFlow, a BDD tool.
Json.NET.
Entity Framework.
ELMAH, a logging framework for ASP.NET.
NuGet itself.
Moq, a mocking framework.
NDepend, a code analysis tool.
826
And, to show I'm being fair, a business application that I wrote in C#.
F# projects
Unfortunately, there is not yet a wide variety of F# projects to choose from. I picked the
following:
FSharp.Core, the core F# library.
FSPowerPack.
FsUnit, extensions for NUnit.
Canopy, a wrapper around the Selenium test automation tool.
FsSql, a nice little ADO.NET wrapper.
WebSharper, the web framework.
TickSpec, a BDD tool.
FSharpx, an F# library.
FParsec, a parser library.
FsYaml, a YAML library built on FParsec.
Storm, a tool for testing web services.
Foq, a mocking framework.
Another business application that I wrote, this time in F#.
I did choose SpecFlow and TickSpec as being directly comparable, and also Moq and and
Foq.
But as you can see, most of the F# projects are not directly comparable to the C# ones. For
example, there is no direct F# equivalent to Nancy, or Entity Framework.
Nevertheless, I was hoping that I might observe some sort of pattern by comparing the
projects. And I was right. Read on for the results!
827
Dependencies
Once we have our units of modularity, we can look at dependencies between modules.
For this analysis, I only want to include dependencies between types in the same assembly.
In other words, dependencies on system types such as String or List do not count as a
dependency.
Let's say we have a top-level type A and another top-level type B . Then I say that a
dependency exists from A to B if:
Type A or any of its nested types inherits from (or implements) type B or any of its
nested types.
Type A or any of its nested types has a field, property or method that references type
B or any of its nested types as a parameter or return value. This includes private
828
Type A or any of its nested types has a method implementation that references type
B or any of its nested types.
This might not be a perfect definition. But it is good enough for my purposes.
In addition to all dependencies, I thought it might be useful to look at "public" or "published"
dependencies. A public dependency from A to B exists if:
Type A or any of its nested types inherits from (or implements) type B or any of its
nested types.
Type A or any of its nested types has a public field, property or method that references
type B or any of its nested types as a parameter or return value.
Finally, a public dependency is only counted if the source type itself is public.
The metrics I chose for dependencies were:
The total number of dependencies. This is simply the sum of all dependencies of all
types. Again, there will be more dependencies in a larger project, but we will also take
the size of the project into account.
The number of types that have more than X dependencies. This gives us an idea of
how many types are "too" complex.
Cyclic dependencies
Given this definition of dependency, then, a cyclic dependency occurs when two different
top-level types depend on each other.
Note what not included in this definition. If a nested type in a module depends on another
nested type in the same module, then that is not a cyclic dependency.
If there is a cyclic dependency, then there is a set of modules that are all linked together. For
example, if A depends on B , B depends on C , and then say, C depends on A , then
A , B and C are linked together. In graph theory, this is called a strongly connected
component.
The metrics I chose for cyclic dependencies were:
The number of cycles. That is, the number of strongly connected components which
had more than one module in them.
The size of the largest component. This gives us an idea of how complex the
dependencies are.
I analyzed cyclic dependencies for all dependencies and also for public dependencies only.
829
Modularity
Let's look at the modularity first.
Here are the modularity-related results for the C# projects:
830
Project
Code
size
Toplevel
types
Authored
types
All
types
Code/Top
Code/Auth
ef
269521
514
565
876
524
477
jsonDotNet
148829
215
232
283
692
642
nancy
143445
339
366
560
423
392
cecil
101121
240
245
247
421
413
nuget
114856
216
237
381
532
485
signalR
65513
192
229
311
341
286
nunit
45023
173
195
197
260
231
specFlow
46065
242
287
331
190
161
elmah
43855
116
140
141
378
313
yamlDotNet
23499
70
73
73
336
322
fparsecCS
57474
41
92
93
1402
625
moq
133189
397
420
533
335
317
ndepend
478508
734
828
843
652
578
ndependPlat
151625
185
205
205
820
740
personalCS
422147
195
278
346
2165
1519
TOTAL
2244670
3869
4392
5420
580
511
831
Project
Code
size
Toplevel
types
Authored
types
All
types
Code/Top
Code/Auth
fsxCore
339596
173
328
2024
1963
1035
fsCore
226830
154
313
1186
1473
725
fsPowerPack
117581
93
150
410
1264
784
storm
73595
67
70
405
1098
1051
fParsec
67252
24
245
8407
2802
websharper
47391
52
128
285
911
370
tickSpec
30797
34
49
170
906
629
websharperHtml
14787
18
28
72
822
528
canopy
15105
16
103
2518
944
fsYaml
15191
11
160
2170
1381
fsSql
15434
13
18
162
1187
857
fsUnit
1848
924
616
foq
26957
35
48
103
770
562
personalFS
118893
30
146
655
3963
814
TOTAL
1111257
692
1332
5987
1606
834
832
Code/Auth is the number of CIL instructions per authored type. This is a measure of
how "big" each authored type is.
Code/All is the number of CIL instructions per type. This is a measure of how "big" each
type is.
Auth/Top is the ratio of all authored types to the top-level-types. It is a rough measure
of how many authored types are in each unit of modularity.
All/Top is the ratio of all types to the top-level-types. It is a rough measure of how many
types are in each unit of modularity.
Analysis
The first thing I noticed is that, with a few exceptions, the code size is bigger for the C#
projects than for the F# projects. Partly that is because I picked bigger projects, of course.
But even for a somewhat comparable project like SpecFlow vs. TickSpec, the SpecFlow
code size is bigger. It may well be that SpecFlow does a lot more than TickSpec, of course,
but it also may be a result of using more generic code in F#. There is not enough information
to know either way right now -- it would be interesting to do a true side by side comparison.
Next, the number of top-level types. I said earlier that this should correspond to the number
of files in a project. Does it?
I didn't get all the sources for all the projects to do a thorough check, but I did a couple of
spot checks. For example, for Nancy, there are 339 top level classes, which implies that
there should be about 339 files. In fact, there are actually 322 .cs files, so not a bad
estimate.
On the other hand, for SpecFlow there are 242 top level types, but only 171 .cs files, so a bit
of an overestimate there. And for Cecil, the same thing: 240 top level classes but only 128
.cs files.
For the FSharpX project, there are 173 top level classes, which implies there should be
about 173 files. In fact, there are actually only 78 .fs files, so it is a serious over-estimate by
a factor of more than 2. And if we look at Storm, there are 67 top level classes. In fact, there
are actually only 35 .fs files, so again it is an over-estimate by a factor of 2.
So it looks like the number of top level classes is always an over-estimate of the number of
files, but much more so for F# than for C#. It would be worth doing some more detailed
analysis in this area.
833
The "Code/Top" ratio is consistently bigger for F# code than for C# code. Overall, the
average top-level type in C# is converted into 580 instructions. But for F# that number is
1606 instructions, about three times as many.
I expect that this is because F# code is more concise than C# code. I would guess that 500
lines of F# code in a single module would create many more CIL instructions than 500 lines
of C# code in a class.
If we visually plot "Code size" vs. "Top-level types", we get this chart:
What's surprising to me is how distinct the F# and C# projects are in this chart. The C#
projects seem to have a consistent ratio of about 1-2 top-level types per 1000 instructions,
even across different project sizes. And the F# projects are consistent too, having a ratio of
about 0.6 top-level types per 1000 instructions.
In fact, the number of top level types in F# projects seems to taper off as projects get larger,
rather than increasing linearly like the C# projects.
834
The message I get from this chart is that, for a given size of project, an F# implementation
will have fewer modules, and presumably less complexity as a result.
You probably noticed that there are two anomalies. Two C# projects are out of place -- the
one at the 50K mark is FParsecCS and the one at the 425K mark is my business
application.
I am fairly certain that this because both these implementations have some rather large C#
classes in them, which helps the code ratio. Probably a necessarily evil for a parser, but in
the case of my business application, I know that it is due to cruft accumulating over the
years, and there are some massive classes that ought to be refactored into smaller ones. So
a metric like this is probably a bad sign for a C# code base.
835
This is surprisingly linear for F#. The total number of types (including compiler generated
ones) seems to depend closely on the size of the project. On the other hand, the number of
types for C# seems to vary a lot.
The average "size" of a type is somewhat smaller for F# code than for C# code. The
average type in C# is converted into about 400 instructions. But for F# that number is about
180 instructions.
I'm not sure why this is. Is it because the F# types are more fine-grained, or could it be
because the F# compiler generates many more little types than the C# compiler? Without
doing a more subtle analysis, I can't tell.
836
Again, there is a significant difference. For each unit of modularity in C# there are an
average of 1.1 authored types. But in F# the average is 1.9, and for some projects a lot more
than that.
Of course, creating nested types is trivial in F#, and quite uncommon in C#, so you could
argue that this is not a fair comparison. But surely the ability to create a dozen types in as
many lines of F# has some effect on the quality of the design? This is harder to do in C#, but
there is nothing to stop you. So might this not mean that there is a temptation in C# to not be
as fine-grained as you could potentially be?
The project with the highest ratio (4.9) is my F# business application. I believe that this is
due to this being only F# project in this list which is designed around a specific business
domain, I created many "little" types to model the domain accurately, using the concepts
described here. For other projects created using DDD principles, I would expect to see this
same high number.
837
Dependencies
Now let's look at the dependency relationships between the top level classes.
Here are the results for the C# projects:
Dep/Top
One
or
more
dep.
Three
or
more
dep.
Five
or
more
dep.
Ten
or
more
dep.
Diagram
2354
4.6
76%
51%
32%
13%
svg
215
913
4.2
69%
42%
30%
14%
svg
nancy
339
1132
3.3
78%
41%
22%
6%
svg
cecil
240
1145
4.8
73%
43%
23%
13%
svg
nuget
216
833
3.9
71%
43%
26%
12%
svg
signalR
192
641
3.3
66%
34%
19%
10%
svg
nunit
173
499
2.9
75%
39%
13%
4%
svg
specFlow
242
578
2.4
64%
25%
17%
5%
svg
elmah
116
300
2.6
72%
28%
22%
6%
svg
yamlDotNet
70
228
3.3
83%
30%
11%
4%
svg
fparsecCS
41
64
1.6
59%
29%
5%
0%
svg
moq
397
1100
2.8
63%
29%
17%
7%
svg
ndepend
734
2426
3.3
67%
37%
25%
10%
svg
ndependPlat
185
404
2.2
67%
24%
11%
4%
svg
personalCS
195
532
2.7
69%
29%
19%
7%
TOTAL
3869
13149
3.4
70%
37%
22%
9%
Top
Level
Types
Total
Dep.
Count
ef
514
jsonDotNet
Project
838
Dep/Top
One
or
more
dep.
Three
or
more
dep.
Five
or
more
dep.
Ten
or
more
dep.
76
0.4
30%
4%
1%
0%
154
287
1.9
55%
26%
14%
3%
fsPowerPack
93
68
0.7
38%
13%
2%
0%
storm
67
195
2.9
72%
40%
18%
4%
fParsec
1.1
63%
25%
0%
0%
websharper
52
18
0.3
31%
0%
0%
0%
tickSpec
34
48
1.4
50%
15%
9%
3%
websharperHtml
18
37
2.1
78%
39%
6%
0%
canopy
1.3
50%
33%
0%
0%
fsYaml
10
1.4
71%
14%
0%
0%
fsSql
13
14
1.1
54%
8%
8%
0%
fsUnit
0.0
0%
0%
0%
0%
foq
35
66
1.9
66%
29%
11%
0%
personalFS
30
111
3.7
93%
60%
27%
7%
TOTAL
692
947
1.4
49%
19%
8%
1%
Top
Level
Types
Total
Dep.
Count
fsxCore
173
fsCore
Project
839
Analysis
These results are very interesting. For C#, the number of total dependencies increases with
project size. Each top-level type depends on 3-4 others, on average.
On the other hand, the number of total dependencies in an F# project does not seem to vary
too much with project size at all. Each F# module depends on no more than 1-2 others, on
average. And the largest project (FSharpX) has a lower ratio than many of the smaller
projects. My business app and the Storm project are the only exceptions.
Here's a chart of the relationship between code size and the number of dependencies:
The disparity between C# and F# projects is very clear. The C# dependencies seem to grow
linearly with project size, while the F# dependencies seem to be flat.
Distribution of dependencies
840
The average number of dependencies per top level type is interesting, but it doesn't help us
understand the variability. Are there many modules with lots of dependencies? Or does each
one just have a few?
This might make a difference in maintainability, perhaps. I would assume that a module with
only one or two dependencies would be easier to understand in the context of the
application that one with tens of dependencies.
Rather than doing a sophisticated statistical analysis, I thought I would keep it simple and
just count how many top level types had one or more dependencies, three or more
dependencies, and so on.
Here are the same results, displayed visually:
841
842
843
Each diagram lists all the top-level types found in the project. If there is a dependency from
one type to another, it is shown by an arrow. The dependencies point from left to right where
possible, so any arrows going from right to left implies that there is a cyclic dependency.
The layout is done automatically by graphviz, but in general, the types are organized into
columns or "ranks". For example, the SpecFlow diagram has 12 ranks, and the TickSpec
diagram has five.
As you can see, there are generally a lot of tangled lines in a typical dependency diagram!
How tangled the diagram looks is a sort of visual measure of the code complexity. For
instance, if I was tasked to maintain the SpecFlow project, I wouldn't really feel comfortable
until I had understood all the relationships between the classes. And the more complex the
project, the longer it takes to come up to speed.
844
Looking at the Moq classes (dotfile), we can see it includes the "Castle" library, which I didn't
eliminate from the analysis. Out of the 249 classes with dependencies, only 66 are Moq
specific. If we had considered only the classes in the Moq namespace, we might have had a
cleaner diagram.
On the other hand, looking at the Foq modules (dotfile) there are only 23 modules with
dependencies, fewer even than just the Moq classes alone.
So something is very different with code organization in F#.
classes are all in the same physical file. In F#, they would all be in the same module, and
their relationships would not count as public dependencies. So the C# code is being
penalized in a way.
Even so, looking at the source code, the C# code has 20 source files compared to F#'s 8, so
there is still some difference in complexity.
845
Another way to think about it is that F# encourages high coupling in some areas (modules)
in exchange for low coupling in others. In C#, the only kind of strict coupling available is
class-based. Anything looser, such as using namespaces, has to be enforced using good
practices or a tool such as NDepend.
Whether the F# approach is better or worse depends on your preference. It does make
certain kinds of refactoring harder as a result.
Cyclic dependencies
Finally, we can turn our attention to the oh-so-evil cyclic dependencies. (If you want to know
why they are bad, read this post ).
Here are the cyclic dependency results for the C# projects.
Toplevel
types
Cycle
count
Partic.
Partic.%
Max
comp.
size
Cycle
count
(public)
Partic.
(public)
ef
514
14
123
24%
79
jsonDotNet
215
88
41%
83
11
nancy
339
35
10%
21
cecil
240
125
52%
123
50
nuget
216
24
11%
10
signalR
192
14
7%
nunit
173
80
46%
78
48
specFlow
242
11
5%
elmah
116
8%
yamlDotNet
70
0%
fparsecCS
41
15%
moq
397
50
13%
15
ndepend
734
12
79
11%
22
36
ndependPlat
185
3%
personalCS
195
11
34
17%
19
TOTAL
3869
683
18%
Project
186
846
Toplevel
types
Cycle
count
Partic.
Partic.%
Max
comp.
size
Cycle
count
(public)
Partic.
(public)
fsxCore
173
0%
fsCore
154
3%
fsPowerPack
93
2%
storm
67
0%
fParsec
0%
websharper
52
0%
tickSpec
34
0%
websharperHtml
18
0%
canopy
0%
fsYaml
0%
fsSql
13
0%
fsUnit
0%
foq
35
0%
personalFS
30
0%
TOTAL
692
1%
Project
847
Analysis
If we are looking for cycles in the F# code, we will be sorely disappointed. Only two of the F#
projects have cycles at all, and those are tiny. For example in FSharp.Core there is a mutual
dependency between two types right next to each other in the same file, here.
On the other hand, almost all the C# projects have one or more cycles. Entity Framework
has the most cycles, involving 24% of the classes, and Cecil has the worst participation rate,
with over half of the classes being involved in a cycle.
Even NDepend has cycles, although to be fair, there may be good reasons for this. First
NDepend focuses on removing cycles between namespaces, not classes so much, and
second, it's possible that the cycles are between types declared in the same source file. As
a result, my method may penalize well-organized C# code somewhat (as noted in the
FParsec vs. FParsecCS discussion above).
848
In C#, there is nothing stopping you from creating cycles -- a perfect example of
accidental complexity. In fact, you have to make a special effort to avoid them.
In F#, of course, it is the other way around. You can't easily create cycles at all.
849
- it is not forced to live in the same class. Over time this approach may create its
own problems too (COBOL anyone?) but right now, I find it a breath of fresh air.
The metrics show that there are an unusually large number of "authored" types per
module (4.9). As I noted above, I think this is a result of having fine-grained DDD-style
design. The code per authored type is in line with the other F# projects, so that implies
they are not too big or small.
Also, as I noted earlier, the inter-module dependencies are the worst of any F# project. I
know that there are some API/service functions that depend on almost all the other
modules, but this could be a clue that they might need refactoring.
However, unlike C# code, I know exactly where to find these problem modules. I
can be fairly certain that all these modules are in the top layer of my application and
will thus appear at the bottom of the module list in Visual Studio. How can I be so
sure? Because...
In terms of cyclic dependencies, it's pretty typical for a F# project. There aren't any.
Summary
I started this analysis from curiosity -- was there any meaningful difference in the
organization of C# and F# projects?
I was quite surprised that the distinction was so clear. Given these metrics, you could
certainly predict which language the assembly was written in.
Project complexity. For a given number of instructions, a C# project is likely to have
many more top level types (and hence files) than an F# one -- more than double, it
seems.
Fine-grained types. For a given number of modules, a C# project is likely to have
fewer authored types than an F# one, implying that the types are not as fine-grained as
they could be.
Dependencies. In a C# project, the number of dependencies between classes
increases linearly with the size of the project. In an F# project, the number of
dependencies is much smaller and stays relatively flat.
Cycles. In a C# project, cycles occur easily unless care is taken to avoid them. In an F#
project, cycles are extremely rare, and if present, are very small.
Perhaps this has do with the competency of the programmer, rather than a difference
between languages? Well, first of all, I think that the quality of the C# projects is quite good
on the whole -- I certainly wouldn't claim that I could write better code! And, in two cases in
particular, the C# and F# projects were written by the same person, and differences were
still apparent, so I don't think this argument holds up.
850
Future work
This approach of using just the binaries might have gone as far as it can go. For a more
accurate analysis, we would need to use metrics from the source code as well (or maybe the
pdb file).
For example, a high "instructions per type" metric is good if it corresponds to small source
files (concise code), but not if it corresponds to large ones (bloated classes). Similarly, my
definition of modularity used top-level types rather than source files, which penalized C#
somewhat over F#.
So, I don't claim that this analysis is perfect (and I hope haven't made a terrible mistake in
the analysis code!) but I think that it could be a useful starting point for further investigation.
Update 2013-06-15
This post caused quite a bit of interest. Based on feedback, I made the following changes:
Assemblies profiled
Added Foq and Moq (at the request of Phil Trelford).
Added the C# component of FParsec (at the request of Dave Thomas and others).
Added two NDepend assemblies.
Added two of my own projects, one C# and one F#.
As you can see, adding seven new data points (five C# and two F# projects) didn't change
the overall analysis.
Algorithm changes
Made definition of "authored" type stricter. Excluded types with
"GeneratedCodeAttribute" and F# types that are subtypes of a sum type. This had an
effect on the F# projects and reduced the "Auth/Top" ratio somewhat.
Text changes
Rewrote some of the analysis.
Removed the unfair comparison of YamlDotNet with FParsec.
Added a comparison of the C# component and F# components of FParsec.
Added a comparison of Moq and Foq.
Added a comparison of my own two projects.
The orginal post is still available here
851
852
Do you want to port C# code to F#? In this series of posts we'll look at various approaches
to this, and the design decisions and trade-offs involved.
Porting from C# to F#: Introduction. Three approaches to porting existing C# code to F#.
Getting started with direct porting. F# equivalents to C#.
853
854
And, as explained in that post and its sequels, these aspects are not just academic, but offer
concrete benefits to you as a developer.
So I have divided the porting process into three levels of sophistication (for lack of a better
term), which represent how well the ported code exploits these benefits.
855
There are two different paths that can get you to this level.
The first path is to do a basic direct port to F#, and then refactor the F# code.
The second path is to convert the existing imperative code to functional code while
staying in C#, and only then port the functional C# code to functional F# code!
The second option might seem clumsy, but for real code it will probably be both faster and
more comfortable. Faster because you can use a tool such as Resharper to do the
refactoring, and more comfortable because you are working in C# until the final port. This
approach also makes it clear that the hard part is not the actual port from C# to F#, but the
conversion of imperative code to functional code!
Porting diagram
Here is a diagram to help you visualize the various porting paths described above.
856
857
Semicolons
Unlike C#'s semicolon, F# does not require any kind of line or statement terminator.
Commas
F# does not use commas for separating parameters or list elements, so remember not to
use commas when porting!
For separating list elements, use semicolons rather than commas.
// C# example
var list = new int[] { 1,2,3}
858
// F# example
let list = [1;2;3] // semicolons
// F# example
let myFunc (x:int) (y:int) (z:int) :int = ... function body ...
let myFunc x y z = ... function body ...
Commas are generally only used for tuples, or for separating parameters when calling .NET
library functions. (See this post for more on tuples vs multiple parameters)
Definitions for all types (classes, structures, interfaces, etc.) use the form:
type someName = // the definition
The use of the = sign is an important difference between F# and C#. Where C# uses curly
braces, F# uses the = and then the following block of code must be indented.
Mutable values
In F#, values are immutable by default. If you are doing a direct imperative port, you
probably need to make some of the values mutable, using the mutable keyword. Then to
assign to the values, use the <- operator, not the equals sign.
// C# example
var variableName = 42
variableName = variableName + 1
859
// F# example
let mutable variableName = 42
variableName <- variableName + 1
Conversion example #1
With these basic guidelines in place, let's look at some real code examples, and do a direct
port for them.
This first example has some very simple code, which we will port line by line. Here's the C#
code.
860
using System;
using System.Collections.Generic;
namespace PortingToFsharp
{
public class Squarer
{
public int Square(int input)
{
var result = input * input;
return result;
}
public void PrintSquare(int input)
{
var result = this.Square(input);
Console.WriteLine("Input={0}. Result={1}",
input, result);
}
}
Unlike C#, F# files do not generally declare namespaces unless they need to interop with
other .NET code. The filename itself acts as a default namespace.
Note that the namespace, if used, must come before anything else, such as "open". This the
opposite order from most C# code.
Note that there are parentheses after the class name. These are required for class
definitions.
More complicated class definitions will be shown in the next example, and you read the
complete discussion of classes.
861
However, because F# can normally infer the parameter and return types, you rarely need to
specify them explicitly.
Here's a more typical F# signature, with inferred types:
let Square input = ... code ...
void
The void keyword in C# is generally not needed, but if required, would be converted to
unit
So the C# code:
void PrintSquare(int input) { ... code ...}
862
but again, the specific types are rarely needed, and so the F# version is just:
let PrintSquare input = ... code ...
// F# value declaration
let result = input * input
Unlike C#, you must always assign ("bind") something to an F# value as part of its
declaration.
// C# example
int unassignedVariable; //valid
// F# example
let unassignedVariable // not valid
As noted above, if you need to change the value after its declaration, you must use the
"mutable" keyword.
863
If you need to specify a type for a value, the type name comes after the value or parameter,
preceded by a colon.
// C# example
int variableName = 42;
// F# example
let variableName:int = 42
However, because F# can normally infer the parameter and return types, you rarely need to
specify them explicitly So here's typical F# code for defining a function and then calling it:
// define a function
let Square input = ... code ...
// call it
let result = Square input
Return values
In C#, you use the return keyword. But in F#, the last value in the block is automatically
the "return" value.
Here's the C# code returning the result variable.
public int Square(int input)
{
var result = input * input;
return result; //explicit "return" keyword
}
864
865
namespace PortingToFsharp
open System
open System.Collections.Generic
type Squarer() =
let Square input =
let result = input * input
result
let PrintSquare input =
let result = Square input
printf "Input=%i. Result=%i" input result
866
In this series, we'll look at some of the ways we can use types as part of the design process.
In particular, the thoughtful use of types can make a design more transparent and improve
correctness at the same time.
This series will be focused on the "micro level" of design. That is, working at the lowest level
of individual types and functions. Higher level design approaches, and the associated
decisions about using functional or object-oriented style, will be discussed in another series.
Many of the suggestions are also feasable in C# or Java, but the lightweight nature of F#
types means that it is much more likely that we will do this kind of refactoring.
Designing with types: Introduction. Making design more transparent and improving
correctness.
Designing with types: Single case union types. Adding meaning to primitive types.
Designing with types: Making illegal states unrepresentable. Encoding business logic in
types.
Designing with types: Discovering new concepts. Gaining deeper insight into the
domain.
Designing with types: Making state explicit. Using state machines to ensure
correctness.
Designing with types: Constrained strings. Adding more semantic information to a
primitive type.
Designing with types: Non-string types. Working with integers and dates safely.
Designing with types: Conclusion. A before and after comparison.
867
A basic example
For demonstration of the various uses of types, I'll work with a very straightforward example,
namely a Contact type, such as the one below.
type Contact =
{
FirstName: string;
MiddleInitial: string;
LastName: string;
EmailAddress: string;
//true if ownership of email address is confirmed
IsEmailVerified: bool;
Address1: string;
Address2: string;
City: string;
State: string;
Zip: string;
//true if validated against address service
IsAddressValid: bool;
}
This seems very obvious -- I'm sure we have all seen something like this many times. So
what can we do with it? How can we refactor this to make the most of the type system?
The first thing to do is to look at the usage pattern of data access and updates. For example,
would be it be likely that Zip is updated without also updating Address1 at the same time?
On the other hand, it might be common that a transaction updates EmailAddress but not
FirstName .
Finally, we can use the option type to signal that certain values, such as MiddleInitial , are
indeed optional.
869
type PersonalName =
{
FirstName: string;
// use "option" to signal optionality
MiddleInitial: string option;
LastName: string;
}
Summary
With all these changes, we now have the following code:
type PersonalName =
{
FirstName: string;
// use "option" to signal optionality
MiddleInitial: string option;
LastName: string;
}
type EmailContactInfo =
{
EmailAddress: string;
IsEmailVerified: bool;
}
type PostalAddress =
{
Address1: string;
Address2: string;
City: string;
State: string;
Zip: string;
}
type PostalContactInfo =
{
Address: PostalAddress;
IsAddressValid: bool;
}
type Contact =
{
Name: PersonalName;
EmailContactInfo: EmailContactInfo;
PostalContactInfo: PostalContactInfo;
}
870
We haven't written a single function yet, but already the code represents the domain better.
However, this is just the beginning of what we can do.
Next up, using single case unions to add semantic meaning to primitive types.
871
EmailAddress: string;
State: string;
Zip: string;
These are all defined as simple strings. But really, are they just strings? Is an email address
interchangable with a zip code or a state abbreviation?
In a domain driven design, they are indeed distinct things, not just strings. So we would
ideally like to have lots of separate types for them so that they cannot accidentally be mixed
up.
This has been known as good practice for a long time, but in languages like C# and Java it
can be painful to create hundred of tiny types like this, leading to the so called "primitive
obsession" code smell.
But F# there is no excuse! It is trivial to create simple wrapper types.
or alternatively, we could use record types with one field, like this:
type EmailAddress = { EmailAddress: string }
type ZipCode = { ZipCode: string }
type StateCode = { StateCode: string}
872
Both approaches can be used to create wrapper types around a string or other primitive
type, so which way is better?
The answer is generally the single case discriminated union. It is much easier to "wrap" and
"unwrap", as the "union case" is actually a proper constructor function in its own right.
Unwrapping can be done using inline pattern matching.
Here's some examples of how an EmailAddress type might be constructed and
deconstructed:
type EmailAddress = EmailAddress of string
// using the constructor as a function
"a" |> EmailAddress
["a"; "b"; "c"] |> List.map EmailAddress
// inline deconstruction
let a' = "a" |> EmailAddress
let (EmailAddress a'') = a'
let addresses =
["a"; "b"; "c"]
|> List.map EmailAddress
let addresses' =
addresses
|> List.map (fun (EmailAddress e) -> e)
873
type PersonalName =
{
FirstName: string;
MiddleInitial: string option;
LastName: string;
}
type EmailAddress = EmailAddress of string
type EmailContactInfo =
{
EmailAddress: EmailAddress;
IsEmailVerified: bool;
}
type ZipCode = ZipCode of string
type StateCode = StateCode of string
type PostalAddress =
{
Address1: string;
Address2: string;
City: string;
State: StateCode;
Zip: ZipCode;
}
type PostalContactInfo =
{
Address: PostalAddress;
IsAddressValid: bool;
}
type Contact =
{
Name: PersonalName;
EmailContactInfo: EmailContactInfo;
PostalContactInfo: PostalContactInfo;
}
Another nice thing about the union type is that the implementation can be encapsulated with
module signatures, as we'll discuss below.
874
This might seem confusing initially, but really they are in different scopes, so there is no
naming collision. One is a type, and one is a constructor function with the same name.
So if you see a function signature like this:
val f: string -> EmailAddress
this refers to things in the world of types, so EmailAddress refers to the type.
On the other hand, if you see some code like this:
let x = EmailAddress y
this refers to things in the world of values, so EmailAddress refers to the constructor
function.
875
876
The disadvantage is that with complex validations, it might not be obvious what went wrong.
Was the email too long, or missing a '@' sign, or an invalid domain? We can't tell.
If you do need more detail, you might want to return a type which contains a more detailed
explanation in the error case.
The following example uses a CreationResult type to indicate the error in the failure case.
type EmailAddress = EmailAddress of string
type CreationResult<'T> = Success of 'T | Error of string
let CreateEmailAddress2 (s:string) =
if System.Text.RegularExpressions.Regex.IsMatch(s,@"^\S+@\S+\.\S+$")
then Success (EmailAddress s)
else Error "Email address must contain an @ sign"
// test
CreateEmailAddress2 "example.com"
Finally, the most general approach uses continuations. That is, you pass in two functions,
one for the success case (that takes the newly constructed email as parameter), and
another for the failure case (that takes the error string as parameter).
type EmailAddress = EmailAddress of string
let CreateEmailAddressWithContinuations success failure (s:string) =
if System.Text.RegularExpressions.Regex.IsMatch(s,@"^\S+@\S+\.\S+$")
then success (EmailAddress s)
else failure "Email address must contain an @ sign"
The success function takes the email as a parameter and the error function takes a string.
Both functions must return the same type, but the type is up to you.
Here is a simple example -- both functions do a printf, and return nothing (i.e. unit).
let success (EmailAddress s) = printfn "success creating email %s" s
let failure msg = printfn "error creating email: %s" msg
CreateEmailAddressWithContinuations success failure "example.com"
CreateEmailAddressWithContinuations success failure "x@example.com"
With continuations, you can easily reproduce any of the other approaches. Here's the way to
create options, for example. In this case both functions return an EmailAddress option .
877
This code seems quite cumbersome, but in practice you would probably create a local
partially applied function that you use instead of the long-winded one.
// setup a partially applied function
let success e = Some e
let failure _ = None
let createEmail = CreateEmailAddressWithContinuations success failure
// use the partially applied function
createEmail "x@example.com"
createEmail "example.com"
878
module EmailAddress =
type T = EmailAddress of string
// wrap
let create (s:string) =
if System.Text.RegularExpressions.Regex.IsMatch(s,@"^\S+@\S+\.\S+$")
then Some (EmailAddress s)
else None
// unwrap
let value (EmailAddress e) = e
The users of the type would then use the module functions to create and unwrap the type.
For example:
879
module EmailAddress =
// private type
type _T = EmailAddress of string
// wrap
let create (s:string) =
if System.Text.RegularExpressions.Regex.IsMatch(s,@"^\S+@\S+\.\S+$")
then Some (EmailAddress s)
else None
// unwrap
let value (EmailAddress e) = e
Of course the type is not really private in this case, but you are encouraging the callers to
always use the "published" functions.
If you really want to encapsulate the internals of the type and force callers to use a
constructor function, you can use module signatures.
Here's a signature file for the email address example:
// FILE: EmailAddress.fsi
module EmailAddress
// encapsulated type
type T
// wrap
val create : string -> T option
// unwrap
val value : T -> string
(Note that module signatures only work in compiled projects, not in interactive scripts, so to
test this, you will need to create three files in an F# project, with the filenames as shown
here.)
Here's the implementation file:
880
// FILE: EmailAddress.fs
module EmailAddress
// encapsulated type
type T = EmailAddress of string
// wrap
let create (s:string) =
if System.Text.RegularExpressions.Regex.IsMatch(s,@"^\S+@\S+\.\S+$")
then Some (EmailAddress s)
else None
// unwrap
let value (EmailAddress e) = e
The type EmailAddress.T exported by the module signature is opaque, so clients cannot
access the internals.
As you can see, this approach enforces the use of the constructor. Trying to create the type
directly ( T.EmailAddress "bad email" ) causes a compile error.
881
In this approach, wrapping is done in the UI layer, or when loading from a persistence layer,
and once the wrapped type is created, it is passed in to the domain layer and manipulated
"whole", as an opaque type. It is suprisingly uncommon that you actually need the wrapped
contents directly when working in the domain itself.
As part of the construction, it is critical that the caller uses the provided constructor rather
than doing its own validation logic. This ensures that "bad" values can never enter the
domain.
For example, here is some code that shows the UI doing its own validation:
let processFormSubmit () =
let s = uiTextBox.Text
if (s.Length < 50)
then // set email on domain object
else // show validation error message
You would pass in a function which gets applied to the inner value, like this:
address |> EmailAddress.apply (printfn "the value is %s")
882
module EmailAddress =
type _T = EmailAddress of string
// create with continuation
let createWithCont success failure (s:string) =
if System.Text.RegularExpressions.Regex.IsMatch(s,@"^\S+@\S+\.\S+$")
then success (EmailAddress s)
else failure "Email address must contain an @ sign"
// create directly
let create s =
let success e = Some e
let failure _ = None
createWithCont success failure s
// unwrap with continuation
let apply f (EmailAddress e) = f e
// unwrap directly
let value e = apply id e
The create and value functions are not strictly necessary, but are added for the
convenience of callers.
883
// unwrap directly
let value e = apply id e
module ZipCode =
type T = ZipCode of string
// create with continuation
let createWithCont success failure (s:string) =
if System.Text.RegularExpressions.Regex.IsMatch(s,@"^\d{5}$")
then success (ZipCode s)
else failure "Zip code must be 5 digits"
// create directly
let create s =
let success e = Some e
let failure _ = None
createWithCont success failure s
// unwrap with continuation
let apply f (ZipCode e) = f e
// unwrap directly
let value e = apply id e
module StateCode =
type T = StateCode of string
// create with continuation
let createWithCont success failure (s:string) =
let s' = s.ToUpper()
let stateCodes = ["AZ";"CA";"NY"] //etc
if stateCodes |> List.exists ((=) s')
then success (StateCode s')
else failure "State is not in list"
// create directly
let create s =
let success e = Some e
let failure _ = None
createWithCont success failure s
// unwrap with continuation
let apply f (StateCode e) = f e
// unwrap directly
let value e = apply id e
type PersonalName =
{
FirstName: string;
MiddleInitial: string option;
884
LastName: string;
}
type EmailContactInfo =
{
EmailAddress: EmailAddress.T;
IsEmailVerified: bool;
}
type PostalAddress =
{
Address1: string;
Address2: string;
City: string;
State: StateCode.T;
Zip: ZipCode.T;
}
type PostalContactInfo =
{
Address: PostalAddress;
IsAddressValid: bool;
}
type Contact =
{
Name: PersonalName;
EmailContactInfo: EmailContactInfo;
PostalContactInfo: PostalContactInfo;
}
By the way, notice that we now have quite a lot of duplicate code in the three wrapper type
modules. What would be a good way of getting rid of it, or at least making it cleaner?
Summary
To sum up the use of discriminated unions, here are some guidelines:
Do use single case discriminated unions to create types that represent the domain
accurately.
If the wrapped value needs validation, then provide constructors that do the validation
and enforce their use.
Be clear what happens when validation fails. In simple cases, return option types. In
more complex cases, let the caller pass in handlers for success and failure.
If the wrapped value has many associated functions, consider moving it into its own
module.
If you need to enforce encapsulation, use signature files.
885
We're still not done with refactoring. We can alter the design of types to enforce business
rules at compile time -- making illegal states unrepresentable.
Update
Many people have asked for more information on how to ensure that constrained types such
as EmailAddress are only created through a special constructor that does the validation. So
I have created a gist here that has some detailed examples of other ways of doing it.
886
Now let's say that we have the following simple business rule: "A contact must have an email
or a postal address". Does our type conform to this rule?
The answer is no. The business rule implies that a contact might have an email address but
no postal address, or vice versa. But as it stands, our type requires that a contact must
always have both pieces of information.
The answer seems obvious -- make the addresses optional, like this:
type Contact =
{
Name: PersonalName;
EmailContactInfo: EmailContactInfo option;
PostalContactInfo: PostalContactInfo option;
}
But now we have gone too far the other way. In this design, it would be possible for a contact
to have neither type of address at all. But the business rule says that at least one piece of
information must be present.
What's the solution?
887
This design meets the requirements perfectly. All three cases are explictly represented, and
the fourth possible case (with no email or postal address at all) is not allowed.
Note that for the "email and post" case, I just used a tuple type for now. It's perfectly
adequate for what we need.
Constructing a ContactInfo
Now let's see how we might use this in practice. We'll start by creating a new contact:
let contactFromEmail name emailStr =
let emailOpt = EmailAddress.create emailStr
// handle cases when email is valid or invalid
match emailOpt with
| Some email ->
let emailContactInfo =
{EmailAddress=email; IsEmailVerified=false}
let contactInfo = EmailOnly emailContactInfo
Some {Name=name; ContactInfo=contactInfo}
| None -> None
let name = {FirstName = "A"; MiddleInitial=None; LastName="Smith"}
let contactOpt = contactFromEmail name "abc@example.com"
In this code, we have created a simple helper function contactFromEmail to create a new
contact by passing in a name and email. However, the email might not be valid, so the
function has to handle both cases, which it doesn't by returning a Contact option , not a
Contact
888
Updating a ContactInfo
Now if we need to add a postal address to an existing ContactInfo , we have no choice but
to handle all three possible cases:
If a contact previously only had an email address, it now has both an email address and
a postal address, so return a contact using the EmailAndPost case.
If a contact previously only had a postal address, return a contact using the PostOnly
case, replacing the existing address.
If a contact previously had both an email address and a postal address, return a contact
with using the EmailAndPost case, replacing the existing address.
So here's a helper method that updates the postal address. You can see how it explicitly
handles each case.
let updatePostalAddress contact newPostalAddress =
let {Name=name; ContactInfo=contactInfo} = contact
let newContactInfo =
match contactInfo with
| EmailOnly email ->
EmailAndPost (email,newPostalAddress)
| PostOnly _ -> // ignore existing address
PostOnly newPostalAddress
| EmailAndPost (email,_) -> // ignore existing address
EmailAndPost (email,newPostalAddress)
// make a new contact
{Name=name; ContactInfo=newContactInfo}
889
WARNING: I am using option.Value to extract the contents of an option in this code. This is
ok when playing around interactively but is extremely bad practice in production code! You
should always use matching to handle both cases of an option.
Finally, if the logic is represented by a type, any changes to the business rules will
immediately create breaking changes, which is a generally a good thing.
In the next post, we'll dig deeper into the last point. As you try to represent business logic
using types, you may suddenly find that can gain a whole new insight into the domain.
890
Now let's say that the business decides that phone numbers need to be supported as well.
The new business rule is: "A contact must have at least one of the following: an email, a
postal address, a home phone, or a work phone".
How can we represent this now?
A little thought reveals that there are 15 possible combinations of these four contact
methods. Surely we don't want to create a union case with 15 choices? Is there a better
way?
Let's hold that thought and look at a different but related problem.
And, also let's say that you have created a printReport function that loops through the
information and prints it out in a report:
891
// mock code
let printEmail emailAddress =
printfn "Email Address is %s" emailAddress
// mock code
let printPostalAddress postalAddress =
printfn "Postal Address is %s" postalAddress
let printReport contactInfo =
let {
EmailAddresses = emailAddresses;
PostalAddresses = postalAddresses;
} = contactInfo
for email in emailAddresses do
printEmail email
for postalAddress in postalAddresses do
printPostalAddress postalAddress
If you make this change, you also want to make sure that all the functions that process the
contact infomation are updated to handle the new phone cases as well.
Certainly, you will be forced to fix any pattern matches that break. But in many cases, you
would not be forced to handle the new cases.
For example, here's printReport updated to work with the new lists:
892
Can you see the deliberate mistake? Yes, I forgot to change the function to handle the
phones. The new fields in the record have not caused the code to break at all. There is no
guarantee that you will remember to handle the new cases. It would be all too easy to forget.
Again, we have the challenge: can we design types such that these situations cannot easily
happen?
893
And the reporting code must now be changed to handle the new type as well:
// mock code
let printContactMethod cm =
match cm with
| Email emailAddress ->
printfn "Email Address is %s" emailAddress
| PostalAddress postalAddress ->
printfn "Postal Address is %s" postalAddress
| HomePhone phoneNumber ->
printfn "Home Phone is %s" phoneNumber
| WorkPhone phoneNumber ->
printfn "Work Phone is %s" phoneNumber
let printReport contactInfo =
let {
ContactMethods=methods;
} = contactInfo
methods
|> List.iter printContactMethod
894
type Contact =
{
Name: PersonalName;
ContactMethods: ContactMethod list;
}
But this is still not quite right. The list could be empty. How can we enforce the rule that there
must be at least one contact method?
The simplest way is to create a new field that is required, like this:
type Contact =
{
Name: PersonalName;
PrimaryContactMethod: ContactMethod;
SecondaryContactMethods: ContactMethod list;
}
In this design, the PrimaryContactMethod is required, and the secondary contact methods
are optional, which is exactly what the business rule requires!
And this refactoring too, has given us some insight. It may be that the concepts of "primary"
and "secondary" contact methods might, in turn, clarify code in other areas, creating a
cascading change of insight and refactoring.
Summary
In this post, we've seen how using types to model business rules can actually help you to
understand the domain at a deeper level.
In the Domain Driven Design book, Eric Evans devotes a whole section and two chapters in
particular (chapters 8 and 9) to discussing the importance of refactoring towards deeper
insight. The example in this post is simple in comparison, but I hope that it shows that how
an insight like this can help improve both the model and the code correctness.
In the next post, we'll see how types can help with representing fine-grained states.
895
Background
In an earlier post in this series, we looked at single case unions as a wrapper for types such
as email addresses.
module EmailAddress =
type T = EmailAddress of string
let create (s:string) =
if System.Text.RegularExpressions.Regex.IsMatch(s,@"^\S+@\S+\.\S+$")
then Some (EmailAddress s)
else None
This code assumes that either an address is valid or it is not. If it is not, we reject it
altogether and return None instead of a valid value.
But there are degrees of validity. For example, what happens if we want to keep an invalid
email address around rather than just rejecting it? In this case, as usual, we want to use the
type system to make sure that we don't get a valid address mixed up with an invalid
address.
The obvious way to do this is with a union type:
module EmailAddress =
type T =
| ValidEmailAddress of string
| InvalidEmailAddress of string
let create (s:string) =
if System.Text.RegularExpressions.Regex.IsMatch(s,@"^\S+@\S+\.\S+$")
then ValidEmailAddress s // change result type
else InvalidEmailAddress s // change result type
// test
let valid = create "abc@example.com"
let invalid = create "example.com"
896
and with these types we can ensure that only valid emails get sent:
let sendMessageTo t =
match t with
| ValidEmailAddress email ->
// send email
| InvalidEmailAddress _ ->
// ignore
State machines
In the example above, the "valid" and "invalid" cases are mutually incompatible. That is, a
valid email can never become invalid, and vice versa.
But in many cases, it is possible to go from one case to another, triggered by some kind of
event. At which point we have a "state machine", where each case represents a "state", and
moving from one state to another is a "transition".
Some examples:
A email address might have states "Unverified" and "Verified", where you can transition
from the "Unverified" state to the "Verified" state by asking the user to click on a link in a
confirmation email.
A shopping cart might have states "Empty", "Active" and "Paid", where you can
transition from the "Empty" state to the "Active" state by adding an item to the cart, and
to the "Paid" state by paying.
897
In each of these cases, we have a set of states, a set of transitions, and events that can
trigger a transition.
State machines are often represented by a table, like this one for a shopping cart:
Current
State
Event>
Add Item
Remove Item
Pay
Empty
new state =
Active
n/a
n/a
Active
new state =
Active
new state =
Paid
n/a
n/a
n/a
Paid
With a table like this, you can quickly see exactly what should happen for each event when
the system is in a given state.
898
It is all too easy to have important states that are implicit but never documented.
For example, the "empty cart" has different behavior from the "active cart" but it would be
rare to see this documented explicitly in code.
It is a design tool that forces you to think about every possibility that could occur.
A common cause of errors is that certain edge cases are not handled, but a state machine
forces all cases to be thought about.
For example, what should happen if we try to verify an already verified email? What happens
if we try to remove an item from an empty shopping cart? What happens if white tries to play
when the state is "BlackToPlay"? And so on.
Note that the EmptyCart state has no data, so no special type is needed.
Each event is then represented by a function that accepts the entire state machine (the
union type) and returns a new version of the state machine (again, the union type).
Here's an example using two of the shopping cart events:
899
You can see that from the caller's point of view, the set of states is treated as "one thing" for
general manipulation (the ShoppingCart type), but when processing the events internally,
each state is treated separately.
900
You will see that the original makePayment function takes a cart and results in a cart, while
the new function takes an ActiveCartData and results in a PaidCartData , which seems to
be much more relevant.
But if you did this, how would you handle the same event when the cart was in a different
state, such as empty or paid? Someone has to handle the event for all three possible states
somewhere, and it is much better to encapsulate this business logic inside the function than
to be at the mercy of the caller.
By using a list of PaidCartData as the parameter rather than ShoppingCart itself, I ensure
that I cannot accidentally report on unpaid carts.
If you do this, it should be in a supporting function to the event handlers, never the event
handlers themselves.
901
type EmailContactInfo =
{
EmailAddress: EmailAddress.T;
IsEmailVerified: bool;
}
Any time you see a flag like this, chances are you are dealing with state. In this case, the
boolean is used to indicate that we have two states: "Unverified" and "Verified".
As mentioned above, there will probably be various business rules associated with what is
permissible in each state. For example, here are two:
Business rule: "Verification emails should only be sent to customers who have
unverified email addresses"
Business rule: "Password reset emails should only be sent to customers who have
verified email addresses"
As before, we can use types to ensure that code conforms to these rules.
Let's rewrite the EmailContactInfo type using a state machine. We'll put it in an module as
well.
We'll start by defining the two states.
For the "Unverified" state, the only data we need to keep is the email address.
For the "Verified" state, we might want to keep some extra data in addition to the email
address, such as the date it was verified, the number of recent password resets, on so
on. This data is not relevant (and should not even be visible) to the "Unverified" state.
module EmailContactInfo =
open System
// placeholder
type EmailAddress = string
// UnverifiedData = just the email
type UnverifiedData = EmailAddress
// VerifiedData = email plus the time it was verified
type VerifiedData = EmailAddress * DateTime
// set of states
type T =
| UnverifiedState of UnverifiedData
| VerifiedState of VerifiedData
902
Note that for the UnverifiedData type I just used a type alias. No need for anything more
complicated right now, but using a type alias makes the purpose explicit and helps with
refactoring.
Now let's handle the construction of a new state machine, and then the events.
Construction always results in an unverified email, so that is easy.
There is only one event that transitions from one state to another: the "verified" event.
module EmailContactInfo =
// types as above
let create email =
// unverified on creation
UnverifiedState email
// handle the "verified" event
let verified emailContactInfo dateVerified =
match emailContactInfo with
| UnverifiedState email ->
// construct a new info in the verified state
VerifiedState (email, dateVerified)
| VerifiedState _ ->
// ignore
emailContactInfo
Note that, as discussed here, every branch of the match must return the same type, so when
ignoring the verified state we must still return something, such as the object that was passed
in.
Finally, we can write the two utility functions sendVerificationEmail and sendPasswordReset .
903
module EmailContactInfo =
// types and functions as above
let sendVerificationEmail emailContactInfo =
match emailContactInfo with
| UnverifiedState email ->
// send email
printfn "sending email"
| VerifiedState _ ->
// do nothing
()
let sendPasswordReset emailContactInfo =
match emailContactInfo with
| UnverifiedState email ->
// ignore
()
| VerifiedState _ ->
// ignore
printfn "sending password reset"
There are some obvious business rules that come out of this diagram:
Rule: "You can't put a package on a truck if it is already out for delivery"
Rule: "You can't sign for a package that is already delivered"
and so on.
904
Now, without using union types, we might represent this design by using an enum to
represent the state, like this:
open System
type PackageStatus =
| Undelivered
| OutForDelivery
| Delivered
type Package =
{
PackageId: int;
PackageStatus: PackageStatus;
DeliveryDate: DateTime;
DeliverySignature: string;
}
And then the code to handle the "putOnTruck" and "signedFor" events might look like this:
let putOnTruck package =
{package with PackageStatus=OutForDelivery}
let signedFor package signature =
let {PackageStatus=packageStatus} = package
if (packageStatus = Undelivered)
then
failwith "package not out for delivery"
else if (packageStatus = OutForDelivery)
then
{package with
PackageStatus=OutForDelivery;
DeliveryDate = DateTime.UtcNow;
DeliverySignature=signature;
}
else
failwith "package already delivered"
905
But as usual, the idiomatic and more type-safe F# approach is to use an overall union type
rather than embed a status value inside a data structure.
open System
type UndeliveredData =
{
PackageId: int;
}
type OutForDeliveryData =
{
PackageId: int;
}
type DeliveredData =
{
PackageId: int;
DeliveryDate: DateTime;
DeliverySignature: string;
}
type Package =
| Undelivered of UndeliveredData
| OutForDelivery of OutForDeliveryData
| Delivered of DeliveredData
906
Note: I am using failWith to handle the errors. In a production system, this code should be
replaced by client driven error handlers. See the discussion of handling constructor errors in
the post about single case DUs for some ideas.
You can guess that Orders can be "new", "paid", "shipped" or "returned", and have
timestamps and extra information for each transition, but this is not made explicit in the
structure.
The option types are a clue that this type is trying to do too much. At least F# forces you to
use options -- in C# or Java these might just be nulls, and you would have no idea from the
type definition whether they were required or not.
And now let's look at the kind of ugly code that might test these option types to see what
state the order is in.
Again, there is some important business logic that depends on the state of the order, but
nowhere is it explicitly documented what the various states and transitions are.
907
Note: I added IsSome to test for option values being present as a direct port of the way that
a C# program would test for null . But IsSome is both ugly and dangerous. Don't use it!
Here is a better approach using types that makes the states explicit.
908
open System
type InitialOrderData =
{
OrderId: int;
PlacedDate: DateTime;
}
type PaidOrderData =
{
Date: DateTime;
Amount: float;
}
type ShippedOrderData =
{
Date: DateTime;
Method: string;
}
type ReturnedOrderData =
{
Date: DateTime;
Reason: string;
}
type Order =
| Unpaid of InitialOrderData
| Paid of InitialOrderData * PaidOrderData
| Shipped of InitialOrderData * PaidOrderData * ShippedOrderData
| Returned of InitialOrderData * PaidOrderData * ShippedOrderData * ReturnedOrderD
ata
909
Note: Here I am using printfn to handle the errors. In a production system, do use a
different approach.
910
Consider a blog authoring application. Typically, each blog post can be in a state such as
"Draft", "Published", etc. And there are obviously transitions between these states driven by
events (such as clicking a "publish" button).
But is it worth creating a state machine for this? Generally, I would say not.
Yes, there are state transitions, but is there really any change in logic because of this? From
the authoring point of view, most blogging apps don't have any restrictions based on the
state. You can author a draft post in exactly the same way as you author a published post.
The only part of the system that does care about the state is the display engine, and that
filters out the drafts in the database layer before it ever gets to the domain.
Since there is no special domain logic that cares about the state, it is probably unnecessary.
State transitions occur outside the application
In a customer management application, it is common to classify customers as "prospects",
"active", "inactive", etc.
In the application, these states have business meaning and should be represented by the
type system (such as a union type). But the state transitions generally do not occur within
the application itself. For example, we might classify a customer as inactive if they haven't
ordered anything for 6 months. And then this rule might be applied to customer records in a
database by a nightly batch job, or when the customer record is loaded from the database.
But from our application's point of view, the transitions do not happen within the application,
and so we do not need to create a special state machine.
Dynamic business rules
The last bullet point in the list above refers to "static" business rules. By this I mean that the
rules change slowly enough that they should be embedded into the code itself.
On the other hand, if the rules are dynamic and change frequently, it is probably not worth
going to the trouble of creating static types.
In these situations, you should consider using active patterns, or even a proper rules engine.
911
Summary
In this post, we've seen that if you have data structures with explicit flags ("IsVerified") or
status fields ("OrderStatus"), or implicit state (clued by an excessive number of nullable or
option types), it is worth considering using a simple state machine to model the domain
objects. In most cases the extra complexity is compensated for by the explicit documention
of the states and the elimination of errors due to not handling all possible cases.
912
Constrained strings
The type says that the first name is a string . But really, is that all it is? Are there any other
constraints that we might need to add to it?
Well, OK, it must not be null. But that is assumed in F#.
What about the length of the string? Is it acceptable to have a name which is 64K characters
long? If not, then is there some maximum length allowed?
And can a name contain linefeed characters or tabs? Can it start or end with whitespace?
Once you put it this way, there are quite a lot of constraints even for a "generic" string. Here
are some of the obvious ones:
What is its maximum length?
Can it cross over multiple lines?
Can it have leading or trailing whitespace?
Can it contain non-printing characters?
Constrained strings
So we might acknowledge that some constraints exist, but should they really be part of the
domain model (and the corresponding types derived from it)? For example, the constraint
that a last name is limited to 100 characters -- surely that is specific to a particular
implementation and not part of the domain at all.
I would answer that there is a difference between a logical model and a physical model. In a
logical model some of these constraints might not be relevant, but in a physical model they
most certainly are. And when we are writing code, we are always dealing with a physical
model anyway.
Another reason for incorporating the constraints into the model is that often the model is
shared across many separate applications. For example, a personal name may be created
in a e-commerce application, which writes it into a database table and then puts it on a
message queue to be picked up by a CRM application, which in turn calls an email
templating service, and so on.
It is important that all these applications and services have the same idea of what a personal
name is, including the length and other constraints. If the model does not make the
constraints explicit, then it is easy to have a mismatch when moving across service
boundaries.
For example, have you ever written code that checks the length of a string before writing it to
a database?
void SaveToDatabase(PersonalName personalName)
{
var first = personalName.First;
if (first.Length > 50)
{
// ensure string is not too long
first = first.Substring(0,50);
}
//save to database
}
If the string is too long at this point, what should you do? Silently truncate it? Throw an
exception?
A better answer is to avoid the problem altogether if you can. By the time the string gets to
the database layer it is too late -- the database layer should not be making these kinds of
decisions.
The problem should be dealt with when the string was first created, not when it is used. In
other words, it should have been part of the validation of the string.
914
Constrained strings
But how can we trust that the validation has been done correctly for all possible paths? I
think you can guess the answer...
Note that we immediately have to deal with the case when the validation fails by using an
option type as the result. It makes creation more painful, but we can't avoid it if we want the
benefits later.
For example, here is a good string and a bad string of length 2.
915
Constrained strings
In order to use the String2 value we are forced to check whether it is Some or None at the
time of creation.
This kind of thing will make working with dictionaries and lists harder.
Refactoring
At this point we can exploit F#'s support for interfaces, and create a common interface that
all wrapped strings have to support, and also some standard functions:
916
Constrained strings
module WrappedString =
/// An interface that all wrapped strings support
type IWrappedString =
abstract Value : string
/// Create a wrapped value option
/// 1) canonicalize the input first
/// 2) If the validation succeeds, return Some of the given constructor
/// 3) If the validation fails, return None
/// Null values are never valid.
let create canonicalize isValid ctor (s:string) =
if s = null
then None
else
let s' = canonicalize s
if isValid s'
then Some (ctor s')
else None
/// Apply the given function to the wrapped value
let apply f (s:IWrappedString) =
s.Value |> f
/// Get the wrapped value
let value s = apply id s
/// Equality test
let equals left right =
(value left) = (value right)
/// Comparison
let compareTo left right =
(value left).CompareTo (value right)
The key function is create , which takes a constructor function and creates new values
using it only when the validation passes.
With this in place it is a lot easier to define new types:
917
Constrained strings
module WrappedString =
// ... code from above ...
/// Canonicalizes a string before construction
/// * converts all whitespace to a space char
/// * trims both ends
let singleLineTrimmed s =
System.Text.RegularExpressions.Regex.Replace(s,"\s"," ").Trim()
/// A validation function based on length
let lengthValidator len (s:string) =
s.Length <= len
/// A string of length 100
type String100 = String100 of string with
interface IWrappedString with
member this.Value = let (String100 s) = this in s
/// A constructor for strings of length 100
let string100 = create singleLineTrimmed (lengthValidator 100) String100
/// Converts a wrapped string to a string of length 100
let convertTo100 s = apply string100 s
/// A string of length 50
type String50 = String50 of string with
interface IWrappedString with
member this.Value = let (String50 s) = this in s
/// A constructor for strings of length 50
let string50 = create singleLineTrimmed (lengthValidator 50) String50
/// Converts a wrapped string to a string of length 50
let convertTo50 s = apply string50 s
918
Constrained strings
member this.Value =
let (String100 s) = this
s
If you want to have other types with different constraints, you can easily add them. For
example you might want to have a Text1000 type that supports multiple lines and
embedded tabs and is not trimmed.
module WrappedString =
// ... code from above ...
/// A multiline text of length 1000
type Text1000 = Text1000 of string with
interface IWrappedString with
member this.Value = let (Text1000 s) = this in s
/// A constructor for multiline strings of length 1000
let text1000 = create id (lengthValidator 1000) Text1000
919
Constrained strings
When we need to interact with types such as maps that use raw strings, it is easy to
compose new helper functions.
For example, here are some helpers to work with maps:
module WrappedString =
// ... code from above ...
/// map helpers
let mapAdd k v map =
Map.add (value k) v map
let mapContainsKey k map =
Map.containsKey (value k) map
let mapTryFind k map =
Map.tryFind (value k) map
920
Constrained strings
So overall, this "WrappedString" module allows us to create nicely typed strings without
interfering too much. Now let's use it in a real situation.
We have created a module for the type and added a creation function that converts a pair of
strings into a PersonalName .
Note that we have to decide what to do if either of the input strings are invalid. Again, we
cannot postpone the issue till later, we have to deal with it at construction time.
In this case we use the simple approach of creating an option type with None to indicate
failure.
Here it is in use:
let name = PersonalName.create "John" "Smith"
921
Constrained strings
Should we return a raw string or a wrapped string? The advantage of the latter is that
the callers know exactly how long the string will be, and it will be compatible with other
similar types.
If we do return a wrapped string (say a String100 ), then how do we handle the the
case when the combined length is too long? (It could be up to 151 chars, based on the
length of the first and last name types.). We could either return an option, or force a
truncation if the combined length is too long.
Here's code that demonstrates all three options.
module PersonalName =
// ... code from above ...
/// concat the first and last names together
/// and return a raw string
let fullNameRaw personalName =
let f = personalName.FirstName |> value
let l = personalName.LastName |> value
f + " " + l
/// concat the first and last names together
/// and return None if too long
let fullNameOption personalName =
personalName |> fullNameRaw |> string100
/// concat the first and last names together
/// and truncate if too long
let fullNameTruncated personalName =
// helper function
let left n (s:string) =
if (s.Length > n)
then s.Substring(0,n)
else s
personalName
|> fullNameRaw // concat
|> left 100 // truncate
|> string100 // wrap
|> Option.get // this will always be ok
922
Constrained strings
923
Constrained strings
For example, let's say that you want to output a string to HTML. Should the string be
escaped or not?
If it is already escaped, you want to leave it alone but if it is not, you do want to escape it.
This can be a tricky problem. Joel Spolsky discusses using a naming convention here, but of
course, in F#, we want a type-based solution instead.
A type-based solution will probably use a type for "safe" (already escaped) HTML strings
( HtmlString say), and one for safe Javascript strings ( JsString ), one for safe SQL strings
( SqlString ), etc. Then these strings can be mixed and matched safely without accidentally
causing security issues.
I won't create a solution here (and you will probably be using something like Razor anyway),
but if you are interested you can read about a Haskell approach here and a port of that to
F#.
Update
Many people have asked for more information on how to ensure that constrained types such
as EmailAddress are only created through a special constructor that does the validation. So
I have created a gist here that has some detailed examples of other ways of doing it.
924
Non-string types
compared at all.
Types to the rescue, of course.
type CustomerId = CustomerId of int
type OrderId = OrderId of int
let custId = CustomerId 42
let orderId = OrderId 42
// compiler error
printfn "cust is equal to order? %b" (custId = orderId)
Similarly, you might want avoid mixing up semantically different date values by wrapping
them in a type. ( DateTimeKind is an attempt at this, but not always reliable.)
type LocalDttm = LocalDttm of System.DateTime
type UtcDttm = UtcDttm of System.DateTime
With these types we can ensure that we always pass the right kind of datetime as
parameters. Plus, it acts as documentation as well.
925
Non-string types
Constraints on integers
Just as we had validation and constraints on types such as String50 and ZipCode , we can
use the same approach when we need to have constraints on integers.
For example, an inventory management system or a shopping cart may require that certain
types of number are always positive. You might ensure this by creating a NonNegativeInt
type.
module NonNegativeInt =
type T = NonNegativeInt of int
let create i =
if (i >= 0 )
then Some (NonNegativeInt i)
else None
module InventoryManager =
// example of NonNegativeInt in use
let SetStockQuantity (i:NonNegativeInt.T) =
//set stock
()
Is it worth trying to avoid this issue by using constrained types? Let's look at some real code.
Here is a very simple shopping cart manager using a standard int type for the quantity.
The quantity is incremented or decremented when the related buttons are clicked. Can you
find the obvious bug?
926
Non-string types
module ShoppingCartWithBug =
let mutable itemQty = 1 // don't do this at home!
let incrementClicked() =
itemQty <- itemQty + 1
let decrementClicked() =
itemQty <- itemQty - 1
If you can't quickly find the bug, perhaps you should consider making any constraints more
explicit.
Here is the same simple shopping cart manager using a typed quantity instead. Can you find
the bug now? (Tip: paste the code into a F# script file and run it)
module ShoppingCartQty =
type T = ShoppingCartQty of int
let initialValue = ShoppingCartQty 1
let create i =
if (i > 0 && i < 100)
then Some (ShoppingCartQty i)
else None
let increment t = create (t + 1)
let decrement t = create (t - 1)
module ShoppingCartWithTypedQty =
let mutable itemQty = ShoppingCartQty.initialValue
let incrementClicked() =
itemQty <- ShoppingCartQty.increment itemQty
let decrementClicked() =
itemQty <- ShoppingCartQty.decrement itemQty
You might think this is overkill for such a trivial problem. But if you want to avoid being in the
DailyWTF, it might be worth considering.
Constraints on dates
927
Non-string types
Not all systems can handle all possible dates. Some systems can only store dates going
back to 1/1/1980, and some systems can only go into the future up to 2038 (I like to use
1/1/2038 as a max date to avoid US/UK issues with month/day order).
As with integers, it might be useful to have constraints on the valid dates built into the type,
so that any out of bound issues are dealt with at construction time rather than later on.
type SafeDate = SafeDate of System.DateTime
let create dttm =
let min = new System.DateTime(1980,1,1)
let max = new System.DateTime(2038,1,1)
if dttm < min || dttm > max
then None
else Some (SafeDate dttm)
928
Non-string types
929
And how does that compare to the final result after applying all the techniques above?
930
931
932
933
City: WrappedString.String50;
State: StateCode.T;
Zip: ZipCode.T;
}
type UKPostalAddress =
{
Address1: WrappedString.String50;
Address2: WrappedString.String50;
Town: WrappedString.String50;
PostCode: WrappedString.String50; // todo
}
type GenericPostalAddress =
{
Address1: WrappedString.String50;
Address2: WrappedString.String50;
Address3: WrappedString.String50;
Address4: WrappedString.String50;
Address5: WrappedString.String50;
}
type T =
| USPostalAddress of USPostalAddress
| UKPostalAddress of UKPostalAddress
| GenericPostalAddress of GenericPostalAddress
// ========================================
// PersonalName (not application specific)
// ========================================
module PersonalName =
open WrappedString
type T =
{
FirstName: String50;
MiddleName: String50 option;
LastName: String100;
}
/// create a new value
let create first middle last =
match (string50 first),(string100 last) with
| Some f, Some l ->
Some {
FirstName = f;
MiddleName = (string50 middle)
LastName = l;
}
| _ ->
None
934
// ========================================
// EmailContactInfo -- state machine
// ========================================
module EmailContactInfo =
open System
// UnverifiedData = just the EmailAddress
type UnverifiedData = EmailAddress.T
// VerifiedData = EmailAddress plus the time it was verified
type VerifiedData = EmailAddress.T * DateTime
// set of states
type T =
| UnverifiedState of UnverifiedData
| VerifiedState of VerifiedData
935
936
InvalidState address
// handle the "validated" event
let validated postalContactInfo dateValidated =
match postalContactInfo with
| InvalidState address ->
// construct a new info in the valid state
ValidState (address, dateValidated)
| ValidState _ ->
// ignore
postalContactInfo
let contactValidationService postalContactInfo =
let dateIsTooLongAgo (d:DateTime) =
d < DateTime.Today.AddYears(-1)
match postalContactInfo with
| InvalidState address ->
printfn "contacting the address validation service"
| ValidState (address,date) when date |> dateIsTooLongAgo ->
printfn "last checked a long time ago."
printfn "contacting the address validation service again"
| ValidState _ ->
printfn "recently checked. Doing nothing."
// ========================================
// ContactMethod and Contact
// ========================================
type ContactMethod =
| Email of EmailContactInfo.T
| PostalAddress of PostalContactInfo.T
type Contact =
{
Name: PersonalName.T;
PrimaryContactMethod: ContactMethod;
SecondaryContactMethods: ContactMethod list;
}
Conclusion
Phew! The new code is much, much longer than the original code. Granted, it has a lot of
supporting functions that were not needed in the original version, but even so it seems like a
lot of extra work. So was it worth it?
I think the answer is yes. Here are some of the reasons why:
The new code is more explicit
937
If we look at the original example, there was no atomicity between fields, no validation rules,
no length constraints, nothing to stop you updating flags in the wrong order, and so on.
The data structure was "dumb" and all the business rules were implicit in the application
code. Chances are that the application would have lots of subtle bugs that might not even
show up in unit tests. (Are you sure the application reset the IsEmailVerified flag to false in
every place the email address was updated?)
On the other hand, the new code is extremely explicit about every little detail. If I stripped
away everything but the types themselves, you would have a very good idea of what the
business rules and domain constraints were.
The new code won't let you postpone error handling
Writing code that works with the new types means that you are forced to handle every
possible thing that could go wrong, from dealing with a name that is too long, to failing to
supply a contact method. And you have to do this up front at construction time. You can't
postpone it till later.
Writing such error handling code can be annoying and tedious, but on the other hand, it
pretty much writes itself. There is really only one way to write code that actually compiles
with these types.
The new code is more likely to be correct
The huge benefit of the new code is that it is probably bug free. Without even writing any unit
tests, I can be quite confident that a first name will never be truncated when written to a
varchar(50) in a database, and that I can never accidentally send out a verification email
twice.
And in terms of the code itself, many of the things that you as a developer have to remember
to deal with (or forget to deal with) are completely absent. No null checks, no casting, no
worrying about what the default should be in a switch statement. And if you like to use
cyclomatic complexity as a code quality metric, you might note that there are only three if
statements in the entire 350 odd lines.
A word of warning...
Finally, beware! Getting comfortable with this style of type-based design will have an
insidious effect on you. You will start to develop paranoia whenever you see code that isn't
typed strictly enough. (How long should an email address be, exactly?) and you will be
unable to write the simplest python script without getting anxious. When this happens, you
will have been fully inducted into the cult. Welcome!
938
If you liked this series, here is a slide deck that covers many of the same topics. There is a
video as well (here)
Domain Driven Design with the F# type System -- F#unctional Londoners 2014 from
my slideshare page
939
Getting started
I'm going to define the "size" of a type by thinking of it as a set, and counting the number of
possible elements.
For example, there are two possible booleans, so the size of the Boolean type is two.
Is there a type with size one? Yes -- the unit type only has one value: () .
Is there a type with size zero? That is, is there a type that has no values at all? Not in F#, but
in Haskell there is. It is called Void .
What about a type like this:
type ThreeState =
| Checked
| Unchecked
| Unknown
What is its size? There are three possible values, so the size is three.
What about a type like this:
type Direction =
| North
| East
| South
| West
Obviously, four.
I think you get the idea!
Let's look at calculating the sizes of compound types now. If you remember from the
understanding F# types series, there are two kinds of algebraic types: "product" types such
as tuples and records, and "sum" types, called discriminated unions in F#.
For example, let's say that we have a Speed as well as a Direction , and we combine them
into a record type called Velocity :
type Speed =
| Slow
| Fast
type Velocity = {
direction: Direction
speed: Speed
}
There are eight possible values, one for every possible combination of the two Speed
values and the four Direction values.
We can generalize this into a rule:
RULE: The size of a product type is the product of the sizes of the component
types.
That is, given a record type like this:
type RecordType = {
a : TypeA
b : TypeB }
941
Sum types
Sum types can be analyzed the same way. Given a type Movement defined like this:
type Movement =
| Moving of Direction
| NotMoving
So, five in all. Which just happens to be size(Direction) + 1 . Here's another fun one:
type ThingsYouCanSay =
| Yes
| Stop
| Goodbye
type ThingsICanSay =
| No
| GoGoGo
| Hello
type HelloGoodbye =
| YouSay of ThingsYouCanSay
| ISay of ThingsICanSay
942
YouSay Yes
ISay No
YouSay Stop
ISay GoGoGo
YouSay Goodbye
ISay Hello
There are three possible values in the YouSay case, and three possible values in the ISay
case, making six in all.
Again, we can make a general rule.
RULE: The size of a sum or union type is the sum of the sizes of the component
types.
That is, given a union type like this:
type SumType =
| CaseA of TypeA
| CaseB of TypeB
Well, the first thing to say is that Optional<'a> is not a type but a type constructor.
Optional<string> is a type. Optional<int> is a type, but Optional<'a> isn't.
Nevertheless, we can still calculate its size by noting that size(Optional<string>) is just
size(string) + 1 , size(Optional<int>) is just size(int) + 1 , and so on.
So we can say:
943
size(Optional<'a>) = size('a) + 1
we can say that its size can be calculated using the size of the generic components (using
the "sum rule" above):
size(Either<'a,'b>) = size('a) + size('b)
Recursive types
What about a recursive type? Let's look at the simplest one, a linked list.
A linked list is either empty, or it has a cell with a tuple: a head and a tail. The head is an 'a
and the tail is another list. Here's the definition:
type LinkedList<'a> =
| Empty
| Node of head:'a * tail:LinkedList<'a>
To calculate the size, let's assign some names to the various components:
let S = size(LinkedList<'a>)
let N = size('a)
944
You can see where this is going! The formula for S can be expanded out indefinitely to be:
S = 1 + N + N^2 + N^3 + N^4 + N^5 + ...
How can we interpret this? Well, we can say that a list is a union of the following cases:
an empty list(size = 1)
a one element list (size = N)
a two element list (size = N x N)
a three element list (size = N x N x N)
and so on.
And this formula has captured that.
As an aside, you can calculate S directly using the formula S = 1/(1-N) , which means
that a list of Direction (size=4) has size "-1/3". Hmmm, that's strange! It reminds me of this
"-1/12" video.
945
For example, say that we have a function SuitColor that maps a card Suit to a Color ,
red or black.
type Suit = Heart | Spade | Diamond | Club
type Color = Red | Black
type SuitColor = Suit -> Color
One implementation would be to return red, no matter what suit was provided:
(Heart -> Red); (Spade -> Red); (Diamond -> Red); (Club -> Red)
Another implementation would be to return red for all suits except Club :
(Heart -> Red); (Spade -> Red); (Diamond -> Red); (Club -> Black)
Another way to think of it is that we can define a record type where each value represents a
particular implementation: which color do we return for a Heart input, which color do we
return for a Spade input, and so on.
The type definition for the implementations of SuitColor would therefore look like this:
946
type SuitColorImplementation = {
Heart : Color
Spade : Color
Diamond : Color
Club : Color }
There are four size(Color) here. In other words, there is one size(Color) for every input,
so we could write this as:
size(SuitColorImplementation) = size(Color) to the power of size(Suit)
The size of the function is size(output type) to the power of size(input type) :
size(Function) = size(output) ^ size(input)
947
type YesNoUnion =
| Yes
| No
type YesNoRecord = {
isYes: bool }
This is what you might call a "lossless" conversion. If you round-trip the conversion, you can
recover the original value. Mathematicians would call this an isomorphism (from the Greek
"equal shape").
What about another example? Here's a type with three cases, yes, no, and maybe.
type YesNoMaybe =
| Yes
| No
| Maybe
Well, what is the size of an option ? One plus the size of the inner type, which in this case
is a bool . So size(YesNoOption) is also three.
Here are the conversion functions:
948
Lossy conversions
What happens if the types are different sizes?
If the target type is "larger" than the source type, then you can always map without loss, but
if the target type is "smaller" than the source type, you have a problem.
For example, the int type is smaller than the string type. You can convert an int to a
string accurately, but you can't convert a string to an int easily.
949
If you do want to map a string to an int, then some of the non-integer strings will have to be
mapped to a special, non-integer value in the target type:
In other words we know from the sizes that the target type can't just be an int type, it must
be an int + 1 type. In other words, an Option type!
Interestingly, the Int32.TryParse function in the BCL returns two values, a success/failure
bool and the parsed result as an int . In other words, a tuple bool * int .
The size of that tuple is 2 x int , many more values that are really needed. Option types
ftw!
Now let's say we are converting from a string to a Direction . Some strings are valid, but
most of them are not. But this time, instead of having one invalid case, let's also say that we
want to distinguish between empty inputs, inputs that are too long, and other invalid inputs.
We can't model the target with an Option any more, so let's design a custom type that
contains all seven cases:
950
type StringToDirection_V1 =
| North
| East
| South
| West
| Empty
| NotValid
| TooLong
But this design mixes up successful conversions and failed conversions. Why not separate
them?
type Direction =
| North
| East
| South
| West
type ConversionFailure =
| Empty
| NotValid
| TooLong
type StringToDirection_V2 =
| Success of Direction
| Failure of ConversionFailure
version.
In other words, both of these designs are equivalent and we can use either one.
Personally, I prefer version 2, but if we had version 1 in our legacy code, the good news is
that we can losslessly convert from version 1 to version 2 and back again. Which in turn
means that we can safely refactor to version 2 if we need to.
951
type Something_V1 =
| CaseA1 of TypeX * TypeY
| CaseA2 of TypeX * TypeZ
or this one:
type Something_V3 = {
x: TypeX
inner: Inner }
or alternatively, we can pull the common SessionId up to a higher level like this:
952
module Customer_V2 =
type UserInfo = {name:string} //etc
type SessionId = SessionId of int
type WebsiteUserInfo =
| RegisteredUser of UserInfo
| GuestUser
type WebsiteUser = {
sessionId : SessionId
info: WebsiteUserInfo }
Which is better? In one sense, they are both the "same", but obviously the best design
depends on the usage pattern.
If you care more about the type of user than the session id, then version 1 is better.
If you are constantly looking at the session id without caring about the type of user, then
version 2 is better.
The nice thing about knowing that they are isomorphic is that you can define both types if
you like, use them in different contexts, and losslessly map between them as needed.
953
The values are constrained: max 50 chars for the name, a validated email, an age which is
between 1 and 129.
On the other hand, the DTO type might look like this:
type CustomerDTO = {
Name: string
Email: string
Age: int }
The values are unconstrained: any string for the name, a unvalidated email, an age that can
be any of 2^32 different values, including negative ones.
This means that we cannot create a CustomerDTO to DomainCustomer mapping. We have to
have at least one other value ( DomainCustomer + 1 ) to map the invalid inputs onto, and
preferably more to document the various errors.
This leads naturally to the Success/Failure model as described in my functional error
handling talk,
The final version of the mapping would then be from a CustomerDTO to a
SuccessFailure<DomainCustomer> or similar.
Further reading
The "algebra" of algebraic data types is well known. There is a good recent summary in "The
algebra (and calculus!) of algebraic data types" and a series by Chris Taylor.
And after I wrote this, I was pointed to two similar posts:
One by Tomas Petricek with almost the same content!
954
Summary
This might not be the most exciting topic in the world, but I've found this approach both
interesting and useful, and I wanted to share it with you.
Let me know what you think. Thanks for reading!
955
956
Way 12. Monadic control flow, in which we make decisions in the turtle workflow based
on results from earlier commands.
Way 13. A turtle interpreter, in which we completely decouple turtle programming from
turtle implementation, and nearly encounter the free monad.
Review of all the techniques used.
and 2 bonus ways for the extended edition:
Way 14. Abstract Data Turtle, in which we encapsulate the details of a turtle
implementation by using an Abstract Data Type.
Way 15. Capability-based Turtle, in which we control what turtle functions are available
to a client, based on the current state of the turtle.
All source code for this post is available on github.
All of the following implementations will be based on this interface or some variant of it.
Note that the turtle must convert these instructions to drawing lines on a canvas or other
graphics context. So the implementation will probably need to keep track of the turtle
position and current state somehow.
Common code
957
Before we start implementing, let's get some common code out of the way.
First, we'll need some types to represent distances, angles, the pen state, and the pen
colors.
/// An alias for a float
type Distance = float
/// Use a unit of measure to make it clear that the angle is in degrees, not radians
type [<Measure>] Degrees
/// An alias for a float of Degrees
type Angle = float<Degrees>
/// Enumeration of available pen states
type PenState = Up | Down
/// Enumeration of available pen colors
type PenColor = Black | Red | Blue
and we'll also need a type to represent the position of the turtle:
/// A structure to store the (x,y) coordinates
type Position = {x:float; y:float}
We'll also need a helper function to calculate a new position based on moving a certain
distance at a certain angle:
// round a float to two places to make it easier to read
let round2 (flt:float) = Math.Round(flt,2)
/// calculate a new position from the current position given an angle and a distance
let calcNewPosition (distance:Distance) (angle:Angle) currentPos =
// Convert degrees to radians with 180.0 degrees = 1 pi radian
let angleInRads = angle * (Math.PI/180.0) * 1.0<1/Degrees>
// current pos
let x0 = currentPos.x
let y0 = currentPos.y
// new pos
let x1 = x0 + (distance * cos angleInRads)
let y1 = y0 + (distance * sin angleInRads)
// return a new Position
{x=round2 x1; y=round2 y1}
958
959
type Turtle(log) =
let mutable currentPosition = initialPosition
let mutable currentAngle = 0.0<Degrees>
let mutable currentColor = initialColor
let mutable currentPenState = initialPenState
member this.Move(distance) =
log (sprintf "Move %0.1f" distance)
// calculate new position
let newPosition = calcNewPosition distance currentAngle currentPosition
// draw line if needed
if currentPenState = Down then
dummyDrawLine log currentPosition newPosition currentColor
// update the state
currentPosition <- newPosition
member this.Turn(angle) =
log (sprintf "Turn %0.1f" angle)
// calculate new angle
let newAngle = (currentAngle + angle) % 360.0<Degrees>
// update the state
currentAngle <- newAngle
member this.PenUp() =
log "Pen up"
currentPenState <- Up
member this.PenDown() =
log "Pen down"
currentPenState <- Down
member this.SetColor(color) =
log (sprintf "SetColor %A" color)
currentColor <- color
960
Note that drawOneSide() does not return anything -- all the code is imperative and stateful.
Compare this to the code in the next example, which takes a pure functional approach.
961
Here's the definition of TurtleState and the values for the initial state:
962
module Turtle =
type TurtleState = {
position : Position
angle : float<Degrees>
color : PenColor
penState : PenState
}
let initialTurtleState = {
position = initialPosition
angle = 0.0<Degrees>
color = initialColor
penState = initialPenState
}
And here are the "api" functions, all of which take a state parameter and return a new state:
module Turtle =
// [state type snipped]
let move log distance state =
log (sprintf "Move %0.1f" distance)
// calculate new position
let newPosition = calcNewPosition distance state.angle state.position
// draw line if needed
if state.penState = Down then
dummyDrawLine log state.position newPosition state.color
// update the state
{state with position = newPosition}
let turn log angle state =
log (sprintf "Turn %0.1f" angle)
// calculate new angle
let newAngle = (state.angle + angle) % 360.0<Degrees>
// update the state
{state with angle = newAngle}
let penUp log state =
log "Pen up"
{state with penState = Up}
let penDown log state =
log "Pen down"
{state with penState = Down}
let setColor log color state =
log (sprintf "SetColor %A" color)
{state with color = color}
963
Note that the state is always the last parameter -- this makes it easier to use the "piping"
idiom.
With these simpler versions, the client can just pipe the state through in a natural way:
let drawTriangle() =
Turtle.initialTurtleState
|> move 100.0
|> turn 120.0<Degrees>
|> move 100.0
|> turn 120.0<Degrees>
|> move 100.0
|> turn 120.0<Degrees>
// back home at (0,0) with angle 0
When it comes to drawing a polygon, it's a little more complicated, as we have to "fold" the
state through the repetitions for each side:
964
let drawPolygon n =
let angle = 180.0 - (360.0/float n)
let angleDegrees = angle * 1.0<Degrees>
// define a function that draws one side
let oneSide state sideNumber =
state
|> move 100.0
|> turn angleDegrees
// repeat for all sides
[1..n]
|> List.fold oneSide Turtle.initialTurtleState
965
If the command is not valid, the API must indicate that to the client. Since we are using an
OO approach, we'll do this by throwing a TurtleApiException containing a string, like this.
exception TurtleApiException of string
966
type TurtleApi() =
let turtle = Turtle(log)
member this.Exec (commandStr:string) =
let tokens = commandStr.Split(' ') |> List.ofArray |> List.map trimString
match tokens with
| [ "Move"; distanceStr ] ->
let distance = validateDistance distanceStr
turtle.Move distance
| [ "Turn"; angleStr ] ->
let angle = validateAngle angleStr
turtle.Turn angle
| [ "Pen"; "Up" ] ->
turtle.PenUp()
| [ "Pen"; "Down" ] ->
turtle.PenDown()
| [ "SetColor"; colorStr ] ->
let color = validateColor colorStr
turtle.SetColor color
| _ ->
let msg = sprintf "Instruction '%s' is not recognized" commandStr
raise (TurtleApiException msg)
You can see that the code is quite similar to the earlier OO version, with the direct call
turtle.Move 100.0 being replaced with the indirect API call api.Exec "Move 100.0" .
Now if we trigger an error with a bad command such as api.Exec "Move bad" , like this:
967
let triggerError() =
let api = TurtleApi()
api.Exec "Move bad"
968
Let's start by implementing the API class. This time it contains a mutable turtle state:
type TurtleApi() =
let mutable state = initialTurtleState
/// Update the mutable state value
let updateState newState =
state <- newState
The validation functions no longer throw an exception, but return Success or Failure :
let validateDistance distanceStr =
try
Success (float distanceStr)
with
| ex ->
Failure (InvalidDistance distanceStr)
Now because the validation functions now return a Result<Distance> rather than a "raw"
distance, the move function needs to be lifted to the world of Results , as does the current
state.
There are three functions that we will use when working with Result s: returnR , mapR and
lift2R .
returnR transforms a "normal" value into a value in the world of Results:
969
As an example, with these helper functions, we can turn the normal move function into a
function in the world of Results:
The distance parameter is already in Result world
The state parameter is lifted into Result world using returnR
The move function is lifted into Result world using lift2R
970
(For more details on lifting functions to Result world, see the post on "lifting" in general )
Here's the complete code for Exec :
971
972
let drawTriangle() =
let api = TurtleApi()
result {
do! api.Exec "Move 100"
do! api.Exec "Turn 120"
do! api.Exec "Move 100"
do! api.Exec "Turn 120"
do! api.Exec "Move 100"
do! api.Exec "Turn 120"
}
The source code for the result computation expression is available here.
Similarly, for the drawPolygon code, we can create a helper to draw one side and then call it
n times inside a result expression.
let drawPolygon n =
let angle = 180.0 - (360.0/float n)
let api = TurtleApi()
// define a function that draws one side
let drawOneSide() = result {
do! api.Exec "Move 100.0"
do! api.Exec (sprintf "Turn %f" angle)
}
// repeat for all sides
result {
for i in [1..n] do
do! drawOneSide()
}
The code looks imperative, but is actually purely functional, as the returned Result values
are being handled transparently by the result workflow.
973
The source code for this version is available here (api helper functions) and here (API and
client).
There are no mutables in the API (or anywhere). The TurtleAgent manages state by
storing the current state as a parameter in the recursive message processing loop.
Now because the TurtleAgent has a typed message queue, where all messages are the
same type, we must combine all possible commands into a single discriminated union type
( TurtleCommand ).
type TurtleCommand =
| Move of Distance
| Turn of Angle
| PenUp
| PenDown
| SetColor of PenColor
The agent implementation is similar to the previous ones, but rather than exposing the turtle
functions directly, we now do pattern matching on the incoming command to decide which
function to call:
974
type TurtleAgent() =
/// Function to log a message
let log message =
printfn "%s" message
// logged versions
let move = Turtle.move log
let turn = Turtle.turn log
let penDown = Turtle.penDown log
let penUp = Turtle.penUp log
let setColor = Turtle.setColor log
let mailboxProc = MailboxProcessor.Start(fun inbox ->
let rec loop turtleState = async {
// read a command message from teh queue
let! command = inbox.Receive()
// create a new state from handling the message
let newState =
match command with
| Move distance ->
move distance turtleState
| Turn angle ->
turn angle turtleState
| PenUp ->
penUp turtleState
| PenDown ->
penDown turtleState
| SetColor color ->
setColor color turtleState
return! loop newState
}
loop Turtle.initialTurtleState )
// expose the queue externally
member this.Post(command) =
mailboxProc.Post command
975
we'll use the result computation expression instead, so the code above would have
looked like this:
result {
let! distance = validateDistance distanceStr
move distance state
}
In the agent implementation, we are not calling a move command, but instead creating the
Move case of the Command type, so the code looks like:
result {
let! distance = validateDistance distanceStr
let command = Move distance
turtleAgent.Post command
}
976
977
Disadvantages
Agents are stateful and have the same problem as stateful objects:
It is harder to reason about your code.
Testing is harder.
It is all too easy to create a web of complex dependencies between actors.
A robust implementation for agents can get quite complex, as you may need support for
supervisors, heartbeats, back pressure, etc.
The source code for this version is available here .
Note that there are a lot of unit s in these functions. A unit in a function signature implies
side effects, and indeed the TurtleState is not used anywhere, as this is a OO-based
approach where the mutable state is encapsulated in the object.
978
Next, we need to change the API layer to use the interface by injecting it in the constructor
for TurtleApi . Other than that, the rest of the API code is unchanged, as shown by the
snippet below:
type TurtleApi(turtle: ITurtle) =
// other code
member this.Exec (commandStr:string) =
let tokens = commandStr.Split(' ') |> List.ofArray |> List.map trimString
match tokens with
| [ "Move"; distanceStr ] ->
let distance = validateDistance distanceStr
turtle.Move distance
| [ "Turn"; angleStr ] ->
let angle = validateAngle angleStr
turtle.Turn angle
// etc
"proxy" wrapper around the orginal Turtle class, where the proxy implements the new
interface.
In some languages, creating proxy wrappers can be long-winded, but in F# you can use
object expressions to implement an interface quickly:
let normalSize() =
let log = printfn "%s"
let turtle = Turtle(log)
// return an interface wrapped around the Turtle
{new ITurtle with
member this.Move dist = turtle.Move dist
member this.Turn angle = turtle.Turn angle
member this.PenUp() = turtle.PenUp()
member this.PenDown() = turtle.PenDown()
member this.SetColor color = turtle.SetColor color
}
979
And to create the halfSize version, we do the same thing, but intercept the calls to Move
and halve the distance parameter:
let halfSize() =
let normalSize = normalSize()
// return a decorated interface
{new ITurtle with
member this.Move dist = normalSize.Move (dist/2.0) // halved!!
member this.Turn angle = normalSize.Turn angle
member this.PenUp() = normalSize.PenUp()
member this.PenDown() = normalSize.PenDown()
member this.SetColor color = normalSize.SetColor color
}
This is actually the "decorator" pattern at work: we're wrapping normalSize in a proxy with
an identical interface, then changing the behavior for some of the methods, while passing
others though untouched.
And now let's try drawing the triangle by instantiating the API object with the normal
interface:
let iTurtle = normalSize() // an ITurtle type
let api = TurtleApi(iTurtle)
drawTriangle(api)
Obviously, in a real system, the dependency injection would occur away from the call site,
using an IoC container or similar.
If we run it, the output of drawTriangle is just as before:
980
Move 100.0
...Draw line from (0.0,0.0) to (100.0,0.0) using Black
Turn 120.0
Move 100.0
...Draw line from (100.0,0.0) to (50.0,86.6) using Black
Turn 120.0
Move 100.0
...Draw line from (50.0,86.6) to (0.0,0.0) using Black
Turn 120.0
981
type TurtleFunctions = {
move : Distance -> TurtleState -> TurtleState
turn : Angle -> TurtleState -> TurtleState
penUp : TurtleState -> TurtleState
penDown : TurtleState -> TurtleState
setColor : PenColor -> TurtleState -> TurtleState
}
Note that there are no unit s in these function signatures, unlike the OO version. Instead,
the TurtleState is explicitly passed in and returned.
Also note that there is no logging either. The logging method will be baked in to the functions
when the record is created.
The TurtleApi constructor now takes a TurtleFunctions record rather than an ITurtle ,
but as these functions are pure, the API needs to manage the state again with a mutable
field.
type TurtleApi(turtleFunctions: TurtleFunctions) =
let mutable state = initialTurtleState
The implementation of the main Exec method is very similar to what we have seen before,
with these differences:
The function is fetched from the record (e.g. turtleFunctions.move ).
All the activity takes place in a result computation expression so that the result of the
validations can be used.
Here's the code:
982
And to create the halfSize version, we clone the record, and change just the move
function:
let halfSize() =
let normalSize = normalSize()
// return a reduced turtle
{ normalSize with
move = fun dist -> normalSize.move (dist/2.0)
}
983
What's nice about cloning records rather than proxying interfaces is that we don't have to
reimplement every function in the record, just the ones we care about.
As you can see, the client code in the ITurtle version and TurtleFunctions version looks
identical! If it wasn't for the different types, you could not tell them apart.
984
To stress that point again: in this approach dependencies are always passed "just in time", to
the function that needs them. No dependencies are used in the constructor and then used
later.
Here's a bigger snippet of the Exec method using those functions:
985
986
So now when we want to draw something, we need only pass in any function of type string
-> Result<unit,ErrorMessage> . The TurtleApi is no longer needed or mentioned!
So, although we did have mutable state in the TurtleApi , the final "published" api is a
function that hides that fact.
This approach of having the api be a single function makes it very easy to mock for testing!
let mockApi s =
printfn "[MockAPI] %s" s
Success ()
drawTriangle(mockApi)
The trick is to pass in just one function! But how can one function handle five different
actions? Easy - by using a discriminated union to represent the possible commands.
We've seen this done before in the agent example, so let's revisit that type again:
type TurtleCommand =
| Move of Distance
| Turn of Angle
| PenUp
| PenDown
| SetColor of PenColor
All we need now is a function that handles each case of that type.
Befor we do that though, let's look at the changes to the Exec method implementation:
member this.Exec turtleFn (commandStr:string) =
...
// return Success of unit, or Failure
match tokens with
| [ "Move"; distanceStr ] -> result {
let! distance = validateDistance distanceStr
let command = Move distance // create a Command object
let newState = turtleFn command state
updateState newState
}
| [ "Turn"; angleStr ] -> result {
let! angle = validateAngle angleStr
let command = Turn angle // create a Command object
let newState = turtleFn command state
updateState newState
}
...
Note that a command object is being created and then the turtleFn parameter is being
called with it.
And by the way, this code is very similar to the agent implementation, which used
turtleAgent.Post command rather than newState = turtleFn command state :
988
earlier:
let api = normalSize()
drawTriangle(api)
let api = halfSize()
drawTriangle(api)
989
990
(It's true that, so far. we have not had any useable output from the turtle functions, but in a
later example we will see this output being used to make decisions.)
There is a standard way to deal with these kinds of functions -- the "state monad".
Let's look at how this is built.
First, note that, thanks to currying, we can recast a function in this shape into two separate
one-parameter functions: processing the input generates another function that in turn has
the state as the parameter:
We can then think of a turtle function as something that takes an input and returns a new
function, like this:
In our case, using TurtleState as the state, the returned function will look like this:
TurtleState -> 'a * TurtleState
Finally, to make it easier to work with, we can treat the returned function as a thing in its own
right, give it a name such as TurtleStateComputation :
991
In the implementation, we would typically wrap the function with a single case discriminated
union like this:
type TurtleStateComputation<'a> =
TurtleStateComputation of (Turtle.TurtleState -> 'a * Turtle.TurtleState)
So that is the basic idea behind the "state monad". However, it's important to realize that a
state monad consists of more than just this type -- you also need some functions ("return"
and "bind") that obey some sensible laws.
I won't define the returnT and bindT functions here, but you can see their definitions in
the full source.
We need some additional helper functions too. (I'm going to add a T for Turtle suffix to all
the functions).
In particular, we need a way to feed some state into the TurtleStateComputation to "run" it:
let runT turtle state =
// pattern match against the turtle
// to extract the inner function
let (TurtleStateComputation innerFn) = turtle
// run the inner function with the passed in state
innerFn state
Finally, we can create a turtle workflow, which is a computation expression that makes it
easier to work with the TurtleStateComputation type:
// define a computation expression builder
type TurtleBuilder() =
member this.Return(x) = returnT x
member this.Bind(x,f) = bindT f x
// create an instance of the computation expression builder
let turtle = TurtleBuilder()
992
The toUnitComputation helper function does the lifting. Don't worry about how it works, but
the effect is that the original version of the move function ( Distance -> TurtleState ->
TurtleState ) is reborn as a function returning a TurtleStateComputation ( Distance ->
TurtleStateComputation<unit> )
Once we have these "monadic" versions, we can use them inside the turtle workflow like
this:
let drawTriangle() =
// define a set of instructions
let t = turtle {
do! move 100.0
do! turn 120.0<Degrees>
do! move 100.0
do! turn 120.0<Degrees>
do! move 100.0
do! turn 120.0<Degrees>
}
// finally, run them using the initial state as input
runT t initialTurtleState
The first part of drawTriangle chains together six instructions, but importantly, does not run
them. Only when the runT function is used at the end are the instructions actually
executed.
The drawPolygon example is a little more complicated. First we define a workflow for
drawing one side:
993
But then we need a way of combining all the sides into a single workflow. There are a couple
of ways of doing this. I'll go with creating a pairwise combiner chain and then using
reduce to combine all the sides into one operation.
994
And since all the commands are run at once, this approach means that there is no state that
needs to be persisted between calls by the client.
Here's the TurtleCommand definition again:
995
type TurtleCommand =
| Move of Distance
| Turn of Angle
| PenUp
| PenDown
| SetColor of PenColor
To process a sequence of commands, we will need to fold over them, threading the state
through, so we need a function that applies a single command to a state and returns a new
state:
/// Apply a command to the turtle state and return the new state
let applyCommand state command =
match command with
| Move distance ->
move distance state
| Turn angle ->
turn angle state
| PenUp ->
penUp state
| PenDown ->
penDown state
| SetColor color ->
setColor color state
996
let drawTriangle() =
// create the list of commands
let commands = [
Move 100.0
Turn 120.0<Degrees>
Move 100.0
Turn 120.0<Degrees>
Move 100.0
Turn 120.0<Degrees>
]
// run them
run commands
Now, since the commands are just a collection, we can easily build bigger collections from
smaller ones.
Here's an example for drawPolygon , where drawOneSide returns a collection of commands,
and that collection is duplicated for each side:
let drawPolygon n =
let angle = 180.0 - (360.0/float n)
let angleDegrees = angle * 1.0<Degrees>
// define a function that draws one side
let drawOneSide sideNumber = [
Move 100.0
Turn angleDegrees
]
// repeat for all sides
let commands =
[1..n] |> List.collect drawOneSide
// run the commands
run commands
997
Only suitable when control flow is not based on the response from a previous
command. If you do need to respond to the result of each command, consider using the
"interpreter" approach discussed later.
The source code for this version is available here.
998
Data structures are easy to evolve safely. For example, if I added a sixth turtle action, or
removed an action, or changed the parameters of an action, the discriminated union
type would change and all clients of the shared type would fail to compile until the sixth
turtle action is accounted for, etc. On the other hand, if you didn't want existing code to
break, you can use a versioning-friendly data serialization format like protobuf. Neither
of these options are as easy when interfaces are used.
Summary
The meme is spreading.
The turtle must be paddling.
-- "Thirteen ways of looking at a turtle", by Wallace D Coriacea
Hello? Anyone still there? Thanks for making it this far!
So, time for a break! In the next post, we'll cover the remaining four ways of looking at a
turtle.
The source code for this post is available on github.
999
1000
1001
In this way, neither the client nor the command handler needs to track state. Only the
EventStore is mutable.
1002
It is an important part of event sourcing that all events are labeled in the past tense: Moved
and Turned rather than Move and Turn . The event are facts -- they have happened in the
past.
1003
/// Apply an event to the current state and return the new state of the turtle
let applyEvent log oldState event =
match event with
| Moved distance ->
Turtle.move log distance oldState
| Turned angle ->
Turtle.turn log angle oldState
| PenWentUp ->
Turtle.penUp log oldState
| PenWentDown ->
Turtle.penDown log oldState
| ColorChanged color ->
Turtle.setColor log color oldState
The eventsFromCommand function contains the key logic for validating the command and
creating events.
In this particular design, the command is always valid, so at least one event is returned.
The StateChangedEvent is created from the TurtleCommand in a direct one-to-one map
of the cases.
The MovedEvent is only created from the TurtleCommand if the turtle has changed
position.
1004
// Determine what events to generate, based on the command and the state.
let eventsFromCommand log command stateBeforeCommand =
// ------------------------- // create the StateChangedEvent from the TurtleCommand
let stateChangedEvent =
match command.action with
| Move dist -> Moved dist
| Turn angle -> Turned angle
| PenUp -> PenWentUp
| PenDown -> PenWentDown
| SetColor color -> ColorChanged color
// ------------------------- // calculate the current state from the new event
let stateAfterCommand =
applyEvent log stateBeforeCommand stateChangedEvent
// ------------------------- // create the MovedEvent
let startPos = stateBeforeCommand.position
let endPos = stateAfterCommand.position
let penColor =
if stateBeforeCommand.penState=Down then
Some stateBeforeCommand.color
else
None
let movedEvent = {
startPos = startPos
endPos = endPos
penColor = penColor
}
// ------------------------- // return the list of events
if startPos <> endPos then
// if the turtle has moved, return both the stateChangedEvent and the movedEve
nt
// lifted into the common TurtleEvent type
[ StateChangedEvent stateChangedEvent; MovedEvent movedEvent]
else
// if the turtle has not moved, return just the stateChangedEvent
[ StateChangedEvent stateChangedEvent]
1005
/// The type representing a function that gets the StateChangedEvents for a turtle id
/// The oldest events are first
type GetStateChangedEventsForId =
TurtleId -> StateChangedEvent list
/// The type representing a function that saves a TurtleEvent
type SaveTurtleEvent =
TurtleId -> TurtleEvent -> unit
/// main function : process a command
let commandHandler
(log:string -> unit)
(getEvents:GetStateChangedEventsForId)
(saveEvent:SaveTurtleEvent)
(command:TurtleCommand) =
/// First load all the events from the event store
let eventHistory =
getEvents command.turtleId
/// Then, recreate the state before the command
let stateBeforeCommand =
let nolog = ignore // no logging when recreating state
eventHistory
|> List.fold (applyEvent nolog) Turtle.initialTurtleState
/// Construct the events from the command and the stateBeforeCommand
/// Do use the supplied logger for this bit
let events = eventsFromCommand log command stateBeforeCommand
// store the events in the event store
events |> List.iter (saveEvent command.turtleId)
And then we can draw a figure by sending the various commands to the command handler:
1006
let drawTriangle() =
let handler = makeCommandHandler()
handler (move 100.0)
handler (turn 120.0<Degrees>)
handler (move 100.0)
handler (turn 120.0<Degrees>)
handler (move 100.0)
handler (turn 120.0<Degrees>)
NOTE: I have not shown how to create the command handler or event store, see the code
for full details.
1007
However the commandHandler only does the minimal amount of work, such as updating
state, and does NOT do any complex domain logic. The complex logic is performed by one
or more downstream "processors" (also sometimes called "aggregators") that subscribe to
the event stream.
You can even think of these events as "commands" to the processors, and of course, the
processors can generate new events for another processor to consume, so this approach
can be extended into an architectural style where an application consists of a set of
command handlers linked by an event store.
This techique is often called "stream processing". However, Jessica Kerr once called this
approach "Functional Retroactive Programming" -- I like that, so I'm going to steal that
name!
1008
However, before we can create a processor, we need some helper functions that can filter
the event store feed to only include turtle specific events, and of those only
StateChangedEvent s or MovedEvent s.
Now let's create a processor that listens for movement events and moves a physical turtle
when the virtual turtle is moved.
We will make the input to the processor be an IObservable -- an event stream -- so that it is
not coupled to any specific source such as the EventStore . We will connect the EventStore
"save" event to this processor when the application is configured.
/// Physically move the turtle
let physicalTurtleProcessor (eventStream:IObservable<Guid*obj>) =
// the function that handles the input from the observable
let subscriberFn (ev:MovedEvent) =
let colorText =
match ev.penColor with
| Some color -> sprintf "line of color %A" color
| None -> "no line"
printfn "[turtle ]: Moved from (%0.2f,%0.2f) to (%0.2f,%0.2f) with %s"
ev.startPos.x ev.startPos.y ev.endPos.x ev.endPos.y colorText
// start with all events
eventStream
// filter the stream on just TurtleEvents
|> Observable.choose (function (id,ev) -> turtleFilter ev)
// filter on just MovedEvents
|> Observable.choose moveFilter
// handle these
|> Observable.subscribe subscriberFn
1009
In this case we are just printing the movement -- I'll leave the building of an actual Lego
Mindstorms turtle as an exercise for the reader!
Let's also create a processor that draws lines on a graphics display:
/// Draw lines on a graphics device
let graphicsProcessor (eventStream:IObservable<Guid*obj>) =
// the function that handles the input from the observable
let subscriberFn (ev:MovedEvent) =
match ev.penColor with
| Some color ->
printfn "[graphics]: Draw line from (%0.2f,%0.2f) to (%0.2f,%0.2f) with co
lor %A"
ev.startPos.x ev.startPos.y ev.endPos.x ev.endPos.y color
| None ->
() // do nothing
// start with all events
eventStream
// filter the stream on just TurtleEvents
|> Observable.choose (function (id,ev) -> turtleFilter ev)
// filter on just MovedEvents
|> Observable.choose moveFilter
// handle these
|> Observable.subscribe subscriberFn
And finally, let's create a processor that accumulates the total distance moved so that we
can keep track of how much ink has been used, say.
1010
This processor uses Observable.scan to accumulate the events into a single value -- the
total distance travelled.
Processors in practice
Let's try these out!
For example, here is drawTriangle :
1011
let drawTriangle() =
// clear older events
eventStore.Clear turtleId
// create an event stream from an IEvent
let eventStream = eventStore.SaveEvent :> IObservable<Guid*obj>
// register the processors
use physicalTurtleProcessor = EventProcessors.physicalTurtleProcessor eventStream
use graphicsProcessor = EventProcessors.graphicsProcessor eventStream
use inkUsedProcessor = EventProcessors.inkUsedProcessor eventStream
let handler = makeCommandHandler
handler (move 100.0)
handler (turn 120.0<Degrees>)
handler (move 100.0)
handler (turn 120.0<Degrees>)
handler (move 100.0)
handler (turn 120.0<Degrees>)
You can see that all the processors are handling events successfully.
The turtle is moving, the graphics processor is drawing lines, and the ink used processor has
correctly calculated the total distance moved as 300 units.
Note, though, that the ink used processor is emitting output on every state change (such as
turning), rather than only when actual movement happens.
We can fix this by putting a pair (previousDistance, currentDistance) in the stream, and
then filtering out those events where the values are the same.
1012
1013
and there are no longer any duplicate messages from the inkUsedProcessor .
1014
type MoveResponse =
| MoveOk
| HitABarrier
type SetColorResponse =
| ColorOk
| OutOfInk
We will need a bounds checker to see if the turtle is in the arena. Say that if the position tries
to go outside the square (0,0,100,100), the response is HitABarrier :
// if the position is outside the square (0,0,100,100)
// then constrain the position and return HitABarrier
let checkPosition position =
let isOutOfBounds p =
p > 100.0 || p < 0.0
let bringInsideBounds p =
max (min p 100.0) 0.0
if isOutOfBounds position.x || isOutOfBounds position.y then
let newPos = {
x = bringInsideBounds position.x
y = bringInsideBounds position.y }
HitABarrier,newPos
else
MoveOk,position
And finally, the move function needs an extra line to check the new position:
let move log distance state =
let newPosition = ...
// adjust the new position if out of bounds
let moveResult, newPosition = checkPosition newPosition
...
1015
We will make similar changes for the setColor function too, returning OutOfInk if we
attempt to set the color to Red .
let setColor log color state =
let colorResult =
if color = Red then OutOfInk else ColorOk
log (sprintf "SetColor %A" color)
// return the new state and the SetColor result
let newState = {state with color = color}
(colorResult,newState)
With the new versions of the turtle functions available, we have to create implementations
that can respond to the error cases. That will be done in the next two examples.
The source code for the new turtle functions is available here.
1016
let drawShape() =
// define a set of instructions
let t = turtle {
do! move 60.0
// error FS0001:
// This expression was expected to have type
// Turtle.MoveResponse
// but here has type
// unit
do! move 60.0
}
// etc
The code does compile and work, but if we run it the output shows that, by the third call, we
are banging our turtle against the wall (at 100,0) and not moving anywhere.
Move 60.0
...Draw line from (0.0,0.0) to (60.0,0.0) using Black
Move 60.0
...Draw line from (60.0,0.0) to (100.0,0.0) using Black
Move 60.0
...Draw line from (100.0,0.0) to (100.0,0.0) using Black
1017
Let's design a function to implement this. The input will be a MoveResponse , but what will the
output be? We want to encode the turn action somehow, but the raw turn function needs
state input that we don't have. So instead let's return a turtle workflow that represents the
instruction we want to do, when the state becomes available (in the run command).
So here is the code:
let handleMoveResponse moveResponse = turtle {
match moveResponse with
| Turtle.MoveOk ->
() // do nothing
| Turtle.HitABarrier ->
// turn 90 before trying again
printfn "Oops -- hit a barrier -- turning"
do! turn 90.0<Degrees>
}
which means that it is a monadic (or "diagonal") function -- one that starts in the normal
world and ends in the TurtleStateComputation world.
These are exactly the functions that we can use "bind" with, or within computation
expressions, let! or do! .
Now we can add this handleMoveResponse step after move in the turtle workflow:
let drawShape() =
// define a set of instructions
let t = turtle {
let! response = move 60.0
do! handleMoveResponse response
let! response = move 60.0
do! handleMoveResponse response
let! response = move 60.0
do! handleMoveResponse response
}
// finally, run the monad using the initial state
runT t initialTurtleState
1018
Move 60.0
...Draw line from (0.0,0.0) to (60.0,0.0) using Black
Move 60.0
...Draw line from (60.0,0.0) to (100.0,0.0) using Black
Oops -- hit a barrier -- turning
Turn 90.0
Move 60.0
...Draw line from (100.0,0.0) to (100.0,60.0) using Black
You can see that the move response worked. When the turtle hit the edge at (100,0) it
turned 90 degrees and the next move succeeded (from (100,0) to (100,60)).
So there you go! This code demonstrates how you can make decisions inside the turtle
workflow while the state is being passed around behind the scenes.
Designing an interpreter
1019
The approach we will take is to design an "interpreter" for a set of turtle commands, where
the client provides the commands to the turtle, and responds to outputs from the turtle, but
the actual turtle functions are provided later by a particular implementation.
In other words, we have a chain of interleaved commands and turtle functions that look like
this:
The problem is that we cannot be sure that the response correctly matches the command.
For example, if I send a Move command, I expect to get a MoveResponse , and never a
SetColorResponse . But this implementation doesn't enforce that!
1020
Move command => pair of (Move command parameters), (function MoveResponse -> something
)
Turn command => pair of (Turn command parameters), (function unit -> something)
etc
Or in code:
type TurtleProgram =
// (input params) (response)
| Move of Distance * (MoveResponse -> TurtleProgram)
| Turn of Angle * (unit -> TurtleProgram)
| PenUp of (* none *) (unit -> TurtleProgram)
| PenDown of (* none *) (unit -> TurtleProgram)
| SetColor of PenColor * (SetColorResponse -> TurtleProgram)
I've renamed the type from TurtleCommand to TurtleProgram because it is no longer just a
command, but is now a complete chain of commands and associated response handlers.
There's a problem though! Every step needs yet another TurtleProgram to follow -- so when
will it stop? We need some way of saying that there is no next command.
To solve this issue, we will add a special Stop case to the program type:
1021
type TurtleProgram =
// (input params) (response)
| Stop
| Move of Distance * (MoveResponse -> TurtleProgram)
| Turn of Angle * (unit -> TurtleProgram)
| PenUp of (* none *) (unit -> TurtleProgram)
| PenDown of (* none *) (unit -> TurtleProgram)
| SetColor of PenColor * (SetColorResponse -> TurtleProgram)
Note that there is no mention of TurtleState in this structure. How the turtle state is
managed is internal to the interpreter, and is not part of the "instruction set", as it were.
TurtleProgram is an example of an Abstract Syntax Tree (AST) -- a structure that
This program is a data structure containing only client commands and responses -- there are
no actual turtle functions in it anywhere! And yes, it is really ugly right now, but we will fix that
shortly.
Now the next step is to interpret this data structure.
Let's create an interpreter that calls the real turtle functions. How would we implement the
Move case, say?
Get the next step in the program by passing the MoveResult to the associated function
Finally call the interpreter again (recursively) with the new program and new turtle state.
1022
You can see that the updated turtle state is passed as a parameter to the next recursive call,
and so no mutable field is needed.
Here's the full code for interpretAsTurtle :
let rec interpretAsTurtle state program =
let log = printfn "%s"
match program with
| Stop ->
state
| Move (dist,next) ->
let result,newState = Turtle.move log dist state
let nextProgram = next result // compute the next step
interpretAsTurtle newState nextProgram
| Turn (angle,next) ->
let newState = Turtle.turn log angle state
let nextProgram = next() // compute the next step
interpretAsTurtle newState nextProgram
| PenUp next ->
let newState = Turtle.penUp log state
let nextProgram = next()
interpretAsTurtle newState nextProgram
| PenDown next ->
let newState = Turtle.penDown log state
let nextProgram = next()
interpretAsTurtle newState nextProgram
| SetColor (color,next) ->
let result,newState = Turtle.setColor log color state
let nextProgram = next result
interpretAsTurtle newState nextProgram
1023
But unlike all the previous approaches we can take exactly the same program and interpret it
in a new way. We don't need to set up any kind of dependency injection, we just need to use
a different interpreter.
So let's create another interpreter that aggregates the distance travelled, without caring
about the turtle state:
let rec interpretAsDistance distanceSoFar program =
let recurse = interpretAsDistance
let log = printfn "%s"
match program with
| Stop ->
distanceSoFar
| Move (dist,next) ->
let newDistanceSoFar = distanceSoFar + dist
let result = Turtle.MoveOk // hard-code result
let nextProgram = next result
recurse newDistanceSoFar nextProgram
| Turn (angle,next) ->
// no change in distanceSoFar
let nextProgram = next()
recurse distanceSoFar nextProgram
| PenUp next ->
// no change in distanceSoFar
let nextProgram = next()
recurse distanceSoFar nextProgram
| PenDown next ->
// no change in distanceSoFar
let nextProgram = next()
recurse distanceSoFar nextProgram
| SetColor (color,next) ->
// no change in distanceSoFar
let result = Turtle.ColorOk // hard-code result
let nextProgram = next result
recurse distanceSoFar nextProgram
1024
In this case, I've aliased interpretAsDistance as recurse locally to make it obvious what
kind of recursion is happening.
Let's run the same program with this new interpreter:
let program = drawTriangle // same program
let interpret = interpretAsDistance // choose an interpreter
let initialState = 0.0
interpret initialState program |> printfn "Total distance moved is %0.1f"
Note that the Stop case has a value of type 'a associated with it now. This is needed so
that we can implement return properly:
let returnT x =
Stop x
The bind function is more complicated to implement. Don't worry about how it works right
now -- the important thing is that the types match up and it compiles!
1025
We can now create a workflow that handles MoveResponse s just as in the monadic control
flow example (way 12) earlier.
1026
// helper functions
let stop = fun x -> Stop x
let move dist = Move (dist, stop)
let turn angle = Turn (angle, stop)
let penUp = PenUp stop
let penDown = PenDown stop
let setColor color = SetColor (color,stop)
let handleMoveResponse log moveResponse = turtleProgram {
match moveResponse with
| Turtle.MoveOk ->
()
| Turtle.HitABarrier ->
// turn 90 before trying again
log "Oops -- hit a barrier -- turning"
let! x = turn 90.0<Degrees>
()
}
// example
let drawTwoLines log = turtleProgram {
let! response = move 60.0
do! handleMoveResponse log response
let! response = move 60.0
do! handleMoveResponse log response
}
Let's interpret this using the real turtle functions (assuming that the interpretAsTurtle
function has been modified to handle the new generic structure):
let log = printfn "%s"
let program = drawTwoLines log
let interpret = interpretAsTurtle
let initialState = Turtle.initialTurtleState
interpret initialState program |> ignore
The output shows that the MoveResponse is indeed being handled correctly when the barrier
is encountered:
Move 60.0
...Draw line from (0.0,0.0) to (60.0,0.0) using Black
Move 60.0
...Draw line from (60.0,0.0) to (100.0,0.0) using Black
Oops -- hit a barrier -- turning
Turn 90.0
This approach works fine, but it bothers me that there is a special Stop case in the
TurtleProgram type. It would nice if we could somehow just focus on the five turtle actions
Note that I've also changed the responses for Turn , PenUp and PenDown to be single
values rather than a unit function. Move and SetColor remain as functions though.
In this new "free monad" approach, the only custom code we need to write is a simple map
function for the api type, in this case TurtleInstruction :
let mapInstr f inst =
match inst with
| Move(dist,next) -> Move(dist,next >> f)
| Turn(angle,next) -> Turn(angle,f next)
| PenUp(next) -> PenUp(f next)
| PenDown(next) -> PenDown(f next)
| SetColor(color,next) -> SetColor(color,next >> f)
The rest of the code ( return , bind , and the computation expression) is always
implemented exactly the same way, regardless of the particular api. That is, more boilerplate
is needed but less thinking is required!
The interpreters need to change in order to handle the new cases. Here's a snippet of the
new version of interpretAsTurtle :
1028
And we also need to adjust the helper functions when creating a workflow. You can see
below that we now have slightly more complicated code like KeepGoing (Move (dist, Stop))
instead of the simpler code in the original interpreter.
// helper functions
let stop = Stop()
let move dist = KeepGoing (Move (dist, Stop)) // "Stop" is a function
let turn angle = KeepGoing (Turn (angle, stop)) // "stop" is a value
let penUp = KeepGoing (PenUp stop)
let penDown = KeepGoing (PenDown stop)
let setColor color = KeepGoing (SetColor (color,Stop))
let handleMoveResponse log moveResponse = turtleProgram {
... // as before
// example
let drawTwoLines log = turtleProgram {
let! response = move 60.0
do! handleMoveResponse log response
let! response = move 60.0
do! handleMoveResponse log response
}
But with those changes, we are done, and the code works just as before.
1029
1030
having state only at the edge (the functional core/imperative shell in way 4)
hiding state in an agent (way 5)
threading state behind the scenes in a state monad (the turtle workflow in ways
8 and 12)
avoiding state altogether by using batches of commands (way 9) or batches of
events (way 10) or an interpreter (way 13)
Wrapping a function in a type. Used in way 8 to manage state (the State monad) and
in way 13 to store responses.
Computation expressions, lots of them! We created and used three:
result for working with errors
turtle for managing turtle state
turtleProgram for building an AST in the interpreter approach (way 13).
Chaining of monadic functions in the result and turtle workflows. The underlying
functions are monadic ("diagonal") and would not normally compose properly, but inside
a workflow, they can be sequenced easily and transparently.
Representing behavior as a data structure in the "functional dependency injection"
example (way 7) so that a single function could be passed in rather than a whole
interface.
Decoupling using a data-centric protocol as seen in the agent, batch command,
event sourcing, and interpreter examples.
Lock free and async processing using an agent (way 5).
The separation of "building" a computation vs. "running" it, as seen in the turtle
workflows (ways 8 and 12) and the turtleProgram workflow (way 13: interpreter).
Use of event sourcing to rebuild state from scratch rather than maintaining mutable
state in memory, as seen in the event sourcing (way 10) and FRP (way 11) examples.
Use of event streams and FRP (way 11) to break business logic into small,
independent, and decoupled processors rather than having a monolithic object.
I hope it's clear that examining these thirteen ways is just a fun exercise, and I'm not
suggesting that you immediately convert all your code to use stream processors and
interpreters! And, especially if you are working with people who are new to functional
programming, I would tend to stick with the earlier (and simpler) approaches unless there is
a clear benefit in exchange for the extra complexity.
Summary
1031
1032
1033
The client can not see inside the turtle state type, and so must rely entirely on the
1034
That's all there is to it. We only need to add some privacy modifiers to the earlier FP version
and we are done!
The implementation
First, we are going to put both the turtle state type and the Turtle module inside a common
module called AdtTurtle . This allows the turtle state to be accessible to the functions in the
AdtTurtle.Turtle module, while being inaccessible outside the AdtTurtle .
Next, the turtle state type is going to be called Turtle now, rather than TurtleState ,
because we are treating it almost as an object.
Finally, the associated module Turtle (that contains the functions) is going have some
special attributes:
RequireQualifiedAccess means the module name must be used when accessing the
This would not be required for generic types (e.g if we had Turtle<'a> instead).
module AdtTurtle =
/// A private structure representing the turtle
type Turtle = private {
position : Position
angle : float<Degrees>
color : PenColor
penState : PenState
}
/// Functions for manipulating a turtle
/// "RequireQualifiedAccess" means the module name *must*
/// be used (just like List module)
/// "ModuleSuffix" is needed so the that module can
/// have the same name as the state type
[<RequireQualifiedAccess>]
[<CompilationRepresentation (CompilationRepresentationFlags.ModuleSuffix)>]
module Turtle =
An alternative way to avoid collisions is to have the state type have a different case, or a
different name with a lowercase alias, like this:
1035
No matter how the naming is done, we will need a way to construct a new Turtle .
If there are no parameters to the constructor, and the state is immutable, then we just need
an initial value rather than a function (like Set.empty say).
Otherwise we can define a function called make (or create or similar):
[<RequireQualifiedAccess>]
[<CompilationRepresentation (CompilationRepresentationFlags.ModuleSuffix)>]
module Turtle =
/// return a new turtle with the specified color
let make(initialColor) = {
position = initialPosition
angle = 0.0<Degrees>
color = initialColor
penState = initialPenState
}
The rest of the turtle module functions are unchanged from their implementation in way 2.
An ADT client
Let's look the client now.
First, let's check that the state really is private. If we try to create a state explicitly, as shown
below, we get a compiler error:
let initialTurtle = {
position = initialPosition
angle = 0.0<Degrees>
color = initialColor
penState = initialPenState
}
// Compiler error FS1093:
// The union cases or fields of the type 'Turtle'
// are not accessible from this code location
If we use the constructor and then try to directly access a field directly (such as position ),
we again get a compiler error:
1036
But if we stick to the functions in the Turtle module, we can safely create a state value and
then call functions on it, just as we did before:
// versions with log baked in (via partial application)
let move = Turtle.move log
let turn = Turtle.turn log
// etc
let drawTriangle() =
Turtle.make(Red)
|> move 100.0
|> turn 120.0<Degrees>
|> move 100.0
|> turn 120.0<Degrees>
|> move 100.0
|> turn 120.0<Degrees>
1037
First, there is no TurtleState anywhere. The published turtle functions will encapsulate the
state for us. Similarly there is no log function.
Next, the record of functions TurtleFunctions defines a field for each function in the API
( move , turn , etc.):
The move function is optional, meaning that it might not be available.
The turn , penUp and penDown functions are always available.
The setColor operation has been broken out into three separate functions, one for
each color, because you might not be able to use red ink, but still be able to use blue
ink. To indicate that these functions might not be available, option is used again.
We have also declared type aliases for each function to make them easier to work. Writing
MoveFn is easier than writing Distance -> (MoveResponse * TurtleFunctions) everywhere!
Note that, since these definitions are mutually recursive, I was forced to use the and
keyword.
Finally, note the difference between the signature of MoveFn in this design and the signature
of move in the earlier design of way 12.
Earlier version:
val move :
Log -> Distance -> TurtleState -> (MoveResponse * TurtleState)
New version:
val move :
Distance -> (MoveResponse * TurtleFunctions)
On the input side, the Log and TurtleState parameters are gone, and on the output side,
the TurtleState has been replaced with TurtleFunctions .
This means that somehow, the output of every API function must be changed to be a
TurtleFunctions record.
1039
1040
We need some way of changing canMove back to true! So let's assume that if you turn, you
can move again.
Let's add that logic to the turn function then:
let private turn log angle state =
log (sprintf "Turn %0.1f" angle)
// calculate new angle
let newAngle = (state.angle + angle) % 360.0<Degrees>
// NEW!! assume you can always move after turning
let canMove = true
// update the state
{state with angle = newAngle; canMove = canMove}
The penUp and penDown functions are unchanged, other than being made private.
And for the last operation, setColor , we'll remove the ink from the availability set as soon
as it is used just once!
let private setColor log color state =
let colorResult =
if color = Red then OutOfInk else ColorOk
log (sprintf "SetColor %A" color)
// NEW! remove color ink from available inks
let newAvailableInk = state.availableInk |> Set.remove color
// return the new state and the SetColor result
let newState = {state with color = color; availableInk = newAvailableInk}
(colorResult,newState)
Finally we need a function that can create a TurtleFunctions record from the TurtleState .
I'll call it createTurtleFunctions .
Here's the complete code, and I'll discuss it in detail below:
/// Create the TurtleFunctions structure associated with a TurtleState
let rec private createTurtleFunctions state =
let ctf = createTurtleFunctions // alias
// create the move function,
// if the turtle can't move, return None
let move =
// the inner function
let f dist =
let resp, newState = move state.logger dist state
(resp, ctf newState)
1041
1042
First, note that this function needs the rec keyword attached, as it refers to itself. I've
added a shorter alias ( ctf ) for it as well.
Next, new versions of each of the API functions are created. For example, a new turn
function is defined like this:
let turn angle =
let newState = turn state.logger angle state
ctf newState
This calls the original turn function with the logger and state, and then uses the recursive
call ( ctf ) to convert the new state into the record of functions.
For an optional function like move , it is a bit more complicated. An inner function f is
defined, using the orginal move , and then either f is returned as Some , or None is
returned, depending on whether the state.canMove flag is set:
// create the move function,
// if the turtle can't move, return None
let move =
// the inner function
let f dist =
let resp, newState = move state.logger dist state
(resp, ctf newState)
// return Some of the inner function
// if the turtle can move, or None
if state.canMove then
Some f
else
None
Similarly, for setColor , an inner function f is defined and then returned or not depending
on whether the color parameter is in the state.availableInk collection:
let setColor color =
// the inner function
let f() =
let resp, newState = setColor state.logger color state
(resp, ctf newState)
// return Some of the inner function
// if that color is available, or None
if state.availableInk |> Set.contains color then
Some f
else
None
1043
This function bakes in the log function, creates a new state, and then calls
createTurtleFunctions to return a TurtleFunction record for the client to use.
1044
let testBoundary() =
let turtleFns = Turtle.make(Red,log)
match turtleFns.move with
| None ->
log "Error: Can't do move 1"
| Some moveFn ->
...
In the last case, the moveFn is available, so we can call it with a distance of 60.
The output of the function is a pair: a MoveResponse type and a new TurtleFunctions
record.
We'll ignore the MoveResponse and check the TurtleFunctions record again to see if we can
do the next move:
let testBoundary() =
let turtleFns = Turtle.make(Red,log)
match turtleFns.move with
| None ->
log "Error: Can't do move 1"
| Some moveFn ->
let (moveResp,turtleFns) = moveFn 60.0
match turtleFns.move with
| None ->
log "Error: Can't do move 2"
| Some moveFn ->
...
1045
Now we can try setting some colors using the maybe workflow:
1046
let testInk() =
maybe {
// create a turtle
let turtleFns = Turtle.make(Black,log)
// attempt to get the "setRed" function
let! setRedFn = turtleFns.setRed
// if so, use it
let (resp,turtleFns) = setRedFn()
// attempt to get the "move" function
let! moveFn = turtleFns.move
// if so, move a distance of 60 with the red ink
let (resp,turtleFns) = moveFn 60.0
// check if the "setRed" function is still available
do! match turtleFns.setRed with
| None ->
logO "Error: Can no longer use Red ink"
| Some _ ->
logO "Success: Can still use Red ink"
// check if the "setBlue" function is still available
do! match turtleFns.setBlue with
| None ->
logO "Error: Can no longer use Blue ink"
| Some _ ->
logO "Success: Can still use Blue ink"
} |> ignore
Actually, using a maybe workflow is not a very good idea, because the first failure exits the
workflow! You'd want to come up with something a bit better for real code, but I hope that
you get the idea.
1047
Advantages
Prevents clients from abusing the API.
Allows APIs to evolve (and devolve) without affecting clients. For example, I could
transition to a monochrome-only turtle by hard-coding None for each color function in
the record of functions, after which I could safely remove the setColor implementation.
During this process no client would break! This is similar to the HATEAOS approach for
RESTful web services.
Clients are decoupled from a particular implementation because the record of functions
acts as an interface.
Disadvantages
Complex to implement.
The client's logic is much more convoluted as it can never be sure that a function will be
available! It has to check every time.
The API is not easily serializable, unlike some of the data-oriented APIs.
For more on capability-based security, see my posts or watch my "Enterprise Tic-Tac-Toe"
video.
The source code for this version is available here.
Summary
I was of three minds,
Like a finger tree
In which there are three immutable turtles.
-- "Thirteen ways of looking at a turtle", by Wallace D Coriacea
I feel better now that I've got these two extra ways out of my system! Thanks for reading!
The source code for this post is available on github.
.
1048
Overview
Here's an overview of what I plan to cover in this series:
Converting a use-case into a function. In this first post, we'll examine a simple use
case and see how it might be implemented using a functional approach.
Connecting smaller functions together. In the next post, we'll discuss a simple
metaphor for combining smaller functions into bigger functions.
Type driven design and failure types. In the third post, we'll build the types needed for
the use case, and discuss the use of special error types for the failure path.
Configuration and dependency management. In this post, we'll talk about how to
1049
Getting started
Let's pick a very simple use case, namely updating some customer information via a web
service.
So here are the basic requirements:
A user submits some data (userid, name and email).
We check to see that the name and email are valid.
The appropriate user record in a database is updated with the new name and email.
If the email has changed, send a verification email to that address.
Display the result of the operation to the user.
This is a typical data centric use case. There is some sort of request that triggers the use
case, and then the request data "flows" through the system, being processed by each step
in turn. This kind of scenario is common in enterprise software, which is why I am using it as
an example.
Here's a diagram of the various components:
1050
But this describes the "happy path" only. Reality is never so simple! What happens if the
userid is not found in the database, or the email address is not valid, or the database has an
error?
Let's update the diagram to show all the things that could go wrong.
At each step in the use case, various things could cause errors, as shown. Explaining how to
handle these errors in an elegant way will be one of the goals of this series.
Thinking functionally
So now that we understand the steps in the use case, how do we design a solution using a
functional approach?
1051
First of all, we have to address a mismatch between the original use case and functional
thinking.
In the use case, we typically think of a request/response model. The request is sent, and the
response comes back. If something goes wrong, the flow is short-circuited and response is
returned "early".
Here's a diagram showing what I mean, based on a simplified version of the use case:
But in the functional model, a function is a black box with an input and an output, like this:
Once we have done that, we can convert the whole flow into a single "black box" function
like this:
1052
But of course, if you look inside the big function, it is made up of ("composed from" in
functional-speak) smaller functions, one for each step, joined in a pipeline.
Error handling
In that last diagram, there is one success output and three error outputs. This is a problem,
as functions can have only one output, not four!
How can we handle this?
The answer is to use a union type, with one case to represent each of the different possible
outputs. And then the function as a whole would indeed only have a single output.
Here's an example of a possible type definition for the output:
type UseCaseResult =
| Success
| ValidationError
| UpdateError
| SmtpError
And here's the diagram reworked to show a single output with four different cases
embedded in it:
1053
type UseCaseResult =
| Success
| Failure
This type is very generic and will work with any workflow! In fact, you'll soon see that we can
create a nice library of useful functions that will work with this type, and which can be reused
for all sorts of scenarios.
One more thing though -- as it stands there is no data in the result at all, just a
success/failure status. We need to tweak it a bit so that it can contain an actual success or
failure object. We will specify the success type and failure type using generics (a.k.a. type
parameters).
Here's the final, completely generic and reusable version:
type Result<'TSuccess,'TFailure> =
| Success of 'TSuccess
| Failure of 'TFailure
In fact, there is already a type almost exactly like this defined in the F# library. It's called
Choice. For clarity though, I will continue to use the Result type defined above for this and
the next post. When we come to some more serious coding, we'll revisit this.
So, now, showing the individual steps again, we can see that we will have to combine the
errors from each step onto to a single "failure" path.
1054
The use case function will return a union type with two cases: Success and Failure .
The use case function will be built from a series of smaller functions, each representing
one step in a data flow.
The errors from each step will be combined into a single error path.
1055
In this post, we'll look at various ways of connecting these step functions into a single unit.
The detailed internal design of the functions will be described in a later post.
But as before, this would not be a valid function. A function can only have one output, so we
must use the Result type we defined last time:
type Result<'TSuccess,'TFailure> =
| Success of 'TSuccess
| Failure of 'TFailure
1056
To show you how this works in practice, here is an example of what an actual validation
function might look like:
type Request = {name:string; email:string}
let validateInput input =
if input.name = "" then Failure "Name must not be blank"
else if input.email = "" then Failure "Email must not be blank"
else Success input // happy path
If you look at the type of the function, the compiler has deduced that it takes a Request and
spits out a Result as output, with a Request for the success case and a string for the
failure case:
validateInput : Request -> Result<Request,string>
We can analyze the other steps in the flow in the same way. We will find that each one will
have the same "shape" -- some sort of input and then this Success/Failure output.
A pre-emptive apology: Having just said that a function can't have two outputs, I may
occasionally refer to them hereafter as "two output" functions! Of course, what I mean is that
the shape of the function output has two cases.
There is a great analogy for doing this -- something you are probably already familiar with.
Railways!
Railways have switches ("points" in the UK) for directing trains onto a different track. We can
think of these "Success/Failure" functions as railway switches, like this:
1057
How do we combine them so that both failure tracks are connected? It's obvious -- like this!
And if we have a whole series of switches, we will end up with a two track system, looking
something like this:
The top track is the happy path, and the bottom track is the failure path.
Now stepping back and looking at the big picture, we can see that we will have a series of
black box functions that appear to be straddling a two-track railway, each function
processing data and passing it down the track to the next function:
But if we look inside the functions, we can see that there is actually a switch inside each
one, for shunting bad data onto the failure track:
Note that once we get on the failure path, we never (normally) get back onto the happy path.
We just bypass the rest of the functions until we reach the end.
1058
Basic composition
Before we discuss how to "glue" the step functions together, let's review how composition
works.
Imagine that a standard function is a black box (a tunnel, say) sitting on a one-track railway.
It has one input and one output.
If we want to connect a series of these one-track functions, we can use the left-to-right
composition operator, with the symbol >> .
The same composition operation also works with two-track functions as well:
The only constraint on composition is that the output type of the left-hand function has to
match the input type of the right-hand function.
In our railway analogy, this means that you can connect one-track output to one-track input,
or two-track output to two-track input, but you can't directly connect two-track output to onetrack input.
1059
The answer is simple. We can create an "adapter" function that has a "hole" or "slot" for a
switch function and converts it into a proper two-track function. Here's an illustration:
And here's what the actual code looks like. I'm going to name the adapter function bind ,
which is the standard name for it.
let bind switchFunction =
fun twoTrackInput ->
match twoTrackInput with
| Success s -> switchFunction s
| Failure f -> Failure f
The bind function takes a switch function as a parameter and returns a new function. The
new function takes a two-track input (which is of type Result ) and then checks each case.
If the input is a Success it calls the switchFunction with the value. But if the input is a
Failure , then the switch function is bypassed.
One way of interpreting this signature is that the bind function has one parameter, a switch
function ( 'a -> Result<..> ) and it returns a fully two-track function ( Result<..> ->
Result<..> ) as output.
The output of the returned function is another Result , this time of type 'b (for
success) and 'c (for failure) -- the same type as the switch function output.
If you think about it, this type signature is exactly what we would expect.
1060
Note that this function is completely generic -- it will work with any switch function and any
types. All it cares about is the "shape" of the switchFunction , not the actual types involved.
This is exactly the same as the first definition. And if you are wondering how a two
parameter function can be exactly the same as a one parameter function, you need to read
the post on currying!
Yet another way of writing it is to replace the match..with syntax with the more concise
function keyword, like this:
You might see all three styles in other code, but I personally prefer to use the second style
( let bind switchFunction twoTrackInput = ), because I think that having explicit parameters
makes the code more readable for non-experts.
1061
type Result<'TSuccess,'TFailure> =
| Success of 'TSuccess
| Failure of 'TFailure
type Request = {name:string; email:string}
let bind switchFunction twoTrackInput =
match twoTrackInput with
| Success s -> switchFunction s
| Failure f -> Failure f
Next we'll create three validation functions, each of which is a "switch" function, with the goal
of combining them into one bigger function:
let validate1 input =
if input.name = "" then Failure "Name must not be blank"
else Success input
let validate2 input =
if input.name.Length > 50 then Failure "Name must not be longer than 50 chars"
else Success input
let validate3 input =
if input.email = "" then Failure "Email must not be blank"
else Success input
Now to combine them, we apply bind to each validation function to create a new
alternative function that is two-tracked.
Then we can connect the two-tracked functions using standard function composition, like
this:
/// glue the three validation functions together
let combinedValidation =
// convert from switch to two-track input
let validate2' = bind validate2
let validate3' = bind validate3
// connect the two-tracks together
validate1 >> validate2' >> validate3'
The functions validate2' and validate3' are new functions that take two-track input. If
you look at their signatures you will see that they take a Result and return a Result . But
note that validate1 does not need to be converted to two track input. Its input is left as
one-track, and its output is two-track already, as needed for composition to work.
1062
Here's a diagram showing the Validate1 switch (unbound) and the Validate2 and
Validate3 switches, together with the Validate2' and Validate3' adapters.
I would encourage you to try it for yourself and play around with the validation functions and
test input.
You might be wondering if there is a way to run all three validations in parallel, rather than
serially, so that you can get back all the validation errors at once. Yes, there is a way, which
I'll explain later in this post.
While we are discussing the bind function, there is a common symbol for it, >>= , which is
used to pipe values into switch functions.
Here's the definition, which switches around the two parameters to make them easier to
chain together:
/// create an infix operator
let (>>=) twoTrackInput switchFunction =
bind switchFunction twoTrackInput
One way to remember the symbol is to think of it as the composition symbol, >> , followed
by a two-track railway symbol, = .
When used like this, the >>= operator is sort of like a pipe ( |> ) but for switch functions.
In a normal pipe, the left hand side is a one-track value, and the right hand value is a normal
function. But in a "bind pipe" operation, the left hand side is a two-track value, and the right
hand value is a switch function.
Here it is in use to create another implementation of the combinedValidation function.
let combinedValidation x =
x
|> validate1 // normal pipe because validate1 has a one-track input
// but validate1 results in a two track output...
>>= validate2 // ... so use "bind pipe". Again the result is a two track output
>>= validate3 // ... so use "bind pipe" again.
The difference between this implementation and the previous one is that this definition is
data-oriented rather than function-oriented. It has an explicit parameter for the initial data
value, namely x . x is passed to the first function, and then the output of that is passed to
the second function, and so on.
In the previous implementation (repeated below), there was no data parameter at all! The
focus was on the functions themselves, not the data that flows through them.
let combinedValidation =
validate1
>> bind validate2
>> bind validate3
An alternative to bind
1064
Another way to combine switches is not by adapting them to a two track input, but simply by
joining them directly together to make a new, bigger switch.
In other words, this:
becomes this:
But if you think about it, this combined track is actually just another switch! You can see this
if you cover up the middle bit. There's one input and two outputs:
So what we have really done is a form of composition for switches, like this:
Because each composition results in just another switch, we can always add another switch
again, resulting in an even bigger thing that is still a switch, and so on.
Here's the code for switch composition. The standard symbol used is >=> , a bit like the
normal composition symbol, but with a railway track between the angles.
let (>=>) switch1 switch2 x =
match switch1 x with
| Success s -> switch2 s
| Failure f -> Failure f
Again, the actual implementation is very straightforward. Pass the single track input x
through the first switch. On success, pass the result into the second switch, otherwise
bypass the second switch completely.
1065
Now we can rewrite the combinedValidation function to use switch composition rather than
bind:
let combinedValidation =
validate1
>=> validate2
>=> validate3
This one is the simplest yet, I think. It's very easy to extend of course, if we have a fourth
validation function, we can just append it to the end.
On the other hand, if your entire data flow consists of a chain of switches, then switch
composition can be simpler.
1066
And then here are the switches composed together to make a new bigger switch:
And here's the same thing done by using bind on the second switch:
Here's the switch composition operator rewritten using this way of thinking:
let (>=>) switch1 switch2 =
switch1 >> (bind switch2)
This implementation of switch composition is much simpler than the first one, but also more
abstract. Whether it is easier to comprehend for a beginner is another matter! I find that if
you think of functions as things in their own right, rather than just as conduits for data, this
approach becomes easier to understand.
1067
The code to do this is trivial. All we need to do is take the output of the one track function
and turn it into a two-track result. In this case, the result will always be Success.
// convert a normal function into a switch
let switch f x =
f x |> Success
In railway terms, we have added a bit of failure track. Taken as a whole, it looks like a switch
function (one-track input, two-track output), but of course, the failure track is just a dummy
and the switch never actually gets used.
1068
Once switch is available, we can easily append the canonicalizeEmail function to the end
of the chain. Since we are beginning to extend it, let's rename the function to usecase .
let usecase =
validate1
>=> validate2
>=> validate3
>=> switch canonicalizeEmail
1069
Again, we just need an adapter block with a slot for the simple function. We typically call this
adapter map .
And again, the actual implementation is very straightforward. If the two-track input is
Success , call the function, and turn its output into Success. On the other hand, if the two-
Note that normal composition is now used because map canonicalizeEmail is a fully twotrack function and can be connected to the output of the validate3 switch directly.
In other words, for one-track functions, >=> switch is exactly the same as >> map . Your
choice.
1070
To make this work, we need another adapter function, like switch , except that this time it
has a slot for one-track dead-end function, and converts it into a single-track pass through
function, with a one-track output.
Here's the code, which I will call tee , after the UNIX tee command:
let tee f x =
f x |> ignore
x
1071
Once we have converted the dead-end function to a simple one-track pass through function,
we can then use it in the data flow by converting it using switch or map as described
above.
Here's the code in use with the "switch composition" style:
// a dead-end function
let updateDatabase input =
() // dummy dead-end function for now
let usecase =
validate1
>=> validate2
>=> validate3
>=> switch canonicalizeEmail
>=> switch (tee updateDatabase)
Or alternatively, rather than using switch and then connecting with >=> , we can use map
and connect with >> .
Here's a variant implementation which is exactly the same but uses the "two-track" style with
normal composition
let usecase =
validate1
>> bind validate2
>> bind validate3
>> map canonicalizeEmail
>> map (tee updateDatabase)
Handling exceptions
Our dead end database update might not return anything, but that doesn't mean that it might
not throw an exception. Rather than crashing, we want to catch that exception and turn it
into a failure.
The code is similar to the switch function, except that it catches exceptions. I'll call it
tryCatch :
let tryCatch f x =
try
f x |> Success
with
| ex -> Failure ex.Message
1072
And here is a modified version of the data flow, using tryCatch rather than switch for the
update database code.
let usecase =
validate1
>=> validate2
>=> validate3
>=> switch canonicalizeEmail
>=> tryCatch (tee updateDatabase)
As an aside, we can use this function to create a simpler version of map , using id for the
failure function:
let map successFunc =
doubleMap successFunc id
1073
Let's use doubleMap to insert some logging into the data flow:
let log twoTrackInput =
let success x = printfn "DEBUG. Success so far: %A" x; x
let failure x = printfn "ERROR. %A" x; x
doubleMap success failure twoTrackInput
let usecase =
validate1
>=> validate2
>=> validate3
>=> switch canonicalizeEmail
>=> tryCatch (tee updateDatabase)
>> log
Right now these are trivial, just calling the constructor of the Result type, but when we get
down to some proper coding we'll see that by using these rather than the union case
constructor directly, we can isolate ourselves from changes behind the scenes.
1074
To make this easier, we can reuse the same trick that we did for switch composition. Rather
than doing many at once, if we just focus on a single pair, and "add" them to make a new
switch, we can then easily chain the "addition" together so that we can add as many as we
want. In other words, we just need to implement this:
1075
But we now have a new problem. What do we do with two successes, or two failures? How
do we combine the inner values?
I used s1 + s2 and f1 + f2 in the example above, but that implies that there is some sort
of + operator we can use. That may be true for strings and ints, but it is not true in general.
The method of combining values might change in different contexts, so rather than trying to
solve it once and for all, let's punt by letting the caller pass in the functions that are needed.
Here's a rewritten version:
let plus addSuccess addFailure switch1 switch2 x =
match (switch1 x),(switch2 x) with
| Success s1,Success s2 -> Success (addSuccess s1 s2)
| Failure f1,Success _ -> Failure f1
| Success _ ,Failure f2 -> Failure f2
| Failure f1,Failure f2 -> Failure (addFailure f1 f2)
I have put these new functions first in the parameter list, to aid partial application.
When both functions fail, they will return different strings, so the addFailure function
should concatenate them.
For validation then, the "plus" operation that we want is like an "AND" function. Only if both
parts are "true" is the result "true".
That naturally leads to wanting to use && as the operator symbol. Unfortunately, && is
reserved, but we can use &&& , like this:
1076
And now using &&& , we can create a single validation function that combines the three
smaller validations:
let combinedValidation =
validate1
&&& validate2
&&& validate3
The first test now has two validation errors combined into a single string, just as we wanted.
Next, we can tidy up the main dataflow function by using the usecase function now instead
of the three separate validation functions we had before:
let usecase =
combinedValidation
>=> switch canonicalizeEmail
>=> tryCatch (tee updateDatabase)
And if we test that now, we can see that a success flows all the way to the end and that the
email is lowercased and trimmed:
1077
// test 4
let input4 = {name="Alice"; email="UPPERCASE "}
usecase input4
|> printfn "Result4=%A"
// ==> Result4=Success {name = "Alice"; email = "uppercase";}
You might be asking, can we create a way of OR-ing validation functions as well? That is,
the overall result is valid if either part is valid? The answer is yes, of course. Try it! I suggest
that you use the symbol ||| for this.
1078
1079
Concept
Description
succeed
fail
bind
An adapter that takes a switch function and creates a new function that
accepts two-track values as input.
>>=
An infix version of bind for piping two-track values into switch functions.
>>
>=>
switch
map
An adapter that takes a normal one-track function and turns it into a twotrack function. (Also known as a "lift" in some contexts.)
tee
tryCatch
doubleMap
An adapter that takes two one-track functions and turns them into a
single two-track function. (Also known as bimap .)
plus
A combiner that takes two switch functions and creates a new switch
function by joining them in "parallel" and "adding" the results. (Also
known as ++ and in other contexts.)
&&&
1080
| Failure of 'TFailure
// convert a single value into a two-track result
let succeed x =
Success x
// convert a single value into a two-track result
let fail x =
Failure x
// apply either a success function or failure function
let either successFunc failureFunc twoTrackInput =
match twoTrackInput with
| Success s -> successFunc s
| Failure f -> failureFunc f
// convert a switch function into a two-track function
let bind f =
either f fail
// pipe a two-track value into a switch function
let (>>=) x f =
bind f x
// compose two switches into another switch
let (>=>) s1 s2 =
s1 >> bind s2
// convert a one-track function into a switch
let switch f =
f >> succeed
// convert a one-track function into a two-track function
let map f =
either (f >> succeed) fail
// convert a dead-end function into a one-track function
let tee f x =
f x; x
// convert a one-track function into a switch with exception handling
let tryCatch f exnHandler x =
try
f x |> succeed
with
| ex -> exnHandler ex |> fail
// convert two one-track functions into a two-track function
let doubleMap successFunc failureFunc =
either (successFunc >> succeed) (failureFunc >> fail)
// add two switches in parallel
let plus addSuccess addFailure switch1 switch2 x =
1081
And a cargo of apples will transform into bananas when it goes through the tunnel called
function2 .
This magical railway has an important rule, namely that you can only connect tracks which
carry the same type of cargo. In this case we can connect function1 to function2
because the cargo coming out of function1 (apples) is the same as the cargo going into
function2 (also apples).
Of course, it is not always true that the tracks carry the same cargo, and a mismatch in the
kind of cargo will cause an error.
But you'll notice that in this discussion so far, we haven't mentioned the cargo once! Instead,
we have spent all our time talking about one-track vs. two track functions.
1082
Of course, it goes without saying that the cargo must match up. But I hope you can see that
it is the shape of the track that is really the important thing, not the cargo that is carried.
It takes a function parameter 'a -> 'b and a value Result<'a,'c> and returns a value
Result<'b,'c> .
We don't know anything about the types 'a , 'b , and 'c . The only things we know are
that:
The same type 'a shows up in both the function parameter and the Success case of
the first Result .
The same type 'b shows up in both the function parameter and the Success case of
the second Result .
The same type 'c shows up in the Failure cases of both the first and second
Result s, but doesn't show up in the function parameter at all.
1083
pass it to the function parameter. And then the Success case of the Result<'b,'c> return
value must be constructed from the result of the function.
Finally, the same logic applies to 'c . We are forced to get the Failure value from the
Result<'a,'c> input parameter and use it to construct the Failure case of the
Result<'a,'c> return value.
In other words, there is basically only one way to implement the map function! The type
signature is so generic that we have no choice.
On the other hand, imagine that the map function had been very specific about the types it
needed, like this:
val map : (int -> int) -> (Result<int,int> -> Result<int,int>)
In this case, we can come up a huge number of different implementations. To list a few:
We could have swapped the success and failure tracks.
We could have added a random number to the success track.
We could have ignored the function parameter altogether, and returned zero on both the
success and failure tracks.
All of these implementations are "buggy" in the sense that they don't do what we expect. But
they are all only possible because we know in advance that the type is int , and therefore
we can manipulate the values in ways we are not supposed to. The less we know about the
types, the less likely we are to make a mistake.
1084
is best?
Of course, there is never one "right way" for all scenarios, but nevertheless, as promised,
here are some guidelines that can be used as the basis of a reliable and repeatable recipe.
Guidelines
Use double-track railway as your underlying model for dataflow situations.
Create a function for each step in the use case. The function for each step can in turn
be built from smaller functions (e.g. the validation functions).
Use standard composition ( >> ) to connect the functions.
If you need to insert a switch into the flow, use bind .
If you need to insert a single-track function into the flow, use map .
If you need to insert other types of functions into the flow, create an appropriate adapter
block and use it.
These guidelines may result in code that is not particularly concise or elegant, but on the
other hand, you will be using a consistent model, and it should be understandable to other
people when it needs to be maintained.
So with these guidelines, here are the main bits of the implementation so far. Note especially
the use of >> everywhere in the final usecase function.
1085
open RailwayCombinatorModule
let (&&&) v1 v2 =
let addSuccess r1 r2 = r1 // return first
let addFailure s1 s2 = s1 + "; " + s2 // concat
plus addSuccess addFailure v1 v2
let combinedValidation =
validate1
&&& validate2
&&& validate3
let canonicalizeEmail input =
{ input with email = input.email.Trim().ToLower() }
let updateDatabase input =
() // dummy dead-end function for now
// new function to handle exceptions
let updateDatebaseStep =
tryCatch (tee updateDatabase) (fun ex -> ex.Message)
let usecase =
combinedValidation
>> map canonicalizeEmail
>> bind updateDatebaseStep
>> log
One final suggestion. If you are working with a team of non-experts, unfamiliar operator
symbols will put people off. So here some extra guidelines with respect to operators:
Don't use any "strange" operators other than >> and |> .
In particular, that means you should not use operators like >>= or >=> unless
everyone is aware of them.
An exception can be made if you define the operator at the top of the module or function
where it is used. For example, the &&& operator could be defined at the top of the
validation module and then used later in that same module.
Further reading
If you like this "railway oriented" approach, you can also see it applied to FizzBuzz.
I also have some slides and video that show how take this approach further. (At some
point I will turn these into a proper blog post)
I presented on this topic at NDC Oslo 2014 (click image to view video)
1086
1087
I have defined a function fizzBuzz that, given an integer i , uses match with when
clauses to do the various tests, and then prints the appropriate value.
1088
Simple and straightforward, and fine for a quick hack, but there are a number of problems
with this implementation.
First, note that we had to have a special case for "fifteen". We couldn't just reuse the code
from the "three" and "five" cases. And this means that if we want to add another case, such
as "seven", we also need to add special cases for all the combinations as well (that is "21",
"35" and "105"). And of course, adding more numbers would lead to a combinatorial
explosion of cases.
Second, the order of matching is important. If the "fifteen" case had come last in the list of
patterns, the code would have run correctly, but not actually met the requirements. And
again, if we need to add new cases, we must always remember to put the largest ones first
to ensure correctness. This is just the kind of thing that causes subtle bugs.
Let's try another implementation, where we reuse the code for the "three" and "five" case,
and eliminate the need for a "fifteen" case altogether:
module FizzBuzz_IfPrime =
let fizzBuzz i =
let mutable printed = false
if i % 3 = 0 then
printed <- true
printf "Fizz"
if i % 5 = 0 then
printed <- true
printf "Buzz"
if not printed then
printf "%i" i
printf "; "
// do the fizzbuzz
[1..100] |> List.iter fizzBuzz
In this implementation, the printed value for "15" will be correct, because both the "3" and "5"
cases will be used. And also we don't have to worry about order -- as much, anyway.
But -- these branches are no longer independent, so we have to track whether any branch
has been used at all, so that we can handle the default case. And that has led to the mutable
variable. Mutables are a code smell in F#, so this implementation is not ideal.
However, this version does have the advantage that it can be easily refactored to support
multiple factors, not just 3 and 5.
1089
Below is a version that does just this. We pass in a list of "rules" to fizzBuzz . Each rule
consists of a factor and a corresponding label to print out. The fizzBuzz function then just
iterates through these rules, processing each in turn.
module FizzBuzz_UsingFactorRules =
let fizzBuzz rules i =
let mutable printed = false
for factor,label in rules do
if i % factor = 0 then
printed <- true
printf "%s" label
if not printed then
printf "%i" i
printf "; "
// do the fizzbuzz
let rules = [ (3,"Fizz"); (5,"Buzz") ]
[1..100] |> List.iter (fizzBuzz rules)
If we want additional numbers to be processed, we just add them to the list of rules, like this:
module FizzBuzz_UsingFactorRules =
// existing code as above
let rules2 = [ (3,"Fizz"); (5,"Buzz"); (7,"Baz") ]
[1..105] |> List.iter (fizzBuzz rules2)
To sum up, we have created a very imperative implementation that would be almost the
same in C#. It's flexible, but the mutable variable is a bit of a code smell. Is there another
way?
1090
As an additional requirement, we want the pipeline to have no side effects. This means that
the intermediate functions must not print anything. They must instead pass any generated
labels down the pipe to the end, and only at that point print the results.
anything.
One way would be to use an empty string (or, horrors, a null string), but let's be good and
use a string option .
So, here's the final data type that we will be using:
type Data = {i:int; label:string option}
Pipeline version 1
With this data structure, we can define how handleThreeCase and handleFiveCase will work.
First, test the input int i to see if it is divisible by the factor.
1091
If it is divisible, look at the label -- if it is None , then replace it with Some "Fizz" or
Some "Buzz" .
If the label already has a value, then append "Buzz" (or whatever) to it.
If the input is not divisible by the factor, just pass on the data unchanged.
Given this design, here's the implementation. It's a generic function that I will call carbonate
(after raganwald) because it works with both "Fizz" and "Buzz":
let carbonate factor label data =
let {i=i; label=labelSoFar} = data
if i % factor = 0 then
// pass on a new data record
let newLabel =
match labelSoFar with
| Some s -> s + label
| None -> label
{data with label=Some newLabel}
else
// pass on the unchanged data
data
Note that we have to create an initial record using {i=i; label=None} for passing into the
first function ( carbonate 3 "Fizz" ).
1092
Pipeline version 2
Creating a new record type can be useful as a form of documentation, but in a case like this,
it would probably be more idiomatic to just use a tuple rather than creating a special data
structure.
So here's a modified implementation that uses tuples.
1093
module FizzBuzz_Pipeline_WithTuple =
// type Data = int * string option
let carbonate factor label data =
let (i,labelSoFar) = data
if i % factor = 0 then
// pass on a new data record
let newLabel =
labelSoFar
|> Option.map (fun s -> s + label)
|> defaultArg <| label
(i,Some newLabel)
else
// pass on the unchanged data
data
let labelOrDefault data =
let (i,labelSoFar) = data
labelSoFar
|> defaultArg <| sprintf "%i" i
let fizzBuzz i =
(i,None) // use tuple instead of record
|> carbonate 3 "Fizz"
|> carbonate 5 "Buzz"
|> labelOrDefault // convert to string
|> printf "%s; " // print
[1..100] |> List.iter fizzBuzz
1094
// before
let newLabel =
match labelSoFar with
| Some s -> s + label
| None -> label
// after
let newLabel =
labelSoFar
|> Option.map (fun s -> s + label)
|> defaultArg <| label
and in labelOrDefault :
// before
match labelSoFar with
| Some s -> s
| None -> sprintf "%i" i
// after
labelSoFar
|> defaultArg <| sprintf "%i" i
You might be wondering about the strange looking |> defaultArg <| idiom.
I am using it because the option is the first parameter to defaultArg , not the second, so a
normal partial application won't work. But "bi-directional" piping does work, hence the
strange looking code.
Here's what I mean:
// OK - normal usage
defaultArg myOption defaultValue
// ERROR: piping doesn't work
myOption |> defaultArg defaultValue
// OK - bi-directional piping does work
myOption |> defaultArg <| defaultValue
Pipeline version 3
Our carbonate function is generic for any factor, so we can easily extend the code to
support "rules" just as in the earlier imperative version.
1095
But one issue seems to be that we have hard-coded the "3" and "5" cases into the pipeline,
like this:
|> carbonate 3 "Fizz"
|> carbonate 5 "Buzz"
Each rule is mapped into a function. And then the list of functions is combined into one
single function using >> .
Putting it all together, we have this final implementation:
1096
module FizzBuzz_Pipeline_WithRules =
let carbonate factor label data =
let (i,labelSoFar) = data
if i % factor = 0 then
// pass on a new data record
let newLabel =
labelSoFar
|> Option.map (fun s -> s + label)
|> defaultArg <| label
(i,Some newLabel)
else
// pass on the unchanged data
data
let labelOrDefault data =
let (i,labelSoFar) = data
labelSoFar
|> defaultArg <| sprintf "%i" i
let fizzBuzz rules i =
// create a single function from all the rules
let allRules =
rules
|> List.map (fun (factor,label) -> carbonate factor label)
|> List.reduce (>>)
(i,None)
|> allRules
|> labelOrDefault // convert to string
|> printf "%s; " // print
// test
let rules = [ (3,"Fizz"); (5,"Buzz"); (7,"Baz") ]
[1..105] |> List.iter (fizzBuzz rules)
Comparing this "pipeline" version with the previous imperative version, the design is much
more functional. There are no mutables and there are no side-effects anywhere (except in
the final printf statement).
There is a subtle bug in the use of List.reduce , however. Can you see what it is?** For a
discussion of the problem and the fix, see the postscript at the bottom of this page.
** Hint: try an empty list of rules.
1097
The pipeline version is a perfectly adequate functional implementation of FizzBuzz, but for
fun, let's see if we can use the "two-track" design described in the railway oriented
programming post.
As a quick reminder, in "railway oriented programming" (a.k.a the "Either" monad), we define
a union type with two cases: "Success" and "Failure", each representing a different "track".
We then connect a set of "two-track" functions together to make the railway.
Most of the functions we actually use are what I called "switch" or "points" functions, with a
one track input, but a two-track output, one for the Success case, and one for the Failure
case. These switch functions are converted into two-track functions using a glue function
called "bind".
Here is a module containing definitions of the functions we will need.
module RailwayCombinatorModule =
let (|Success|Failure|) =
function
| Choice1Of2 s -> Success s
| Choice2Of2 f -> Failure f
/// convert a single value into a two-track result
let succeed x = Choice1Of2 x
/// convert a single value into a two-track result
let fail x = Choice2Of2 x
// appy either a success function or failure function
let either successFunc failureFunc twoTrackInput =
match twoTrackInput with
| Success s -> successFunc s
| Failure f -> failureFunc f
// convert a switch function into a two-track function
let bind f =
either f fail
I am using the Choice type here, which is built into the F# core library. But I have created
some helpers to make it look like the Success/Failure type: an active pattern and two
constructors.
Now, how will we adapt FizzBuzz to this?
Let's start by doing the obvious thing: defining "carbonation" as success, and an unmatched
integer as a failure.
In other words, the Success track contains the labels, and the Failure track contains the ints.
1098
This implementation is similar to the one used in the pipeline design discussed above, but it
is cleaner because the input is just an int, not a record or a tuple.
Next, we need to connect the components together. The logic will be:
if the int is already carbonated, ignore it
if the int is not carbonated, connect it to the input of the next switch function
Here is the implementation:
let connect f =
function
| Success x -> succeed x
| Failure i -> f i
Another way of writing this is to use the either function we defined in the library module:
let connect' f =
either succeed f
Make sure you understand that both of these implementations do exactly the same thing!
Next, we can create our "two-track" pipeline, like this:
let fizzBuzz =
carbonate 15 "FizzBuzz" // need the 15-FizzBuzz rule because of short-circuit
>> connect (carbonate 3 "Fizz")
>> connect (carbonate 5 "Buzz")
>> either (printf "%s; ") (printf "%i; ")
This is superficially similar to the "one-track" pipeline, but actually uses a different technique.
The switches are connected together through composition ( >> ) rather than piping ( |> ).
As a result, the fizzBuzz function does not have an int parameter -- we are defining a
function by combining other functions. There is no data anywhere.
A few other things have changed as well:
1099
We have had to reintroduce the explicit test for the "15" case. This is because we have
only two tracks (success or failure). There is no "half-finished track" that allows the "5"
case to add to the output of the "3" case.
The labelOrDefault function from the previous example has been replaced with
either . In the Success case, a string is printed. In the Failure case, an int is printed.
Carbonation as failure?
We defined carbonation as "success"in the example above -- it seems the natural thing to
do, surely. But if you recall, in the railway oriented programming model, "success" means
that data should be passed on to the next function, while "failure" means to bypass all the
intermediate functions and go straight to the end.
For FizzBuzz, the "bypass all the intermediate functions" track is the track with the
carbonated labels, while the "pass on to next function" track is the one with integers.
1100
So we should really reverse the tracks: "Failure" now means carbonation, while "Success"
means no carbonation.
By doing this, we also get to reuse the pre-defined bind function, rather than having to
write our own connect function.
Here's the code with the tracks switched around:
module FizzBuzz_RailwayOriented_CarbonationIsFailure =
open RailwayCombinatorModule
// carbonate a value
let carbonate factor label i =
if i % factor = 0 then
fail label
else
succeed i
let fizzBuzz =
carbonate 15 "FizzBuzz"
>> bind (carbonate 3 "Fizz")
>> bind (carbonate 5 "Buzz")
>> either (printf "%i; ") (printf "%s; ")
// test
[1..100] |> List.iter fizzBuzz
1101
let (|Uncarbonated|Carbonated|) =
function
| Choice1Of2 u -> Uncarbonated u
| Choice2Of2 c -> Carbonated c
/// convert a single value into a two-track result
let uncarbonated x = Choice1Of2 x
let carbonated x = Choice2Of2 x
If you are doing domain driven design, it is good practice to write code that uses the
appropriate Ubiquitous Language rather than language that is not applicable.
In this case, if FizzBuzz was our domain, then our functions could now use the domainfriendly terminology of carbonated and uncarbonated rather than "success" or "failure".
let carbonate factor label i =
if i % factor = 0 then
carbonated label
else
uncarbonated i
let connect f =
function
| Uncarbonated i -> f i
| Carbonated x -> carbonated x
Note that, as before, the connect function can be rewritten using either (or we can just
use the predefined bind as before):
let connect' f =
either f carbonated
1102
module FizzBuzz_RailwayOriented_UsingCustomChoice =
open RailwayCombinatorModule
let (|Uncarbonated|Carbonated|) =
function
| Choice1Of2 u -> Uncarbonated u
| Choice2Of2 c -> Carbonated c
/// convert a single value into a two-track result
let uncarbonated x = Choice1Of2 x
let carbonated x = Choice2Of2 x
// carbonate a value
let carbonate factor label i =
if i % factor = 0 then
carbonated label
else
uncarbonated i
let connect f =
function
| Uncarbonated i -> f i
| Carbonated x -> carbonated x
let connect' f =
either f carbonated
let fizzBuzz =
carbonate 15 "FizzBuzz"
>> connect (carbonate 3 "Fizz")
>> connect (carbonate 5 "Buzz")
>> either (printf "%i; ") (printf "%s; ")
// test
[1..100] |> List.iter fizzBuzz
Adding rules
There are some problems with the version we have so far:
The "15" test is ugly. Can we get rid of it and reuse the "3" and "5" cases?
The "3" and "5" cases are hard-coded. Can we make this more dynamic?
As it happens, we can kill two birds with one stone and address both of these issues at
once.
1103
Instead of combining all the "switch" functions in series, we can "add" them together in
parallel. In the railway oriented programming post, we used this technique for combining
validation functions. For FizzBuzz we are going to use it for doing all the factors at once.
The trick is to define a "append" or "concat" function for combining two functions. Once we
can add two functions this way, we can continue and add as many as we like.
So given that we have two carbonation functions, how do we concat them?
Well, here are the possible cases:
If they both have carbonated outputs, we concatenate the labels into a new carbonated
label.
If one has a carbonated output and the other doesn't, then we use the carbonated one.
If neither has a carbonated output, then we use either uncarbonated one (they will be
the same).
Here's the code:
// concat two carbonation functions
let (<+>) switch1 switch2 x =
match (switch1 x),(switch2 x) with
| Carbonated s1,Carbonated s2 -> carbonated (s1 + s2)
| Uncarbonated f1,Carbonated s2 -> carbonated s2
| Carbonated s1,Uncarbonated f2 -> carbonated s1
| Uncarbonated f1,Uncarbonated f2 -> uncarbonated f1
As an aside, notice that this code is almost like math, with uncarbonated playing the role of
"zero", like this:
something + something = combined somethings
zero + something = something
something + zero = something
zero + zero = zero
This is not a coincidence! We will see this kind of thing pop up over and over in functional
code. I'll be talking about this in a future post.
Anyway, with this "concat" function in place, we can rewrite the main fizzBuzz like this:
let fizzBuzz =
let carbonateAll =
carbonate 3 "Fizz" <+> carbonate 5 "Buzz"
carbonateAll
>> either (printf "%i; ") (printf "%s; ")
1104
The two carbonate functions are added and then passed to either as before.
Here is the complete code:
module FizzBuzz_RailwayOriented_UsingAppend =
open RailwayCombinatorModule
let (|Uncarbonated|Carbonated|) =
function
| Choice1Of2 u -> Uncarbonated u
| Choice2Of2 c -> Carbonated c
/// convert a single value into a two-track result
let uncarbonated x = Choice1Of2 x
let carbonated x = Choice2Of2 x
// concat two carbonation functions
let (<+>) switch1 switch2 x =
match (switch1 x),(switch2 x) with
| Carbonated s1,Carbonated s2 -> carbonated (s1 + s2)
| Uncarbonated f1,Carbonated s2 -> carbonated s2
| Carbonated s1,Uncarbonated f2 -> carbonated s1
| Uncarbonated f1,Uncarbonated f2 -> uncarbonated f1
// carbonate a value
let carbonate factor label i =
if i % factor = 0 then
carbonated label
else
uncarbonated i
let fizzBuzz =
let carbonateAll =
carbonate 3 "Fizz" <+> carbonate 5 "Buzz"
carbonateAll
>> either (printf "%i; ") (printf "%s; ")
// test
[1..100] |> List.iter fizzBuzz
With this addition logic available, we can easily refactor the code to use rules. Just as with
the earlier "pipeline" implementation, we can use reduce to add all the rules together at
once.
Here's the version with rules:
1105
module FizzBuzz_RailwayOriented_UsingAddition =
// code as above
let fizzBuzzPrimes rules =
let carbonateAll =
rules
|> List.map (fun (factor,label) -> carbonate factor label)
|> List.reduce (<+>)
carbonateAll
>> either (printf "%i; ") (printf "%s; ")
// test
let rules = [ (3,"Fizz"); (5,"Buzz"); (7,"Baz") ]
[1..105] |> List.iter (fizzBuzzPrimes rules)
Summary
In this post, we've seen three different implementations:
An imperative version that used mutable values and mixed side-effects with logic.
A "pipeline" version that passed a data structure through a series of functions.
A "railway oriented" version that had two separate tracks, and used "addition" to
combine functions in parallel.
In my opinion, the imperative version is the worst design. Even though it was easy to hack
out quickly, it has a number of problems that make it brittle and error-prone.
Of the two functional versions, I think the "railway oriented" version is cleaner, for this
problem at least.
By using the Choice type rather than a tuple or special record, we made the code more
elegant thoughout. You can see the difference if you compare the pipeline version of
carbonate with the railway oriented version of carbonate .
In other situations, of course, the railway oriented approach might not work, and the pipeline
approach might be better. I hope this post has given some useful insight into both.
If you're a FizzBuzz fan, check out the Functional Reactive Programming page, which has
yet another variant of the problem.
1106
Be careful with List.reduce -- it will fail with empty lists. So if you have an empty rule set,
the code will throw a System.ArgumentException .
In the pipeline case, you can see this by adding the following snippet to the module:
module FizzBuzz_Pipeline_WithRules =
// code as before
// bug
let emptyRules = []
[1..105] |> List.iter (fizzBuzz emptyRules)
1107
1108
1109
1110
Ok! We're done! If you can understand these equations, then you have all the math you
need to understand monoids.
1111
What about the equals function? It combines two integers but returns a boolean, not an
integer, so the answer is no.
Enough of integers! What other kinds of things can we think of?
Floats are similar to integers, but unlike integers, using division with floats does result in
another float, so the division operation fits the pattern.
How about booleans? They can be combined using operators such as AND, OR and so on.
Does aBool AND aBool result in another bool? Yes! And OR too fits the pattern.
Strings next. How can they be combined? One way is string concatenation, which returns
another string, which is what we want. But something like the equality operation doesn't fit,
because it returns a boolean.
Finally, let's consider lists. As for strings, the obvious way to combine them is with list
concatenation, which returns another list and fits the pattern.
We can continue on like this for all sorts of objects and combining operations, but you should
see how it works now.
You might ask: why is it so important that the operation return another thing of the same
type? The answer is that you can chain together multiple objects using the operation.
For example, because 1 + 2 is another integer, you can add 3 to it. And then because 1 +
2 + 3 is an integer as well, you can keep going and add say, 4, to the result. In other words,
it is only because integer addition fits the pattern that you can write a sequence of additions
like this: 1 + 2 + 3 + 4 . You couldn't write 1 = 2 = 3 = 4 in the same way, because
integer equality doesn't fit the pattern.
And of course, the chain of combined items can be as long as we like. In other words, this
pattern allows us to extend a pairwise operation into an operation that works on lists.
Mathematicians call the requirement that "the result is another one of these things" the
closure requirement.
combining in? Should we combine 1 and 2 first, then combine the result with 3? Or should
we combine 2 and 3 first and then combine 1 with that result? Does it make a difference?
1112
That's where this second equation is useful. It says that, for addition, the order of
combination doesn't matter. You get the same result either way.
So for a chain of four items like this: 1 + 2 + 3 + 4 , we could start working from the left
hand side: ((1+2) + 3) + 4 or from the right hand side: 1 + (2 + (3+4)) or even do it in
two parts and then combine them like this: (1+2) + (3+4) .
Let's see if this pattern applies to the examples we've already looked at.
Again, let's start with other ways of combining integers.
We'll start with multiplication again. Does 1 * (2 * 3) give the same result as (1 * 2) *
3 ? Yes. Just as with addition, the order doesn't matter.
Now let's try subtraction. Does 1 - (2 - 3) give the same result as (1 - 2) - 3 ? No. For
subtraction, the order does matter.
What about division? Does 12 / (2 / 3) give the same result as (12 / 2) / 3 ? No. For
division also, the order matters.
But the max function does work. max( max(12,2), 3) gives the same result as max(12,
max(2,3) .
What about strings and lists? Does concatenation meet the requirement? What do you
think?
Here's a question... Can we come up with an operation for strings that is order dependent?
Well, how about a function like "subtractChars" which removes all characters in the right
string from the left string. So subtractChars("abc","ab") is just "c" . subtractChars is
indeed order dependent, as you can see with a simple example: subtractChars("abc",
subtractChars("abc","abc")) is not the same string as
subtractChars(subtractChars("abc","abc"),"abc") .
Mathematicians call the requirement that "the order doesn't matter" the associativity
requirement.
Important Note: When I say the "order of combining", I am talking about the order in which
you do the pairwise combining steps -- combining one pair, and then combining the result
with the next item.
But it is critical that the overall sequence of the items be left unchanged. This is because for
certain operations, if you change the sequencing of the items, then you get a completely
different result! 1 - 2 does not mean the same as 2 - 1 and 2 / 3 does not mean the
same as 3 / 2 .
1113
Of course, in many common cases, the sequence order doesn't matter. After all, 1+2 is the
same as 2+1 . In this case, the operation is said to be commutative.
Finally, for list concatenation, the "zero" is just the empty list.
[] @ [1;2;3] = [1;2;3]
[1;2;3] @ [] = [1;2;3]
You can see that the "zero" value depends very much on the operation, not just on the set of
things. The zero for integer addition is different from the "zero" for integer multiplication,
which is different again from the from "zero" for Max .
Mathematicians call the "zero" the identity element.
1114
But now we have something much more abstract, a set of generalized requirements that can
apply to all sorts of things:
You start with a bunch of things, and some way of combining them two at a time.
Rule 1 (Closure): The result of combining two things is always another one of the
things.
Rule 2 (Associativity): When combining more than two things, which pairwise
combination you do first doesn't matter.
Rule 3 (Identity element): There is a special thing called "zero" such that when you
combine any thing with "zero" you get the original thing back.
With these rules in place, we can come back to the definition of a monoid. A "monoid" is just
a system that obeys all three rules. Simple!
As I said at the beginning, don't let the mathematical background put you off. If programmers
had named this pattern, it probably would been called something like "the combinable
pattern" rather than "monoid". But that's life. The terminology is already well-established, so
we have to use it.
Note there are two parts to the definition of a monoid -- the things plus the associated
operation. A monoid is not just "a bunch of things", but "a bunch of things" and "some way of
combining them". So, for example, "the integers" is not a monoid, but "the integers under
addition" is a monoid.
Semigroups
In certain cases, you have a system that only follows the first two rules, and there is no
candidate for a "zero" value.
For example, if your domain consists only of strictly positive numbers, then under addition
they are closed and associative, but there is no positive number that can be "zero".
Another example might be the intersection of finite lists. It is closed and associative, but
there is no (finite) list that when intersected with any other finite list, leaves it untouched.
1115
This kind of system still quite useful, and is called a "semigroup" by mathematicians, rather
than a monoid. Luckily, there is a trick that can convert any semigroup into a monoid (which
I'll describe later).
A table of classifications
Let's put all our examples into a table, so you can see them all together.
Things
Operation
Closed?
Associative?
Identity?
Classification
Int32
Addition
Yes
Yes
Monoid
Int32
Multiplication
Yes
Yes
Monoid
Int32
Subtraction
Yes
No
Other
Int32
Max
Yes
Yes
Int32.MinValue
Monoid
Int32
Equality
No
Other
Int32
Less than
No
Other
Float
Multiplication
Yes
No (See note
1)
Other
Float
Division
Yes
(See
note 2)
No
Other
Positive
Numbers
Addition
Yes
Yes
No identity
Semigroup
Positive
Numbers
Multiplication
Yes
Yes
Monoid
Boolean
AND
Yes
Yes
true
Monoid
Boolean
OR
Yes
Yes
false
Monoid
String
Concatenation
Yes
Yes
Monoid
String
Equality
No
String
"subtractChars"
Yes
No
Other
List
Concatenation
Yes
Yes
Empty list []
Monoid
List
Intersection
Yes
Yes
No identity
Semigroup
Other
There are many other kinds of things you can add to this list; polynomials, matrices,
probability distributions, and so on. This post won't discuss them, but once you get the idea
of monoids, you will see that the concept can be applied to all sorts of things.
1116
[Note 1] As Doug points out in the comments, floats are not associative. Replace 'float' with
'real number' to get associativity.
[Note 2] Mathematical real numbers are not closed under division, because you cannot
divide by zero and get another real number. However, with IEEE floating point numbers you
can divide by zero and get a valid value. So floats are indeed closed under division! Here's a
demonstration:
let x = 1.0/0.0 // infinity
let y = x * 2.0 // two times infinity
let z = 2.0 / x // two divided by infinity
Using reduce
1 + 2 + 3 + 4
1 * 2 * 3 * 4
You can see that reduce can be thought of as inserting the specified operation between
each element of the list.
Note that in the last example, the input to reduce is a list of lists, and the output is a single
list. Make sure you understand why this is.
1117
If the pairwise combinations can be done in any order, that opens up some interesting
implementation techniques, such as:
Divide and conquer algorithms
Parallelization
Incrementalism
These are deep topics, but let's have a quick look!
Divide and conquer algorithms
Consider the task of summing the first 8 integers; how could we implement this?
One way would be a crude step-by-step sum, as follows:
let sumUpTo2 = 1 + 2
let sumUpTo3 = sumUpTo2 + 3
let sumUpTo4 = sumUpTo3 + 4
// etc
let result = sumUpTo7 + 8
But because the sums can be done in any order, we could also implement the requirement
by splitting the sum into two halves, like this
let sum1To4 = 1 + 2 + 3 + 4
let sum5To8 = 5 + 6 + 7 + 8
let result = sum1To4 + sum5To8
and then we can recursively split the sums into sub-sums in the same way until we get down
to the basic pairwise operation:
let sum1To2 = 1 + 2
let sum3To4 = 3 + 4
let sum1To4 = sum1To2 + sum3To4
This "divide and conquer" approach may seem like overkill for something like a simple sum,
but we'll see in a future post that, in conjunction with a map , it is the basis for some well
known aggregation algorithms.
Parallelization
Once we have a divide and conquer strategy, it can be easily converted into a parallel
algorithm.
For example, to sum the first 8 integers on a four-core CPU, we might do something like this:
1118
Core 1
Core 2
Core 3
Core 4
Step
1
sum12 = 1 + 2
sum34 = 3 + 4
sum56 = 5 +
6
sum78 = 7 +
8
Step
2
sum1234 = sum12 +
sum34
sum5678 = sum56 +
sum78
(idle)
(idle)
Step
3
sum1234 + sum5678
(idle)
(idle)
(idle)
There are still seven calculations that need to be done, but because we are doing it parallel,
we can do them all in three steps.
Again, this might seem like a trivial example, but big data systems such as Hadoop are all
about aggregating large amounts of data, and if the aggregation operation is a monoid, then
you can, in theory, easily scale these aggregations by using multiple machines*.
*In practice, of course, the devil is in the details, and real-world systems don't work exactly
this way.
Incrementalism
Even if you do not need parallelism, a nice property of monoids is that they support
incremental calculations.
For example, let's say you have asked me to calculate the sum of one to five. Then of
course I give you back the answer fifteen.
But now you say that you have changed your mind, and you want the sum of one to six
instead. Do I have to add up all the numbers again, starting from scratch? No, I can use the
previous sum, and just add six to it incrementally. This is possible because integer addition
is a monoid.
That is, when faced with a sum like 1 + 2 + 3 + 4 + 5 + 6 , I can group the numbers any
way I like. In particular, I can make an incremental sum like this: (1 + 2 + 3 + 4 + 5) + 6 ,
which then reduces to 15 + 6 .
In this case, recalculating the entire sum from scratch might not be a big deal, but consider a
real-world example like web analytics, counting the number of visitors over the last 30 days,
say. A naive implementation might be to calculate the numbers by parsing the logs of the last
30 days data. A more efficient approach would be to recognize that the previous 29 days
have not changed, and to only process the incremental changes for one day. As a result, the
parsing effort is greatly reduced.
Similarly, if you had a word count of a 100 page book, and you added another page, you
shouldn't need to parse all 101 pages again. You just need to count the words on the last
page and add that to the previous total.*
1119
Technically, these are scary sounding monoid homomorphisms. I will explain what this is
in the next post.
Using a "zero" can result in counter-intuitive results sometimes. For example, what is the
product of an empty list of integers?
The answer is 1 , not 0 as you might expect! Here's the code to prove it:
[1..4] |> List.fold (*) 1 // result is 24
[] |> List.fold (*) 1 // result is 1
1120
So when you are designing code, and you start using terms like "sum", "product",
"composition", or "concatenation", these are clues that you are dealing with a monoid.
Next steps
Now that we understand what a monoid is, let's see how they can be used in practice.
In the next post in this series, we'll look at how you might write real code that implements the
monoid "pattern".
1121
Monoids in practice
Monoids in practice
In the previous post, we looked at the definition of a monoid. In this post, we'll see how to
implement some monoids.
First, let's revisit the definition:
You start with a bunch of things, and some way of combining them two at a time.
Rule 1 (Closure): The result of combining two things is always another one of the
things.
Rule 2 (Associativity): When combining more than two things, which pairwise
combination you do first doesn't matter.
Rule 3 (Identity element): There is a special thing called "zero" such that when you
combine any thing with "zero" you get the original thing back.
For example, if strings are the things, and string concatenation is the operation, then we
have a monoid. Here's some code that demonstrates this:
let s1 = "hello"
let s2 = " world!"
// closure
let sum = s1 + s2 // sum is a string
// associativity
let s3 = "x"
let s4a = (s1+s2) + s3
let s4b = s1 + (s2+s3)
assert (s4a = s4b)
// an empty string is the identity
assert (s1 + "" = s1)
assert ("" + s1 = s1)
1122
Monoids in practice
And then perhaps we might want to find the total for an order, that is, we want to sum the
Total field for a list of lines.
The standard imperative approach would be to create a local total variable, and then loop
through the lines, summing as we go, like this:
let calculateOrderTotal lines =
let mutable total = 0.0
for line in lines do
total <- total + line.Total
total
But of course, being an experienced functional programmer, you would sneer at this, and
use fold in calculateOrderTotal instead, like this:
1123
Monoids in practice
module OrdersUsingFold =
type OrderLine = {
ProductCode: string
Qty: int
Total: float
}
let calculateOrderTotal lines =
let accumulateTotal total line =
total + line.Total
lines
|> List.fold accumulateTotal 0.0
let orderLines = [
{ProductCode="AAA"; Qty=2; Total=19.98}
{ProductCode="BBB"; Qty=1; Total=1.99}
{ProductCode="CCC"; Qty=3; Total=3.99}
]
orderLines
|> calculateOrderTotal
|> printfn "Total is %g"
But this is no good, because we forgot a key aspect of monoids. The addition must return a
value of the same type!
If we look at the signature for the addLine function...
addLine : OrderLine -> OrderLine -> float
...we can see that the return type is float not OrderLine .
What we need to do is return a whole other OrderLine . Here's a correct implementation:
1124
Monoids in practice
Now the signature is correct: addLine : OrderLine -> OrderLine -> OrderLine .
Note that because we have to return the entire structure we have to specify something for
the ProductCode and Qty as well, not just the total. The Qty is easy, we can just do a
sum. For the ProductCode , I decided to use the string "TOTAL", because we don't have a
real product code we can use.
Let's give this a little test:
// utility method to print an OrderLine
let printLine {ProductCode=p; Qty=q;Total=t} =
printfn "%-10s %5i %6g" p q t
let orderLine1 = {ProductCode="AAA"; Qty=2; Total=19.98}
let orderLine2 = {ProductCode="BBB"; Qty=1; Total=1.99}
//add two lines to make a third
let orderLine3 = addLine orderLine1 orderLine2
orderLine3 |> printLine // and print it
NOTE: For more on the printf formatting options used, see the post on printf here.
Now let's apply this to a list using reduce :
let orderLines = [
{ProductCode="AAA"; Qty=2; Total=19.98}
{ProductCode="BBB"; Qty=1; Total=1.99}
{ProductCode="CCC"; Qty=3; Total=3.99}
]
orderLines
|> List.reduce addLine
|> printLine
1125
Monoids in practice
TOTAL 6 25.96
At first, this might seem like extra work, and just to add up a total. But note that we now have
more information than just the total; we also have the sum of the qtys as well.
For example, we can easily reuse the printLine function to make a simple receipt printing
function that includes the total, like this:
let printReceipt lines =
lines
|> List.iter printLine
printfn "-----------------------"
lines
|> List.reduce addLine
|> printLine
orderLines
|> printReceipt
More importantly, we can now use the incremental nature of monoids to keep a running
subtotal that we update every time a new line is added.
Here's an example:
let subtotal = orderLines |> List.reduce addLine
let newLine = {ProductCode="DDD"; Qty=1; Total=29.98}
let newSubtotal = subtotal |> addLine newLine
newSubtotal |> printLine
We could even define a custom operator such as ++ so that we can add lines together
naturally as it they were numbers:
let (++) a b = addLine a b // custom operator
let newSubtotal = subtotal ++ newLine
1126
Monoids in practice
You can see that using the monoid pattern opens up a whole new way of thinking. You can
apply this "add" approach to almost any kind of object.
For example, what would a product "plus" a product look like? Or a customer "plus" a
customer? Let your imagination run wild!
1127
Monoids in practice
This does seem a bit hacky, so I wouldn't recommend this technique in general. There's
another way to get an identity that we'll be discussing later.
Now we have introduced a complication. What should we set the Price to when we
combine two lines? The average price? No price?
let addLine orderLine1 orderLine2 =
{
ProductCode = "TOTAL"
Qty = orderLine1.Qty + orderLine2.Qty
Price = 0 // or use average price?
Total = orderLine1.Total + orderLine2.Total
}
1128
Monoids in practice
type ProductLine = {
ProductCode: string
Qty: int
Price: float
LineTotal: float
}
type TotalLine = {
Qty: int
OrderTotal: float
}
type OrderLine =
| Product of ProductLine
| Total of TotalLine
This design is much nicer. We now have a special structure just for totals and we don't have
to use contortions to make the excess data fit. We can even remove the dummy "TOTAL"
product code.
Note that I named the "total" field differently in each record. Having unique field names like
this means that you don't have to always specify the type explicitly.
Unfortunately, the addition logic is more complicated now, as we have to handle every
combination of cases:
let addLine orderLine1 orderLine2 =
let totalLine =
match orderLine1,orderLine2 with
| Product p1, Product p2 ->
{Qty = p1.Qty + p2.Qty;
OrderTotal = p1.LineTotal + p2.LineTotal}
| Product p, Total t ->
{Qty = p.Qty + t.Qty;
OrderTotal = p.LineTotal + t.OrderTotal}
| Total t, Product p ->
{Qty = p.Qty + t.Qty;
OrderTotal = p.LineTotal + t.OrderTotal}
| Total t1, Total t2 ->
{Qty = t1.Qty + t2.Qty;
OrderTotal = t1.OrderTotal + t2.OrderTotal}
Total totalLine // wrap totalLine to make OrderLine
Note that we cannot just return the TotalLine value. We have to wrap in the Total case to
make a proper OrderLine . If we didn't do that, then our addLine would have the signature
OrderLine -> OrderLine -> TotalLine , which is not correct. We have to have the signature
OrderLine -> OrderLine -> OrderLine -- nothing else will do!
1129
Monoids in practice
Now that we have two cases, we need to handle both of them in the printLine function:
let printLine = function
| Product {ProductCode=p; Qty=q; Price=pr; LineTotal=t} ->
printfn "%-10s %5i @%4g each %6g" p q pr t
| Total {Qty=q; OrderTotal=t} ->
printfn "%-10s %5i %6g" "TOTAL" q t
But once we have done this, we can now use addition just as before:
let orderLine1 = Product {ProductCode="AAA"; Qty=2; Price=9.99; LineTotal=19.98}
let orderLine2 = Product {ProductCode="BBB"; Qty=1; Price=1.99; LineTotal=1.99}
let orderLine3 = addLine orderLine1 orderLine2
orderLine1 |> printLine
orderLine2 |> printLine
orderLine3 |> printLine
Identity again
Again, we haven't dealt with the identity requirement. We could try using the same trick as
before, with a blank product code, but that only works with the Product case.
To get a proper identity, we really need to introduce a third case, EmptyOrder say, to the
union type:
type ProductLine = {
ProductCode: string
Qty: int
Price: float
LineTotal: float
}
type TotalLine = {
Qty: int
OrderTotal: float
}
type OrderLine =
| Product of ProductLine
| Total of TotalLine
| EmptyOrder
With this extra case available, we rewrite the addLine function to handle it:
1130
Monoids in practice
The way you do this is by attaching two static members, + and Zero to your type, like this:
type OrderLine with
static member (+) (x,y) = addLine x y
static member Zero = EmptyOrder // a property
Once this has been done, you can use List.sum and it will work as expected.
1131
Monoids in practice
Note that for this to work you mustn't already have a method or case called Zero . If I had
used the name Zero instead of EmptyOrder for the third case it would not have worked.
Although this is a neat trick, in practice I don't think it is a good idea unless you are defining
a proper math-related type such as ComplexNumber or Vector . It's a bit too clever and nonobvious for my taste.
If you do want to use this trick, your Zero member cannot be an extension method -- it
must be defined with the type.
For example, in the code below, I'm trying to define the empty string as the "zero" for strings.
List.fold works because String.Zero is visible as an extension method in this code right
here, but List.sum fails because the extension method is not visible to it.
module StringMonoid =
// define extension method
type System.String with
static member Zero = ""
// OK.
["a";"b";"c"]
|> List.reduce (+)
|> printfn "Using reduce: %s"
// OK. String.Zero is visible as an extension method
["a";"b";"c"]
|> List.fold (+) System.String.Zero
|> printfn "Using fold: %s"
// Error. String.Zero is NOT visible to List.sum
["a";"b";"c"]
|> List.sum
|> printfn "Using sum: %s"
1132
Monoids in practice
All the fields in CustomerStats are numeric, so it is obvious how we can add two stats
together:
let add stat1 stat2 = {
Count = stat1.Count + stat2.Count;
TotalInactiveDays = stat1.TotalInactiveDays + stat2.TotalInactiveDays
TotalSpend = stat1.TotalSpend + stat2.TotalSpend
}
// define an infix version as well
let (++) a b = add a b
1133
Monoids in practice
As always, the inputs and output of the add function must be the same type. We must have
CustomerStats -> CustomerStats -> CustomerStats , not Customer -> Customer ->
CustomerStats or any other variant.
The first thing to note is that the toStats creates statistics for just one customer. We set the
count to just 1. It might seem a bit strange, but it does make sense, because if there was just
one customer in the list, that's what the aggregate stats would be.
The second thing to note is how the final aggregation is done. First we use map to convert
the source type to a type that is a monoid, and then we use reduce to aggregate all the
stats.
Hmmm.... map followed by reduce . Does that sound familiar to you?
Yes indeed, Google's famous MapReduce algorithm was inspired by this concept (although
the details are somewhat different).
Before we move on, here are some simple exercises for you to check your understanding.
What is the "zero" for CustomerStats ? Test your code by using List.fold on an empty
list.
1134
Monoids in practice
Write a simple OrderStats class and use it to aggregate the OrderLine type that we
introduced at the beginning of this post.
Monoid Homomorphisms
We've now got all the tools we need to understand something called a monoid
homomorphism.
I know what you're thinking... Ugh! Not just one, but two strange math words at once!
But I hope that the word "monoid" is not so intimidating now. And "homomorphism" is
another math word that is simpler than it sounds. It's just greek for "same shape" and it
describes a mapping or function that keeps the "shape" the same.
What does that mean in practice?
Well, we have seen that all monoids have a certain common structure. That is, even though
the underlying objects can be quite different (integers, strings, lists, CustomerStats , etc.) the
"monoidness" of them is the same. As George W. Bush once said, once you've seen one
monoid, you've seen them all.
So a monoid homomorphism is a transformation that preserves an essential "monoidness",
even if the "before" and "after" objects are quite different.
In this section, we'll look at a simple monoid homomorphism. It's the "hello world", the
"fibonacci series", of monoid homomorphisms -- word counting.
Documents as a monoid
Let's say we have a type which represents text blocks, something like this:
type Text = Text of string
And of course we can add two smaller text blocks to make a larger text block:
let addText (Text s1) (Text s2) =
Text (s1 + s2)
1135
Monoids in practice
Since you are now a expert, you will quickly recognize this as a monoid, with the zero
obviously being Text "" .
Now let's say we are writing a book (such as this one) and we want a word count to show
how much we have written.
Here's a very crude implementation, plus a test:
let wordCount (Text s) =
s.Split(' ').Length
// test
Text "Hello world"
|> wordCount
|> printfn "The word count is %i"
So we are writing away, and now we have produced three pages of text. How do we
calculate the word count for the complete document?
Well, one way is to add the separate pages together to make a complete text block, and
then apply the wordCount function to that text block. Here's a diagram:
But everytime we finish a new page, we have to add all the text together and do the word
count all over again.
No doubt you can see that there is a better way of doing this. Instead of adding all the text
together and then counting, get the word count for each page separately, and then add
these counts up, like this:
The second approach relies on the fact that integers (the counts) are themselves a monoid,
and you can add them up to get the desired result.
1136
Monoids in practice
1137
Monoids in practice
module WordCountTest =
open System
type Text = Text of string
let addText (Text s1) (Text s2) =
Text (s1 + s2)
let wordCount (Text s) =
System.Text.RegularExpressions.Regex.Matches(s,@"\S+").Count
Next, we'll create a page with 1000 words in it, and a document with 1000 pages.
module WordCountTest =
// code as above
let page() =
List.replicate 1000 "hello "
|> List.reduce (+)
|> Text
let document() =
page() |> List.replicate 1000
We'll want to time the code to see if there is any difference between the implementations.
Here's a little helper function.
module WordCountTest =
// code as above
let time f msg =
let stopwatch = Diagnostics.Stopwatch()
stopwatch.Start()
f()
stopwatch.Stop()
printfn "Time taken for %s was %ims" msg stopwatch.ElapsedMilliseconds
Ok, let's implement the first approach. We'll add all the pages together using addText and
then do a word count on the entire million word document.
1138
Monoids in practice
module WordCountTest =
// code as above
let wordCountViaAddText() =
document()
|> List.reduce addText
|> wordCount
|> printfn "The word count is %i"
time wordCountViaAddText "reduce then count"
For the second approach, we'll do wordCount on each page first, and then add all the
results together (using reduce of course).
module WordCountTest =
// code as above
let wordCountViaMap() =
document()
|> List.map wordCount
|> List.reduce (+)
|> printfn "The word count is %i"
time wordCountViaMap "map then reduce"
And in wordCountViaMap we have basically swapped these lines. We now do wordCount first
and then reduce afterwards, like this:
|> List.map wordCount
|> List.reduce (+)
Finally, let's see what difference parallelism makes. We'll use the built-in
Array.Parallel.map instead of List.map , which means we'll need to convert the list into an
array first.
1139
Monoids in practice
module WordCountTest =
// code as above
let wordCountViaParallelAddCounts() =
document()
|> List.toArray
|> Array.Parallel.map wordCount
|> Array.reduce (+)
|> printfn "The word count is %i"
time wordCountViaParallelAddCounts "parallel map then reduce"
I hope that you are following along with the implementations, and that you understand what
is going on.
We must recognize that these are crude results, not a proper performance profile. But even
so, it is very obvious that the map/reduce version is about 10 times faster that the
ViaAddText version.
This is the key to why monoid homomorphisms are important -- they enable a "divide and
conquer" strategy that is both powerful and easy to implement.
Yes, you could argue that the algorithms used are very inefficient. String concat is a terrible
way to accumulate large text blocks, and there are much better ways of doing word counts.
But even with these caveats, the fundamental point is still valid: by swapping two lines of
code, we got a huge performance increase.
And with a little bit of hashing and caching, we would also get the benefits of incremental
aggregation -- only recalculating the minimum needed as pages change.
Note that the parallel map didn't make that much difference in this case, even though it did
use all four cores. Yes, we did add some minor expense with toArray but even in the best
case, you might only get a small speed up on a multicore machine. To reiterate, what really
made the most difference was the divide and conquer strategy inherent in the map/reduce
approach.
1140
Monoids in practice
A non-monoid homomorphism
I mentioned earlier that not all mappings are necessarily monoid homomorphisms. In this
section, we'll look at an example of one that isn't.
For this example, rather than using counting words, we're going to return the most frequent
word in a text block.
Here's the basic code.
module FrequentWordTest =
open System
open System.Text.RegularExpressions
type Text = Text of string
let addText (Text s1) (Text s2) =
Text (s1 + s2)
let mostFrequentWord (Text s) =
Regex.Matches(s,@"\S+")
|> Seq.cast<Match>
|> Seq.map (fun m -> m.ToString())
|> Seq.groupBy id
|> Seq.map (fun (k,v) -> k,Seq.length v)
|> Seq.sortBy (fun (_,v) -> -v)
|> Seq.head
|> fst
The mostFrequentWord function is bit more complicated than the previous wordCount
function, so I'll take you through it step by step.
First, we use a regex to match all non-whitespace. The result of this is a MatchCollection
not a list of Match , so we have to explicitly cast it into a sequence (an IEnumerable<Match>
in C# terms).
Next we convert each Match into the matched word, using ToString() . Then we group by
the word itself, which gives us a list of pairs, where each pair is a (word,list of words) . We
then turn those pairs into (word,list count) and then sort descending (using the negated
word count).
Finally we take the first pair, and return the first part of the pair. This is the most frequent
word.
1141
Monoids in practice
Ok, let's continue, and create some pages and a document as before. This time we're not
interested in performance, so we only need a few pages. But we do want to create different
pages. We'll create one containing nothing but "hello world", another containing nothing but
"goodbye world", and a third containing "foobar". (Not a very interesting book IMHO!)
module FrequentWordTest =
// code as above
let page1() =
List.replicate 1000 "hello world "
|> List.reduce (+)
|> Text
let page2() =
List.replicate 1000 "goodbye world "
|> List.reduce (+)
|> Text
let page3() =
List.replicate 1000 "foobar "
|> List.reduce (+)
|> Text
let document() =
[page1(); page2(); page3()]
It is obvious that, with respect to the entire document, "world" is the most frequent word
overall.
So let's compare the two approaches as before. The first approach will combine all the
pages and then apply mostFrequentWord , like this.
The second approach will do mostFrequentWord separately on each page and then combine
the results, like this:
1142
Monoids in practice
Can you see what happened? The first approach was correct. But the second approach
gave a completely wrong answer!
Using add first, the most frequent word is world
Using map reduce, the most frequent word is hellogoodbyefoobar
The second approach just concatenated the most frequent words from each page. The
result is a new string that was not on any of the pages. A complete fail!
What went wrong?
Well, strings are a monoid under concatenation, so the mapping transformed a monoid
(Text) to another monoid (string).
But the mapping did not preserve the "shape". The most frequent word in a big chunk of text
cannot be derived from the most frequent words in smaller chunks of text. In other words, it
is not a proper monoid homomorphism.
1143
Monoids in practice
But for the most frequent word example, we did not get the same answer from the two
different approaches.
1144
Monoids in practice
Next steps
So far, we have only dealt with things that are proper monoids. But what if the thing you want
to work with is not a monoid? What then?
In the next post in this series, I'll give you some tips on converting almost anything into a
monoid.
We'll also fix up the mostFrequentWord example so that it is a proper monoid
homomorphism, and we'll revisit the thorny problem of zeroes, with an elegant approach for
creating them.
See you then!
Further reading
If you are interested in using monoids for data aggregation, there are lots of good
discussions in the following links:
Twitter's Algebird library
Most probabilistic data structures are monoids.
Gaussian distributions form a monoid.
Google's MapReduce Programming Model (PDF).
Monoidify! Monoids as a Design Principle for Efficient MapReduce Algorithms (PDF).
LinkedIn's Hourglass libary for Hadoop
From Stack Exchange: What use are groups, monoids, and rings in database
computations?
If you want to get a bit more technical, here is a detailed study of monoids and semigroups,
using graphics diagrams as the domain:
Monoids: Theme and Variations (PDF).
1145
Getting closure
If you recall, for a proper monoid, we need three things to be true: closure, associativity, and
identity. Each requirement can present a challenge, so we'll discuss each in turn.
We'll start with closure.
In some cases you might want to add values together, but the type of the combined value is
not the same as the type of the original values. How can you handle this?
One way is to just to map from the original type to a new type that is closed. We saw this
approach used with the Customer and CustomerStats example in the previous post. In
many cases, this is the easiest approach, because you don't have to mess with the design of
the original types.
On the other hand, sometimes you really don't want to use map , but instead want to design
your type from the beginning so that it meets the closure requirement.
Either way, whether you are designing a new type or redesigning an existing type, you can
use similar techniques to get closure.
1146
Here's an example:
type MyType = {count:int; items:int list}
let addMyType t1 t2 =
{count = t1.count + t2.count;
items = t1.items @ t2.items}
The addMyType function uses integer addition on the int field, and list concatenation on
the list field. As a result the MyType is closed using the function addMyType -- in fact, not
only is it closed, it is a monoid too. So in this case, we're done!
This is exactly the approach we took with CustomerStats in the previous post.
So here's my first tip:
DESIGN TIP: To easily create a monoidal type, make sure that each field of the
type is also a monoid.
Question to think about: when you do this, what is the "zero" of the new compound type?
But that is very unhelpful, as it does not meet the closure requirement.
One way to fix this is to force the chars into strings, which does work:
"a" + "b" -> "ab"
But that is a specific solution for chars -- is there a more generic solution that will work for
other types?
1147
Well, think for a minute what the relationship of a string to a char is. A string can be
thought of as a list or array of chars.
In other words, we could have used lists of chars instead, like this:
['a'] @ ['b'] -> ['a'; 'b'] // Lists FTW!
You can see that MChar is a wrapper around a list of chars, rather than a single char.
Now let's test it:
1148
open MonoidalChar
// add two chars and convert to string
let a = 'a' |> toMChar
let b = 'b' |> toMChar
let c = a ++ b
c |> toString |> printfn "a + b = %s"
// result: "a + b = ab"
If we want to get fancy we can use map/reduce to work on a set of chars, like this:
[' '..'z'] // get a lot of chars
|> List.filter System.Char.IsPunctuation
|> List.map toMChar
|> List.reduce addChar
|> toString
|> printfn "punctuation chars are %s"
// result: "punctuation chars are !"#%&'()*,-./:;?@[\]_"
module Validation =
type ValidationResult =
| Success
| Failure of string
let validateBadWord badWord (name:string) =
if name.Contains(badWord) then
Failure ("string contains a bad word: " + badWord)
else
Success
let validateLength maxLength name =
if String.length name > maxLength then
Failure "string is too long"
else
Success
In practice, we might perform multiple validations on a string, and we would like to return all
the results at once, added together somehow.
1149
This calls out for being a monoid! If we can add two results pairwise, then we can extend the
operation to add as many results as we like!
So then the question is, how do we combine two validation results?
let result1 = Failure "string is null or empty"
let result2 = Failure "string is too long"
result1 + result2 = ????
A naive approach would be to concatenate the strings, but that wouldn't work if we were
using format strings, or resource ids with localization, etc.
No, a better way is to convert the Failure case to use a list of strings instead of a single
string. That will make combining results simple.
Here's the same code as above, with the Failure case redefined to use a list:
module MonoidalValidation =
type ValidationResult =
| Success
| Failure of string list
// helper to convert a single string into the failure case
let fail str =
Failure [str]
let validateBadWord badWord (name:string) =
if name.Contains(badWord) then
fail ("string contains a bad word: " + badWord)
else
Success
let validateLength maxLength name =
if String.length name > maxLength then
fail "string is too long"
else
Success
You can see that the individual validations call fail with a single string, but behind the
scenes it is being stored as a list of strings, which can, in turn, be concatenated together.
With this in place, we can now create the add function.
The logic will be:
If both results are Success , then the combined result is Success
1150
And here's a more realistic example, where we have a list of validation functions that we
want to apply:
1151
let test4 =
let validationResults str =
[
validateLength 10
validateBadWord "monad"
validateBadWord "cobol"
]
|> List.map (fun validate -> validate str)
"cobol has native support for monads"
|> validationResults
|> List.reduce add
|> printfn "Result is %A"
One more thing is needed to finish up this monoid. We need a "zero" as well. What should it
be?
By definition, it is something that when combined with another result, leaves the other result
alone.
I hope you can see that by this definition, "zero" is just Success .
module MonoidalValidation =
// as above
// identity
let zero = Success
As you know, we would need to use zero if the list to reduce over is empty. So here's an
example where we don't apply any validation functions at all, giving us an empty list of
ValidationResult .
1152
let test5 =
let validationResults str =
[]
|> List.map (fun validate -> validate str)
"cobol has native support for monads"
|> validationResults
|> List.fold add zero
|> printfn "Result is %A"
// Result is Success
Note that we needed to change reduce to fold as well, otherwise we would get a runtime
error.
1153
And if lists aren't performant enough for you, you can easily extend this approach to use
classic data structures like trees, heaps, etc. or mutable types like ResizeArray. (See the
appendix on performance at the bottom of this post for some more discussion on this)
Jargon alert
The concept of using a list of objects as a monoid is common in mathematics, where it is
called a "free monoid". In computer science, it also called a "Kleene star" such as A* . And
if you don't allow empty lists, then you have no zero element. This variant is called a "free
semigroup" or "Kleene plus" such as A+ .
This "star" and "plus" notation will surely be familiar to you if you have ever used regular
expressions.*
* You probably weren't aware that there was a connection between regular expressions and
monoids! There's some even deeper relationships too.
Associativity
Now that we have dealt with closure, let's take on associativity.
We saw a couple of non-associative operations in the very first post, including subtraction
and division.
We can see that 5 - (3 - 2) is not equal to (5 - 3) - 2 . This shows that subtraction is
not associative, and also 12 / (3 / 2) is not equal to (12 / 3) / 2 , which shows that
division is not associative.
There's no single correct answer in these cases, because you might genuinely care about
different answers depending on whether you work from left to right or right to left.
In fact, the F# standard libraries have two versions of fold and reduce to cater for each
preference. The normal fold and reduce work left to right, like this:
//same as (12 - 3) - 2
[12;3;2] |> List.reduce (-) // => 7
//same as ((12 - 3) - 2) - 1
[12;3;2;1] |> List.reduce (-) // => 6
But there is also foldBack and reduceBack that work from right to left, like this:
1154
//same as 12 - (3 - 2)
[12;3;2] |> List.reduceBack (-) // => 11
//same as 12 - (3 - (2 - 1))
[12;3;2;1] |> List.reduceBack (-) // => 10
In a sense, then, the associativity requirement is just a way of saying that you should get the
same answer no matter whether you use fold or foldBack .
This approach of converting the operator into a property of the element can be generalized
nicely.
So here's a tip:
DESIGN TIP: To get associativity for an operation, try to move the operation into
the object.
We can revisit an earlier example to understand how this works. If you recall, in the first post
we tried to come up with a non-associative operation for strings, and settled on
subtractChars .
1155
And we can see for ourselves that the associativity requirement is violated:
("abc" -- "abc") -- "abc" // ""
"abc" -- ("abc" -- "abc") // "abc"
Once we use this approach, we can rework the non-associative example above to look
something like this:
let removalAction = (subtract "abc") + (subtract "abc") + (subtract "abc")
removalAction |> applyTo "abc" // "Result is "
Yes, it is not exactly the same as the original code, but you might find that this is actually a
better fit in many situations.
The implementation is below. We define a CharsToRemove to contain a set of chars, and the
other function implementations fall out from that in a straightforward way.
1156
Let's test!
let test1 =
let removalAction = (subtract "abd")
removalAction |> applyTo "abcdef" |> printfn "Result is %s"
// "Result is cef"
let test2 =
let removalAction = (subtract "abc") ++ (subtract "abc") ++ (subtract "abc")
removalAction |> applyTo "abcdef" |> printfn "Result is %s"
// "Result is "
The way to think about this approach is that, in a sense, we are modelling actions rather
than data. We have a list of CharsToRemove actions, then we combine them into a single
"big" CharsToRemove action, and then we execute that single action at the end, after we have
finished the intermediate manipulations.
We'll see another example of this shortly, but you might be thinking at this point: "this sounds
a bit like functions, doesn't it?" To which I will say "yes, it does!"
In fact rather than creating this CharsToRemove data structure, we could have just partially
applied the original subtractChars function, as shown below:
(Note that we reverse the parameters to make partial application easier)
1157
only applied to strings at the end -- but they both achieve the same goal. Which one is better
depends on the particular problem you're working on.
I'll have more to say on functions and monoids in the next post.
Identity
Now to the last requirement for a monoid: identity.
As we have seen, identity is not always needed, but it is nice to have if you might be dealing
with empty lists.
For numeric values, finding an identity for an operation is generally easy, whether it be 0
(addition), 1 (multiplication) or Int32.MinValue (max).
1158
And this carries over to structures that contain only numeric values as well -- just set all
values to their appropriate identity. The CustomerStats type from the previous post
demonstrates that nicely.
But what if you have objects that are not numeric? How can you create a "zero" or identity
element if there is no natural candidate?
The answer is: you just make one up.
Seriously!
We have already seen an example of this in the previous post, when we added an
EmptyOrder case to the OrderLine type:
type OrderLine =
| Product of ProductLine
| Total of TotalLine
| EmptyOrder
We could do the same thing with the other semigroups we've seen.
For example, we noted earlier that strictly positive numbers (under addition) didn't have an
identity; they are only a semigroup. If we wanted to create a zero using the "augmentation
with extra case" technique (rather than just using 0 !) we would first define a special Zero
case (not an integer), and then create an addPositive function that can handle it, like this:
type PositiveNumberOrIdentity =
| Positive of int
| Zero
let addPositive i1 i2 =
match i1,i2 with
| Zero, _ -> i2
| _, Zero -> i1
| Positive p1, Positive p2 -> Positive (p1 + p2)
Admittedly, PositiveNumberOrIdentity is a contrived example, but you can see how this
same approach would work for any situation where you have "normal" values and a special,
separate, zero value.
A generic solution
There are a few drawbacks to this:
We have to deal with two cases now: the normal case and the zero case.
We have to create custom types and custom addition functions
Unfortunately, there's nothing you can do about the first issue. If you have a system with no
natural zero, and you create an artificial one, then you will indeed always have to deal with
two cases.
But there is something you can do about the second issue! Rather than create a new custom
type over and over, perhaps can we create a generic type that has two cases: one for all
normal values and one for the artificial zero, like this:
type NormalOrIdentity<'T> =
| Normal of 'T
| Zero
Does this type look familiar? It's just the Option type in disguise!
In other words, any time we need an identity which is outside the normal set of values, we
can use Option.None to represent it. And then Option.Some is used for all the other
"normal" values.
1160
Another benefit of using Option is that we can also write a completely generic "add"
function as well. Here's a first attempt:
let optionAdd o1 o2 =
match o1, o2 with
| None, _ -> o2
| _, None -> o1
| Some s1, Some s2 -> Some (s1 + s2)
The logic is straightforward. If either option is None , the other option is returned. If both are
Some , then they are unwrapped, added together, and then wrapped in a Some again.
But the + in the last line makes assumptions about the types that we are adding. Better to
pass in the addition function explicitly, like this:
let optionAdd f o1 o2 =
match o1, o2 with
| None, _ -> o2
| _, None -> o1
| Some s1, Some s2 -> Some (f s1 s2)
In practice, this would used with partial application to bake in the addition function.
So now we have another important tip:
DESIGN TIP: To get identity for an operation, create a special case in a
discriminated union, or, even simpler, just use Option.
PositiveNumber revisited
So here is the Positive Number example again, now using the Option type.
type PositiveNumberOrIdentity = int option
let addPositive = optionAdd (+)
Much simpler!
Notice that we pass in the "real" addition function as a parameter to optionAdd so that it is
baked in. In other situations, you would do the same with the relevant aggregation function
that is associated with the semigroup.
As a result of this partial application, addPositive has the signature: int option -> int
option -> int option , which is exactly what we would expect from a monoid addition
function.
1161
In other words, optionAdd turns any function 'a -> 'a -> 'a into the same function, but
"lifted" to the option type, that is, having a signature 'a option -> 'a option -> 'a option .
So, let's test it! Some test code might look like this:
// create some values
let p1 = Some 1
let p2 = Some 2
let zero = None
// test addition
addPositive p1 p2
addPositive p1 zero
addPositive zero p2
addPositive zero zero
You can see that unfortunately we do have to wrap the normal values in Some in order to
get the None as identity.
That sounds tedious but in practice, it is easy enough. The code below shows how we might
handle the two distinct cases when summing a list. First how to sum a non-empty list, and
then how to sum an empty list.
[1..10]
|> List.map Some
|> List.fold addPositive zero
[]
|> List.map Some
|> List.fold addPositive zero
ValidationResult revisited
While we're at it, let's also revisit the ValidationResult type that we described earlier when
talking about using lists to get closure. Here it is again:
type ValidationResult =
| Success
| Failure of string list
Now that we've got some insight into the positive integer case, let's look at this type from a
different angle as well.
1162
The type has two cases. One case holds data that we care about, and the other case holds
no data. But the data we really care about are the error messages, not the success. As Leo
Tolstoy nearly said "All validation successes are alike; each validation failure is a failure in its
own way."
So, rather than thinking of it as a "Result", let's think of the type as storing failures, and
rewrite it like this instead, with the failure case first:
type ValidationFailure =
| Failure of string list
| Success
The helper to convert a string into the failure case is now just Some with a list:
let fail str =
Some [str]
And the "add" function can reuse optionAdd , but this time with list concatenation as the
underlying operation:
let addFailure f1 f2 = optionAdd (@) f1 f2
Finally, the "zero" that was the Success case in the original design now simply becomes
None in the new design.
1163
1164
]
|> List.map (fun validate -> validate str)
"cobol has native support for monads"
|> validationResults
|> List.reduce addFailure
|> printfn "Result is %A"
// Result is Some
// ["string is too long"; "string contains a bad word: monad";
// "string contains a bad word: cobol"]
let test5 =
let validationResults str =
[]
|> List.map (fun validate -> validate str)
"cobol has native support for monads"
|> validationResults
|> List.fold addFailure Success
|> printfn "Result is %A"
// Result is <null>
1165
let avg i1 i2 =
float (i1 + i2) / 2.0
// test
avg 4 5 |> printfn "Average is %g"
// Average is 4.5
As we mentioned briefly in the first post, avg fails on all three monoid requirements!
First, it is not closed. Two ints that are combined together using avg do not result in
another int.
Second, even if it was closed, avg is not associative, as we can see by defining a similar
float function avgf :
let avgf i1 i2 =
(i1 + i2) / 2.0
// test
avgf (avgf 1.0 3.0) 5.0 |> printfn "Average from left is %g"
avgf 1.0 (avgf 3.0 5.0) |> printfn "Average from right is %g"
// Average from left is 3.5
// Average from right is 2.5
1166
The answer is that we create a structure that is not actually an average, but a "delayed
average" -- everything you need to make an average on demand.
That is, we need a data structure with two components: a total, and a count. With these two
numbers we can calculate an average as needed.
// store all the info needed for an average
type Avg = {total:int; count:int}
// add two Avgs together
let addAvg avg1 avg2 =
{total = avg1.total + avg2.total;
count = avg1.count + avg2.count}
The good thing about this, is that structure stores ints , not floats , so we don't need to
worry about loss of precision or associativity of floats.
The last tip is:
To get identity for an operation, create a special case in a discriminated union, or, even
simpler, just use Option.
In this case, the tip is not needed, as we can easily create a zero by setting the two
components to be zero:
let zero = {total=0; count=0}
We could also have used None for the zero, but it seems like overkill in this case. If the list
is empty, the Avg result is valid, even though we can't do the division.
Once we have had this insight into the data structure, the rest of the implementation follows
easily. Here is all the code, plus some tests:
1167
module Average =
// store all the info needed for an average
type Avg = {total:int; count:int}
// add two Avgs together
let addAvg avg1 avg2 =
{total = avg1.total + avg2.total;
count = avg1.count + avg2.count}
// inline version of add
let (++) = addAvg
// construct an average from a single number
let avg n = {total=n; count=1}
// calculate the average from the data.
// return 0 for empty lists
let calcAvg avg =
if avg.count = 0
then 0.0
else float avg.total / float avg.count
// alternative - return None for empty lists
let calcAvg2 avg =
if avg.count = 0
then None
else Some (float avg.total / float avg.count)
// the identity
let zero = {total=0; count=0}
// test
addAvg (avg 4) (avg 5)
|> calcAvg
|> printfn "Average is %g"
// Average is 4.5
(avg 4) ++ (avg 5) ++ (avg 6)
|> calcAvg
|> printfn "Average is %g"
// Average is 5
// test
[1..10]
|> List.map avg
|> List.reduce addAvg
|> calcAvg
|> printfn "Average is %g"
// Average is 5.5
1168
In the code above, you can see that I created a calcAvg function that uses the Avg
structure to calculate a (floating point) average. One nice thing about this approach is that
we can delay having to make a decision about what to do with a zero divisor. We can just
return 0 , or alternatively None , or we can just postpone the calculation indefinitely, and
only generate the average at the last possible moment, on demand!
And of course, this implementation of "average" has the ability to do incremental averages.
We get this for free because it is a monoid.
That is, if I have already calculated the average of a million numbers, and I want to add one
more, I don't have to recalculate everything, I can just add the new number to the totals so
far.
1169
In the last post, we implemented a "most frequent word" function, but found that it wasn't a
monoid homomorphism. That is,
mostFrequentWord(text1) + mostFrequentWord(text2)
Again, we can use the design tips to fix this up so that it works.
The insight here is again to delay the calculation until the last minute, just as we did in the
"average" example.
Rather than calculating the most frequent word upfront then, we create a data structure that
stores all the information that we need to calculate the most frequent word later.
module FrequentWordMonoid =
open System
open System.Text.RegularExpressions
type Text = Text of string
let addText (Text s1) (Text s2) =
Text (s1 + s2)
// return a word frequency map
let wordFreq (Text s) =
Regex.Matches(s,@"\S+")
|> Seq.cast<Match>
|> Seq.map (fun m -> m.ToString())
|> Seq.groupBy id
|> Seq.map (fun (k,v) -> k,Seq.length v)
|> Map.ofSeq
In the code above we have a new function wordFreq , that returns a Map<string,int> rather
just a single word. That is, we are now working with dictionaries, where each slot has a word
and its associated frequency.
Here is a demonstration of how it works:
1170
module FrequentWordMonoid =
// code from above
let page1() =
List.replicate 1000 "hello world "
|> List.reduce (+)
|> Text
let page2() =
List.replicate 1000 "goodbye world "
|> List.reduce (+)
|> Text
let page3() =
List.replicate 1000 "foobar "
|> List.reduce (+)
|> Text
let document() =
[page1(); page2(); page3()]
// show some word frequency maps
page1() |> wordFreq |> printfn "The frequency map for page1 is %A"
page2() |> wordFreq |> printfn "The frequency map for page2 is %A"
//The frequency map for page1 is map [("hello", 1000); ("world", 1000)]
//The frequency map for page2 is map [("goodbye", 1000); ("world", 1000)]
document()
|> List.reduce addText
|> wordFreq
|> printfn "The frequency map for the document is %A"
//The frequency map for the document is map [
// ("foobar", 1000); ("goodbye", 1000);
// ("hello", 1000); ("world", 2000)]
With this map structure in place, we can create a function addMap to add two maps. It
simply merges the frequency counts of the words from both maps.
1171
module FrequentWordMonoid =
// code from above
// define addition for the maps
let addMap map1 map2 =
let increment mapSoFar word count =
match mapSoFar |> Map.tryFind word with
| Some count' -> mapSoFar |> Map.add word (count + count')
| None -> mapSoFar |> Map.add word count
map2 |> Map.fold increment map1
And when we have combined all the maps together, we can then calculate the most frequent
word by looping through the map and finding the word with the largest frequency.
module FrequentWordMonoid =
// code from above
// as the last step,
// get the most frequent word in a map
let mostFrequentWord map =
let max (candidateWord,maxCountSoFar) word count =
if count > maxCountSoFar
then (word,count)
else (candidateWord,maxCountSoFar)
map |> Map.fold max ("None",0)
So, here are the two scenarios revisited using the new approach.
The first scenario combines all the pages into a single text, then applies wordFreq to get a
frequency map, and applies mostFrequentWord to get the most frequent word.
The second scenario applies wordFreq to each page separately to get a map for each page.
These maps are then combined with addMap to get a single global map. Then
mostFrequentWord is applied as the last step, as before.
1172
module FrequentWordMonoid =
// code from above
document()
|> List.reduce addText
|> wordFreq
// get the most frequent word from the big map
|> mostFrequentWord
|> printfn "Using add first, the most frequent word and count is %A"
//Using add first, the most frequent word and count is ("world", 2000)
document()
|> List.map wordFreq
|> List.reduce addMap
// get the most frequent word from the merged smaller maps
|> mostFrequentWord
|> printfn "Using map reduce, the most frequent and count is %A"
//Using map reduce, the most frequent and count is ("world", 2000)
If you run this code, you will see that you now get the same answer.
This means that wordFreq is indeed a monoid homomorphism, and is suitable for running in
parallel, or incrementally.
Next time
We've seen a lot of code in this post, but it has all been focused on data structures.
However, there is nothing in the definition of a monoid that says that the things to be
combined have to be data structures -- they could be anything at all.
In the next post we'll look at monoids applied to other objects, such as types, functions, and
more.
Appendix: On Performance
In the examples above, I have made frequent use of @ to "add" two lists in the same way
that + adds two numbers. I did this to highlight the analogies with other monoidal
operations such as numeric addition and string concatenation.
1173
I hope that it is clear that the code samples above are meant to be teaching examples, not
necessarily good models for the kind of real-world, battle-hardened, and all-too-ugly code
you need in a production environment.
A couple of people have pointed out that using List append ( @ ) should be avoided in
general. This is because the entire first list needs to be copied, which is not very efficient.
By far the best way to add something to a list is to add it to the front using the so-called
"cons" mechanism, which in F# is just :: . F# lists are implemented as linked lists, so
adding to the front is very cheap.
The problem with using this approach is that it is not symmetrical -- it doesn't add two lists
together, just a list and an element. This means that it cannot be used as the "add" operation
in a monoid.
If you don't need the benefits of a monoid, such as divide and conquer, then that is a
perfectly valid design decision. No need to sacrifice performance for a pattern that you are
not going to benefit from.
The other alternative to using @ is to not use lists in the first place!
Alternatives to lists
In the ValidationResult design, I used a list to hold the error results so that we could get
easy accumulation of the results. But I only chose the list type because it is really the
default collection type in F#. I could have equally well have chosen sequences, or arrays, or
sets. Almost any other collection type would have done the job just as well.
But not all types will have the same performance. For example, combining two sequences is
a lazy operation. You don't have to copy all the data; you just enumerate one sequence, then
the other. So that might be faster perhaps?
Rather than guessing, I wrote a little test script to measure performance at various list sizes,
for various collection types.
I have chosen a very simple model: we have a list of objects, each of which is a collection
containing one item. We then reduce this list of collections into a single giant collection using
the appropriate monoid operation. Finally, we iterate over the giant collection once.
This is very similar to the ValidationResult design, where we would combine all the results
into a single list of results, and then (presumably) iterate over them to show the errors.
It is also similar to the "most frequent word" design, above, where we combine all the
individual frequency maps into a single frequency map, and then iterate over it to find the
most frequent word. In that case, of course, we were using map rather than list , but the
1174
A performance experiment
Ok, here's the code:
1175
module Performance =
let printHeader() =
printfn "Label,ListSize,ReduceAndIterMs"
// time the reduce and iter steps for a given list size and print the results
let time label reduce iter listSize =
System.GC.Collect() //clean up before starting
let stopwatch = System.Diagnostics.Stopwatch()
stopwatch.Start()
reduce() |> iter
stopwatch.Stop()
printfn "%s,%iK,%i" label (listSize/1000) stopwatch.ElapsedMilliseconds
let testListPerformance listSize =
let lists = List.init listSize (fun i -> [i.ToString()])
let reduce() = lists |> List.reduce (@)
let iter = List.iter ignore
time "List.@" reduce iter listSize
let testSeqPerformance_Append listSize =
let seqs = List.init listSize (fun i -> seq {yield i.ToString()})
let reduce() = seqs |> List.reduce Seq.append
let iter = Seq.iter ignore
time "Seq.append" reduce iter listSize
let testSeqPerformance_Yield listSize =
let seqs = List.init listSize (fun i -> seq {yield i.ToString()})
let reduce() = seqs |> List.reduce (fun x y -> seq {yield! x; yield! y})
let iter = Seq.iter ignore
time "seq(yield!)" reduce iter listSize
let testArrayPerformance listSize =
let arrays = List.init listSize (fun i -> [| i.ToString() |])
let reduce() = arrays |> List.reduce Array.append
let iter = Array.iter ignore
time "Array.append" reduce iter listSize
let testResizeArrayPerformance listSize =
let resizeArrays = List.init listSize (fun i -> new ResizeArray<string>( [i.To
String()] ) )
let append (x:ResizeArray<_>) y = x.AddRange(y); x
let reduce() = resizeArrays |> List.reduce append
let iter = Seq.iter ignore
time "ResizeArray.append" reduce iter listSize
part of the decision to use it (or not). Understanding how GC works is an important part
of getting performant code.
The testListPerformance function sets up the list of collections (lists in this case) and
also the reduce and iter functions. It then runs the timer on reduce and iter .
The other functions do the same thing, but with sequences, arrays, and ResizeArrays
(standard .NET Lists). Out of curiosity, I thought I'd test two ways of merging
sequences, one using the standard library function Seq.append and the other using two
yield! s in a row.
The testResizeArrayPerformance uses ResizeArrays and adds the right list to the left
one. The left one mutates and grows larger as needed, using a growth strategy that
keeps inserts efficient.
Now let's write code to check the performance on various sized lists. I chose to start with a
count of 2000 and move by increments of 4000 up to 50000.
open Performance
printHeader()
[2000..4000..50000]
|> List.iter testArrayPerformance
[2000..4000..50000]
|> List.iter testResizeArrayPerformance
[2000..4000..50000]
|> List.iter testListPerformance
[2000..4000..50000]
|> List.iter testSeqPerformance_Append
[2000..4000..50000]
|> List.iter testSeqPerformance_Yield
I won't list all the detailed output -- you can run the code for yourself -- but here is a chart of
the results.
1177
1178
First, you might have all sorts of questions, such as: Were you running in debug or release
mode? Did you have optimization turned on? What about using parallelism to increase
performance? And no doubt, there will be comments saying "why did you use technique X,
technique Y is so much better".
But here's the conclusion I would like to make:
You cannot draw any conclusion from these results!
Every situation is different and requires a different approach:
If you are working with small data sets you might not care about performance anyway.
In this case I would stick with lists -- I'd rather not sacrifice pattern matching and
immutability unless I have to.
The performance bottleneck might not be in the list addition code. There is no point
working on optimizing the list addition if you are actually spending all your time on disk
I/O or network delays. A real-world version of the word frequency example might
actually spend most of its time doing reading from disk, or parsing, rather than adding
lists.
If you working at the scale of Google, Twitter, or Facebook, you really need to go and
hire some algorithm experts.
The only principles that we can take away from any discussion on optimization and
performance are:
A problem must be dealt with in its own context. The size of the data being
processed, the type of hardware, the amount of memory, and so on. All these will make
a difference to your performance. What works for me may not work for you, which is
why...
You should always measure, not guess. Don't make assumptions about where your
code is spending its time -- learn to use a profiler! There are some good examples of
using a profiler here and here.
Be wary of micro-optimizations. Even if your profiler shows that your sorting routine
spends all its time in comparing strings, that doesn't necessarily mean that you need to
improve your string comparison function. You might be better off improving your
algorithm so that you don't need to do so many comparisons in the first place.
Premature optimization and all that.
1179
In this series, we'll look at how so-called "applicative parsers" work. In order to understand
something, there's nothing like building it for yourself, and so we'll create a basic parser
library from scratch, then some useful "parser combinators", and then finish off by building a
complete JSON parser.
Understanding Parser Combinators. Building a parser combinator library from scratch.
Building a useful set of parser combinators. 15 or so combinators that can be combined
to parse almost anything.
Improving the parser library. Adding more informative errors.
Writing a JSON parser from scratch. In 250 lines of code.
1180
1181
which tells us that the input is a string, and the output is a pair consisting of the boolean
result and another string (the remaining input), like this:
As you can see, the A has been consumed and the remaining input is just "BC" .
And now with bad input:
1182
And in this case, the first character was not consumed and the remaining input is still
"ZBC" .
So, there's an incredibly simple parser for you. If you understand that, then everything that
follows will be easy!
This code is just like the previous example, except that the unexpected character is now
shown in the error message.
The signature of pchar is:
1183
val pchar :
(char * string) -> (string * string)
which tells us that the input is a pair of (string,character to match) and the output is a pair
consisting of the (string) result and another string (the remaining input).
Let's test it now -- first with good input:
let inputABC = "ABC"
pchar('A',inputABC)
As before, the A has been consumed and the remaining input is just "BC" .
And now with bad input:
let inputZBC = "ZBC"
pchar('A',inputZBC)
And again, as before, the first character was not consumed and the remaining input is still
"ZBC" .
1184
type Result<'a> =
| Success of 'a
| Failure of string
The Success case is generic and can contain any value. The Failure case contains an
error message.
Note: for more on using this Success/Failure approach, see my talk on functional error
handling.
We can now rewrite the parser to return one of the Result cases, like this:
let pchar (charToMatch,str) =
if String.IsNullOrEmpty(str) then
Failure "No more input"
else
let first = str.[0]
if first = charToMatch then
let remaining = str.[1..]
Success (charToMatch,remaining)
else
let msg = sprintf "Expecting '%c'. Got '%c'" charToMatch first
Failure msg
which tells us that the the output is now a Result (which in the Success case, contains the
matched char and the remaining input string).
Let's test it again -- first with good input:
let inputABC = "ABC"
pchar('A',inputABC)
As before, the A has been consumed and the remaining input is just "BC" . We also get
the actual matched char ( A in this case).
1185
And in this case, the Failure case is returned with the appropriate error message.
This is a diagram of the function's inputs and outputs now:
Can you see the difference? The only difference is in the first line, and even then it is subtle.
Here's the uncurried (tuple) version:
1186
The difference is much more obvious when you look at the type signatures. Here's the
signature for the uncurried (tuple) version:
val pchar :
(char * string) -> Result<char * string>
What is currying?
If you are unclear on how currying works, I have a post about it here, but basically it means
that a multi-parameter function can be written as a series of one-parameter functions.
In other words, this two-parameter function:
let add x y =
x + y
1187
let add x =
let innerFn y = x + y
innerFn // return innerFn
1188
It's very important that you understand this logic before moving on, because the rest of the
post will build on this basic design.
1189
That type is a bit complicated to use, so let's encapsulate it in a "wrapper" type called
Parser , like this:
to this design:
The change to the implementation is very simple. We just need to change the way the inner
function is returned.
That is, from this:
let pchar charToMatch =
let innerFn str =
...
// return the inner function
innerFn
to this:
let pchar charToMatch =
let innerFn str =
...
// return the "wrapped" inner function
Parser innerFn
1190
And of course that is because the function is wrapped in the Parser data structure! It's not
longer directly accessible.
So now we need a helper function that can extract the inner function and run it against the
input stream. Let's call it run !
Here's the implementation of run :
let run parser input =
// unwrap parser to get inner function
let (Parser innerFn) = parser
// call inner function with input
innerFn input
And now we can run the parseA parser against various inputs again:
let inputABC = "ABC"
run parseA inputABC // Success ('A', "BC")
let inputZBC = "ZBC"
run parseA inputZBC // Failure "Expecting 'A'. Got 'Z'"
That's it! We've got a basic Parser type! I hope that this all makes sense so far.
1191
but that gives us a compiler error, as the output of parseA does not match the input of
parseB , and so they cannot be composed like that.
If you are familiar with functional programming patterns, the need to chain a sequence of
wrapped types together like this happens frequently, and the solution is a bind function.
However, in this case, I won't implement bind but will instead go straight to an andThen
implemention.
The implementation logic will be as follows:
Run the first parser.
If there is a failure, return.
Otherwise, run the second parser with the remaining input.
If there is a failure, return.
If both parsers succeed, return a pair (tuple) that contains both parsed values.
Here's the code for andThen :
1192
Note: the parentheses are needed to define a custom operator, but are not needed in the
infix usage.
If we look at the signature of andThen :
val andThen :
parser1:Parser<'a> -> parser2:Parser<'b> -> Parser<'a * 'b>
we can see that it works for any two parsers, and they can be of different types ( 'a and
'b ).
1193
Testing andThen
Let's test it and see if it works!
First, create the compound parser:
let parseA = pchar 'A'
let parseB = pchar 'B'
let parseAThenB = parseA .>>. parseB
If you look at the types, you can see that all three values have type Parser :
val parseA : Parser<char>
val parseB : Parser<char>
val parseAThenB : Parser<char * char>
parseAThenB is of type Parser<char * char> meaning that the parsed value is a pair of
chars.
Now since the combined parser parseAThenB is just another Parser , we can use run with
it as before.
run parseAThenB "ABC" // Success (('A', 'B'), "C")
run parseAThenB "ZBC" // Failure "Expecting 'A'. Got 'Z'"
run parseAThenB "AZC" // Failure "Expecting 'B'. Got 'Z'"
You can see that in the success case, the pair ('A', 'B') was returned, and also that
failure happens when either letter is missing from the input.
1194
On success, return the parsed value, along with the remaining input.
Otherwise, on failure, run the second parser with the original input...
...and in this case, return the result (success or failure) from the second parser.
Here's the code for orElse :
let orElse parser1 parser2 =
let innerFn input =
// run parser1 with the input
let result1 = run parser1 input
// test the result for Failure/Success
match result1 with
| Success result ->
// if success, return the original result
result1
| Failure err ->
// if failed, run parser2 with the input
let result2 = run parser2 input
// return parser2's result
result2
// return the inner function
Parser innerFn
we can see that it works for any two parsers, but they must both be the same type 'a .
Testing orElse
Time to test it. First, create the combined parser:
let parseA = pchar 'A'
let parseB = pchar 'B'
let parseAOrElseB = parseA <|> parseB
1195
If you look at the types, you can see that all three values have type Parser<char> :
val parseA : Parser<char>
val parseB : Parser<char>
val parseAOrElseB : Parser<char>
Now if we run parseAOrElseB we can see that it successfully handles an "A" or a "B" as first
character.
run parseAOrElseB "AZZ" // Success ('A', "ZZ")
run parseAOrElseB "BZZ" // Success ('B', "ZZ")
run parseAOrElseB "CZZ" // Failure "Expecting 'B'. Got 'C'"
Note that the last example gives a misleading error. It says "Expecting 'C'" when it really
should say "Expecting 'B' or 'C'". We won't attempt to fix this right now, but in a later post
we'll implement better error messages.
This is where where the power of combinators starts kicking in, because with orElse in our
toolbox, we can use it to build even more combinators.
For example, let's say that we want choose from a list of parsers, rather than just two.
Well, that's easy. If we have a pairwise way of combining things, we can extend that to
combining an entire list using reduce (for more on working with reduce , see this post on
monoids ).
/// Choose any of a list of parsers
let choice listOfParsers =
List.reduce ( <|> ) listOfParsers
Note that this will fail if the input list is empty, but we will ignore that for now.
The signature of choice is:
val choice :
Parser<'a> list -> Parser<'a>
which shows us that, as expected, the input is a list of parsers, and the output is a single
parser.
With choice available, we can create an anyOf parser that matches any character in a list,
using the following logic:
The input is a list of characters
Each char in the list is transformed into a parser for that char using pchar
Finally, all the parsers are combined using choice
Here's the code:
/// Choose any of a list of characters
let anyOf listOfChars =
listOfChars
|> List.map pchar // convert into parsers
|> choice
Let's test it by creating a parser for any lowercase character and any digit character:
let parseLowercase =
anyOf ['a'..'z']
let parseDigit =
anyOf ['0'..'9']
1197
Again, the error messages are misleading. Any lowercase letter can be expected, not just 'z',
and any digit can be expected, not just '9'. As I said earlier, we'll work on the error messages
in a later post.
Review
Let's stop for now, and review what we have done:
We have created a type Parser that is a wrapper for a parsing function.
The parsing function takes an input (e.g. string) and attempts to match the input using
the criteria baked into the function.
If the match succeeds, the parsing function returns a Success with the matched item
and the remaining input.
If the match fails, the parsing function returns a Failure with reason for the failure.
And finally, we saw some "combinators" -- ways in which Parser s could be combined
to make a new Parser : andThen and orElse and choice .
1198
1199
Summary
In this post, we have created the foundations of a parsing library, and few simple
combinators.
In the next post, we'll build on this to create a library with many more combinators.
The source code for this post is available at this gist.
Further information
1200
If you are interesting in using this technique in production, be sure to investigate the
FParsec library for F#, which is optimized for real-world usage.
For more information about parser combinators in general, search the internet for
"Parsec", the Haskell library that influenced FParsec (and this post).
For some examples of using FParsec, try one of these posts:
Implementing a phrase search query for FogCreek's Kiln
A LOGO Parser
A Small Basic Parser
A C# Parser and building a C# compiler in F#
Write Yourself a Scheme in 48 Hours in F#
Parsing GLSL, the shading language of OpenGL
1201
This doesn't work, because the output of andThen is different from the input (a tuple, not a
char) and so the reduce approach fails.
In order to solve this, we'll need to use a different technique.
To get started, let's try just matching a string of a specific length. Say, for example, that we
want to match a three digits in a row. Well, we can do that using andThen :
let parseDigit =
anyOf ['0'..'9']
let parseThreeDigits =
parseDigit .>>. parseDigit .>>. parseDigit
1202
It does work, but the result contains a tuple inside a tuple (('1', '2'), '3') which is fugly
and hard to use. It would be so much more convenient to just have a simple string ( "123" ).
But in order to turn ('1', '2'), '3') into "123" , we'll need a function that can reach
inside of the parser and transform the result using an arbitrary passed in function.
Of course, what we need is the functional programmer's best friend, map .
To understand map and similar functions, I like to think of there being two worlds: a "Normal
World", where regular things live, and "Parser World", where Parser s live.
You can think of Parser World as a sort of "mirror" of Normal World because it obeys the
following rules:
Every type in Normal World (say char ) has a corresponding type in Parser World
( Parser<char> ).
And:
Every value in Normal World (say "ABC" ) has a corresponding value in Parser World
(that is, some Parser<string> that returns "ABC" ).
And:
Every function in Normal World (say char -> string ) has a corresponding function in
Parser World ( Parser<char> -> Parser<string> ).
1203
Using this metaphor then, map transforms (or "lifts") a function in Normal World into a
function in Parser World.
And by the way, if you like this metaphor, I have a whole series of posts that develop it
further.
So that's what map does; how do we implement it?
The logic is:
Inside the innerFn , run the parser to get the result.
If the result was a success, apply the specified function to the success value to get a
new, transformed value, and...
...return the new, mapped, value instead of the original value.
Here's the code (I've named the map function mapP to avoid confusion with other map
functions):
1204
we can see that it has exactly the signature we want, transforming a function 'a -> 'b into
a function Parser<'a> -> Parser<'b> .
It's common to define an infix version of map as well:
let ( <!> ) = mapP
And in the context of parsing, we'll often want to put the mapping function after the parser,
with the parameters flipped. This makes using map with the pipeline idiom much more
convenient:
let ( |>> ) x f = mapP f x
1205
And if we test it, we get a string in the result now, rather than a tuple:
run parseThreeDigitsAsStr "123A" // Success ("123", "A")
It's a Parser<int> now, not a Parser<char> or Parser<string> . The fact that a Parser can
contain any type, not just a char or string, is a key feature that will be very valuable when we
need to build more complex parsers.
1206
1207
And here is the implementation of applyP , which uses .>>. and map :
let applyP fP xP =
// create a Parser containing a pair (f,x)
(fP .>>. xP)
// map the pair by applying f to x
|> mapP (fun (f,x) -> f x)
Why do we need these two functions? Well, map will lift functions in Normal World into
functions in Parser World, but only for one-parameter functions.
What's great about returnP and applyP is that, together, they can lift any function in
Normal World into a function in Parser World, no matter how many parameters it has.
For example, we now can define a lift2 function that will lift a two parameter function into
Parser World like this:
// lift a two parameter function to Parser World
let lift2 f xP yP =
returnP f <*> xP <*> yP
1208
If you want to know more about how this works, check out my "man page" post on lift2 or
my explanation that involves the "Monadster".
Let's see some examples of using lift2 in practice. First, lifting integer addition to addition
of Parsers:
let addP =
lift2 (+)
which shows that addP does indeed take two Parser<int> parameters and returns another
Parser<int> .
Again, the signature of startsWithP is parallel to the signature of startsWith , but lifted to
the world of Parsers.
val startsWith :
str:string -> prefix:string -> bool
val startsWithP :
Parser<string> -> Parser<string> -> Parser<bool>
1209
which shows that the input is a list of Parser s and the output is a Parser containing a list
of elements.
Let's test it by creating a list of three parsers, and then combining them into one:
1210
As you can see, when we run it we get back a list of characters, one for each parser in the
original list.
1211
1212
With this helper function, we can easily define many now -- it's just a wrapper over
parseZeroOrMore :
The signature of many shows that the output is indeed a list of values wrapped in a
Parser :
val many :
Parser<'a> -> Parser<'a list>
1213
Note that in the last case, even when there is nothing to match, the function succeeds.
There's nothing about many that restricts its use to single characters. For example, we can
use it to match repetitive string sequences too:
let manyAB = many (pstring "AB")
run manyAB "ABCD" // Success (["AB"], "CD")
run manyAB "ABABCD" // Success (["AB"; "AB"], "CD")
run manyAB "ZCD" // Success ([], "ZCD")
run manyAB "AZCD" // Success ([], "AZCD")
Defining many1
We can also define the "one or more" combinator many1 , using the following logic:
Run the parser.
If it fails, return the failure.
If it succeeds:
Call the helper function parseZeroOrMore to get the remaining values.
Then combine the first value and the remaining values.
1214
Again, the signature of many1 shows that the output is indeed a list of values wrapped in a
Parser :
val many1 :
Parser<'a> -> Parser<'a list>
As we saw in an earlier example, the last case gives a misleading error. It says "Expecting
'9'" when it really should say "Expecting a digit". In the next post we'll fix this.
Parsing an integer
Using many1 , we can create a parser for an integer. The implementation logic is:
Create a parser for a digit.
1215
1216
let opt p =
let some = p |>> Some
let none = returnP None
some <|> none
Note that the resultToInt helper function now needs to handle the sign option as well as
the list of digits.
And here it is in action:
run pint "123C" // Success (123, "C")
run pint "-123C" // Success (-123, "C")
1217
We often want to match something in the input, but we don't care about the parsed value
itself. For example:
For a quoted string, we need to parse the quotes, but we don't need the quotes
themselves.
For a statement ending in a semicolon, we need to ensure the semicolon is there, but
we don't need the semicolon itself.
For whitespace separators, we need to ensure the whitespace is there, but we don't
need the actual whitespace data.
To handle these requirements, we will define some new combinators that throw away the
results of a parser:
p1 >>. p2 will apply p1 and p2 in sequence, just like .>>. , but throw away the
These are easy to define -- just map over the result of .>>. , which is a tuple, and keep only
one element of the pair.
/// Keep only the result of the left side parser
let (.>>) p1 p2 =
// create a pair
p1 .>>. p2
// then only keep the first value
|> mapP (fun (a,b) -> a)
/// Keep only the result of the right side parser
let (>>.) p1 p2 =
// create a pair
p1 .>>. p2
// then only keep the second value
|> mapP (fun (a,b) -> b)
You can see that the result now is the same, whether or not the semicolon was present.
1218
The result contains "AB" and "CD" only. The whitespace between them has been discarded.
Introducing between
A particularly common requirement is to look for a parser between delimiters such as quotes
or brackets.
Creating a combinator for this is trivial:
/// Keep only the result of the middle parser
let between p1 p2 p3 =
p1 >>. p2 .>> p3
1219
For the "zero or more" version, we can choose the empty list as an alternate if sepBy1 does
not find any matches:
/// Parses zero or more occurrences of p separated by sep
let sepBy p sep =
sepBy1 p sep <|> returnP []
Here's some tests for sepBy1 and sepBy , with results shown in the comments:
let comma = pchar ','
let digit = anyOf ['0'..'9']
let zeroOrMoreDigitList = sepBy digit comma
let oneOrMoreDigitList = sepBy1 digit comma
run oneOrMoreDigitList "1;" // Success (['1'], ";")
run oneOrMoreDigitList "1,2;" // Success (['1'; '2'], ";")
run oneOrMoreDigitList "1,2,3;" // Success (['1'; '2'; '3'], ";")
run oneOrMoreDigitList "Z;" // Failure "Expecting '9'. Got 'Z'"
run zeroOrMoreDigitList "1;" // Success (['1'], ";")
run zeroOrMoreDigitList "1,2;" // Success (['1'; '2'], ";")
run zeroOrMoreDigitList "1,2,3;" // Success (['1'; '2'; '3'], ";")
run zeroOrMoreDigitList "Z;" // Success ([], "Z;")
1220
But now that we have have some experience, let's implement bind and see what we can
do with it.
Here's the implementation of bindP (as I'll call it)
/// "bindP" takes a parser-producing function f, and a parser p
/// and passes the output of p into f, to create a new parser
let bindP f p =
let innerFn input =
let result1 = run p input
match result1 with
| Failure err ->
// return error from parser1
Failure err
| Success (value1,remainingInput) ->
// apply f to get a new parser
let p2 = f value1
// run parser with remaining input
run p2 remainingInput
Parser innerFn
which conforms to a standard bind signature. The input f is a "diagonal" function ( 'a ->
Parser<'b> ) and the output is a "horizontal" function ( Parser<'a> -> Parser<'b> ). See this
1221
let mapP f =
bindP (f >> returnP)
let andThen p1 p2 =
p1 >>= (fun p1Result ->
p2 >>= (fun p2Result ->
returnP (p1Result,p2Result) ))
let applyP fP xP =
fP >>= (fun f ->
xP >>= (fun x ->
returnP (f x) ))
// (assuming "many" is defined)
let many1 p =
p >>= (fun head ->
many p >>= (fun tail ->
returnP (head::tail) ))
Note that the combinators that check the Failure path can not be implemented using
bind . These include orElse and many .
Review
We could keep building combinators for ever, but I think we have everything we need to
build a JSON parser now, so let's stop and review what we have done.
In the previous post we created these combinators:
.>>. ( andThen ) applies the two parsers in sequence and returns the results in a tuple.
<|> ( orElse ) applies the first parser, and if that fails, the second parsers.
choice extends orElse to choose from a list of parsers.
1222
I hope you can see why the concept of "combinators" is so powerful; given just a few basic
functions, we have built up a library of useful functions quickly and concisely.
1223
1224
1225
returnP []
| head::tail ->
consP head (sequence tail)
/// (helper) match zero or more occurences of the specified parser
let rec parseZeroOrMore parser input =
// run parser with the input
let firstResult = run parser input
// test the result for Failure/Success
match firstResult with
| Failure err ->
// if parse fails, return empty list
([],input)
| Success (firstValue,inputAfterFirstParse) ->
// if parse succeeds, call recursively
// to get the subsequent values
let (subsequentValues,remainingInput) =
parseZeroOrMore parser inputAfterFirstParse
let values = firstValue::subsequentValues
(values,remainingInput)
/// matches zero or more occurences of the specified parser
let many parser =
let rec innerFn input =
// parse the input -- wrap in Success as it always succeeds
Success (parseZeroOrMore parser input)
Parser innerFn
/// matches one or more occurences of the specified parser
let many1 p =
p >>= (fun head ->
many p >>= (fun tail ->
returnP (head::tail) ))
/// Parses an optional occurrence of p and returns an option value.
let opt p =
let some = p |>> Some
let none = returnP None
some <|> none
/// Keep only the result of the left side parser
let (.>>) p1 p2 =
// create a pair
p1 .>>. p2
// then only keep the first value
|> mapP (fun (a,b) -> a)
/// Keep only the result of the right side parser
let (>>.) p1 p2 =
// create a pair
p1 .>>. p2
// then only keep the second value
1226
Summary
In this post, we have built on the basic parsing code from last time to create a library of a 15
or so combinators that can be combined to parse almost anything.
Soon, we'll use them to build a JSON parser, but before that, let's pause and take time to
clean up the error messages. That will be the topic of the next post.
The source code for this post is available at this gist.
1227
1. Labelling a Parser
In some of the failing code examples from earlier posts, we got confusing errors:
let parseDigit = anyOf ['0'..'9']
run parseDigit "|ABC" // Failure "Expecting '9'. Got '|'"
parseDigit is defined as a choice of digit characters, so when the last choice ( '9' ) fails,
1228
The record contains two fields: the parsing function ( parseFn ) and the label .
One problem is that the label is in the parser itself, but not in the Result , which means that
clients will not know how to display the label along with the error.
So let's add it to the Failure case of Result as well, in addition to the error message:
// Aliases
type ParserLabel = string
type ParserError = string
type Result<'a> =
| Success of 'a
| Failure of ParserLabel * ParserError
And while we are at it, let's define a helper function to display the result of a parse:
let printResult result =
match result with
| Success (value,input) ->
printfn "%A" value
| Failure (label,error) ->
printfn "Error parsing %s\n%s" label error
We have to make similar changes to returnP , orElse , and many . For the complete code,
see the gist linked to below.
When we use a combinator to build a new compound parser, we will often want to assign a
new label to it. In order to do this, we replace the original parseFn with another one that
returns the new label.
Here's the code:
/// Update the label in the parser
let setLabel parser newLabel =
// change the inner function to use the new label
let newInnerFn input =
let result = parser.parseFn input
match result with
| Success s ->
// if Success, do nothing
Success s
| Failure (oldLabel,err) ->
// if Failure, return new label
Failure (newLabel,err) // <====== use newLabel here
// return the Parser
{parseFn=newInnerFn; label=newLabel} // <====== use newLabel here
The error message is now Error parsing digit rather than Expecting '9' . Much better!
1230
We can also set the default labels for certain combinators such as andThen and orElse
based on the inputs:
/// Combine two parsers as "A andThen B"
let andThen p1 p2 =
let label = sprintf "%s andThen %s" (getLabel p1) (getLabel p2)
p1 >>= (fun p1Result ->
p2 >>= (fun p2Result ->
returnP (p1Result,p2Result) ))
<?> label // <====== provide a custom label
// combine two parsers as "A orElse B"
let orElse parser1 parser2 =
// construct a new label
let label = // <====== provide a custom label
sprintf "%s orElse %s" (getLabel parser1) (getLabel parser2)
1231
Other than the parameters, the only thing that has changed from the pchar implementation
is this one line:
let satisfy predicate label =
...
if predicate first then
...
Note that we are setting the label to be the charToMatch . This refactoring would not have
been as convenient before, because we didn't have the concept of "labels" yet, and so
pchar would not have been able to return a useful error message.
The satisfy function also lets us write more efficient versions of other parsers. For
example, parsing a digit looked like this originally:
/// parse a digit
let digitChar =
anyOf ['0'..'9']
But now we can rewrite it using a predicate directly, making it a lot more efficient:
1232
1233
type Position = {
line : int
column : int
}
/// define an initial position
let initialPos = {line=0; column=0}
/// increment the column number
let incrCol pos =
{pos with column=pos.column + 1}
/// increment the line number and set the column to 0
let incrLine pos =
{line=pos.line + 1; column=0}
Next, we'll need to combine the input string with a position into a single "input state" type.
Since we are line oriented, we can make our lives easier and store the input string as a array
of lines rather than as one giant string:
/// Define the current input state
type InputState = {
lines : string[]
position : Position
}
Finally, and most importantly, we need a way to read the next character from the input -- let's
call it nextChar .
We know what the input for nextChar will be (an InputState ) but what should the output
look like?
If the input is at the end, we need a way to indicate that there is no next character, so in
that case return None .
Therefore in the case when a character is available, we will return Some .
1234
In addition, the input state will have changed because the column (or line) will have
been incremented as well.
So, putting this together, the input for nextChar is an InputState and the output is a pair
char option * InputState .
The logic for returning the next char will be as follows then:
If we are at the last character of the input, return EOF ( None ) and don't change the
state.
If the current column is not at the end of a line, return the character at that position and
change the state by incrementing the column position.
If the current column is at the end of a line, return a newline character and change the
state by incrementing the line position.
Here's the code:
1235
Unlike the earlier string implementation, the underlying array of lines is never altered or
copied -- only the position is changed. This means that making a new state each time the
position changes should be reasonably efficient, because the text is shared everywhere.
Let's quickly test that the implementation works. We'll create a helper function readAllChars
and then see what it returns for different inputs:
1236
Note that the implementation returns a newline at the end of the input, even if the input
doesn't have one. I think that this is a feature, not a bug!
1237
With all this extra information available, the printResult function can be enhanced to print
the text of the current line, along with a caret where the error is:
let printResult result =
match result with
| Success (value,input) ->
printfn "%A" value
| Failure (label,error,parserPos) ->
let errorLine = parserPos.currentLine
let colPos = parserPos.column
let linePos = parserPos.line
let failureCaret = sprintf "%*s^%s" colPos "" error
printfn "Line:%i Col:%i Error parsing %s\n%s\n%s" linePos colPos label errorLi
ne failureCaret
1238
1239
Note that the failure case code is now Failure (label,err,pos) where the parser position is
built from the input state.
And here is bindP :
/// "bindP" takes a parser-producing function f, and a parser p
/// and passes the output of p into f, to create a new parser
let bindP f p =
let label = "unknown"
let innerFn input =
let result1 = runOnInput p input
match result1 with
| Failure (label,err,pos) -> // <====== new with pos
// return error from parser1
Failure (label,err,pos)
| Success (value1,remainingInput) ->
// apply f to get a new parser
let p2 = f value1
// run parser with remaining input
runOnInput p2 remainingInput
{parseFn=innerFn; label=label}
1240
let parseAB =
pchar 'A' .>>. pchar 'B'
<?> "AB"
run parseAB "A|C"
|> printResult
1241
1242
Whitespace parsers
Whitespace is important in parsing, even if we do end up mostly throwing it away!
/// parse a whitespace char
let whitespaceChar =
let predicate = Char.IsWhiteSpace
let label = "whitespace"
satisfy predicate label
/// parse zero or more whitespace char
let spaces = many whitespaceChar
/// parse one or more whitespace char
let spaces1 = many1 whitespaceChar
1243
Numeric parsers
Finally, we need a parser for ints and floats.
/// parse a digit
let digitChar =
let predicate = Char.IsDigit
let label = "digit"
satisfy predicate label
// parse an integer
let pint =
let label = "integer"
// helper
let resultToInt (sign,digits) =
let i = digits |> int // ignore int overflow for now
match sign with
| Some ch -> -i // negate the int
| None -> i
// define parser for one or more digits
let digits = manyChars1 digitChar
// an "int" is optional sign + one or more digits
opt (pchar '-') .>>. digits
|> mapP resultToInt
<?> label
// parse a float
let pfloat =
let label = "float"
// helper
let resultToFloat (((sign,digits1),point),digits2) =
let fl = sprintf "%s.%s" digits1 digits2 |> float
match sign with
| Some ch -> -fl // negate the float
| None -> fl
// define parser for one or more digits
let digits = manyChars1 digitChar
// a float is sign, digits, point, digits (ignore exponents for now)
opt (pchar '-') .>>. digits .>>. pchar '.' .>>. digits
|> mapP resultToFloat
<?> label
1244
5. Backtracking
One more topic that we should discuss is "backtracking".
Let's say that you have two parsers: one to match the string A-1 and and another to match
the string A-2 . If the input is A-2 then the first parser will fail at the third character and the
second parser will be attempted.
Now the second parser must start at the beginning of the original sequence of characters,
not at the third character. That is, we need to undo the current position in the input stream
and go back to the first position.
If we were using a mutable input stream then this might be a tricky problem, but thankfully
we are using immutable data, and so "undoing" the position just means using the original
input value. And of course, this is exactly what combinators such as orElse ( <|> ) do.
In other words, we get backtracking "for free" when we use immutable input state. Yay!
Sometimes however, we don't want to backtrack. For example, let's say we have these
parsers:
let forExpression = the "for" keyword, then an identifier, then the "in" keyword, etc.
let ifExpression = the "if" keyword, then an identifier, then the "then" keyword, etc.
and we then create a combined expression parser that chooses between them:
let expression = forExpression <|> ifExpression
1245
Now, if the input stream is for &&& in something then the forExpression parser will error
when it hits the sequence &&& , because it is expecting a valid identifier. At this point we
don't want to backtrack and try the ifExpression -- we want to show an error such as
"identifier expected after 'for'".
The rule then is that: if input has been consumed successfully (in this case, the for
keyword was matched successfully) then do not backtrack.
We're not going to implement this rule in our simple library, but a proper library like FParsec
does implement this and also has support for bypassing it when needed.
Summary
In this post, we added better error handling and some more parsers.
Now we have everything we need to build a JSON parser! That will be the topic of the next
post.
The source code for this post is available at this gist.
1246
First, before we do anything else, we need to load the parser library script that we developed
over the last few posts, and then open the ParserLibrary namespace:
#load "ParserLibrary.fsx"
open System
open ParserLibrary
1247
Parsing Null
Parsing the null literal is trivial. The logic will be:
Match the string "null".
Map the result to the JNull case.
Here's the code:
let jNull =
pstring "null"
|>> (fun _ -> JNull) // map to JNull
<?> "null" // give it a label
Note that we don't actually care about the value returned by the parser because we know in
advance that it is going to be "null"!
This is a common situation, so let's write a little utility function, >>% to make this look nicer:
1248
Let's test:
run jNull "null"
// Success: JNull
run jNull "nulp" |> printResult
// Line:0 Col:3 Error parsing null
// nulp
// ^Unexpected 'p'
Parsing Bool
The bool parser will be similar to null:
Create a parser to match "true".
Create a parser to match "false".
And then choose between them using <|> .
Here's the code:
let jBool =
let jtrue =
pstring "true"
>>% JBool true // map to JBool
let jfalse =
pstring "false"
>>% JBool false // map to JBool
// choose between true and false
jtrue <|> jfalse
<?> "bool" // give it a label
1249
Note that the error is misleading due to the backtracking issue discussed in the previous
post. Since "true" failed, it is trying to parse "false" now, and "t" is an unexpected character.
3. Parsing String
Now for something more complicated -- strings.
The spec for string parsing is available as a "railway diagram" like this:
1250
Let's start with "any unicode character other than quote and backslash". We have a simple
condition to test, so we can just use the satisfy function:
let jUnescapedChar =
let label = "char"
satisfy (fun ch -> ch <> '\\' && ch <> '\"') label
Ok, good.
Escaped characters
Now what about the next case, the escaped characters?
In this case we have a list of strings to match ( "\"" , "\n" , etc) and for each of these, a
character to use as the result.
The logic will be:
First define a list of pairs in the form (stringToMatch, resultChar) .
For each of these, build a parser using pstring stringToMatch >>% resultChar) .
Finally, combine all these parsers together using the choice function.
Here's the code:
1251
It works nicely!
Unicode characters
The final case is the parsing of unicode characters with hex digits.
The logic will be:
First define the primitives for backslash , u and hexdigit .
Combine them together, using four hexdigit s.
The output of the parser will be a nested, ugly tuple, so we need a helper function to
convert the digits to an int, and then a char.
Here's the code:
1252
The whole parser is then zero or many jchar between two quotes.
let quotedString =
let quote = pchar '\"' <?> "quote"
let jchar = jUnescapedChar <|> jEscapedChar <|> jUnicodeChar
// set up the main parser
quote >>. manyChars jchar .>> quote
One more thing, which is to wrap the quoted string in a JString case and give it a label:
1253
4. Parsing Number
The "railway diagram" for Number parsing is:
Again, we'll work bottom up. Let's start with the most primitive components, the single chars
and digits:
1254
Now let's build the "integer" part of the number. This is either:
The digit zero, or,
A nonZeroInt , which is a digitOneNine followed by zero or more normal digits.
let nonZeroInt =
digitOneNine .>>. manyChars digit
|>> fun (first,rest) -> string first + rest
let intPart = zero <|> nonZeroInt
Note that, for the nonZeroInt parser, we have to combine the output of digitOneNine (a
char) with manyChars digit (a string) so a simple map function is needed.
The optional fractional part is a decimal point followed by one or more digits:
let fractionPart = point >>. manyChars1 digit
And the exponent part is an e followed by an optional sign, followed by one or more digits:
let exponentPart = e >>. optPlusMinus .>>. manyChars1 digit
1255
We haven't defined convertToJNumber yet though. This function will take the four-tuple
output by the parser and convert it into a float.
Now rather than writing custom float logic, we're going to be lazy and let the .NET framework
to the conversion for us! That is, each of the components will be turned into a string,
concatenated, and the whole string parsed into a float.
The problem is that some of the components (like the sign and exponent) are optional. Let's
write a helper that converts an option to a string using a passed in function, but if the option
is None return the empty string.
I'm going to call it |>? but it doesn't really matter because it is only used locally within the
jNumber parser.
1256
It's pretty crude, and converting things to strings can be slow, so feel free to write a better
version.
With that, we have everything we need for the complete jNumber function:
/// Parse a JNumber
let jNumber =
// set up the "primitive" parsers
let optSign = opt (pchar '-')
let zero = pstring "0"
let digitOneNine =
satisfy (fun ch -> Char.IsDigit ch && ch <> '0') "1-9"
let digit =
satisfy (fun ch -> Char.IsDigit ch ) "digit"
let point = pchar '.'
let e = pchar 'e' <|> pchar 'E'
let optPlusMinus = opt (pchar '-' <|> pchar '+')
let nonZeroInt =
digitOneNine .>>. manyChars digit
|>> fun (first,rest) -> string first + rest
1257
It's a bit long-winded, but each component follows the spec, so I think it is still quite
readable.
Let's start testing it:
run jNumber "123" // JNumber 123.0
run jNumber "-123" // JNumber -123.0
run jNumber "123.4" // JNumber 123.4
1258
1259
// exponent only
run jNumber_ "123e4" // JNumber 1230000.0
// fraction and exponent
run jNumber_ "123.4e5" // JNumber 12340000.0
run jNumber_ "123.4e-5" // JNumber 0.001234
5. Parsing Array
Next up is the Array case. Again, we can use the railway diagram to guide the
implementation:
We will start with the primitives again. Note that we are adding optional whitespace after
each token:
let jArray =
let left = pchar '[' .>> spaces
let right = pchar ']' .>> spaces
let comma = pchar ',' .>> spaces
let value = jValue .>> spaces
And then we create a list of values separated by a comma, with the whole list between the
left and right brackets.
let jArray =
...
// set up the list parser
let values = sepBy1 value comma
// set up the main parser
between left values right
|>> JArray
<?> "array"
let jArray =
...
let value = jValue .>> spaces // <=== what is "jValue"?
...
Well, the spec says that an Array can contain a list of values, so we'll assume that we have
a jValue parser that can parse them.
But to parse a JValue , we need to parse a Array first!
We have hit a common problem in parsing -- mutually recursive definitions. We need a
JValue parser to build an Array , but we need an Array parser to build a JValue .
Forward references
The trick is to create a forward reference, a dummy JValue parser that we can use right
now to define the Array parser, and then later on, we will fix up the forward reference with
the "real" JValue parser.
This is one time where mutable references come in handy!
We will need a helper function to assist us with this, and the logic will be as follows:
Define a dummy parser that will be replaced later.
Define a real parser that forwards the input stream to the dummy parser.
Return both the real parser and a reference to the dummy parser.
Now when the client fixes up the reference, the real parser will forward the input to the new
parser that has replaced the dummy parser.
Here's the code:
1261
let createParserForwardedToRef<'a>() =
let dummyParser=
let innerFn input : Result<'a * Input> = failwith "unfixed forwarded parser"
{parseFn=innerFn; label="unknown"}
// ref to placeholder Parser
let parserRef = ref dummyParser
// wrapper Parser
let innerFn input =
// forward input to the placeholder
runOnInput !parserRef input
let wrapperParser = {parseFn=innerFn; label="unknown"}
wrapperParser, parserRef
With this in place, we can create a placeholder for a parser of type JValue :
let jValue,jValueRef = createParserForwardedToRef<JValue>()
If we try to test it now, we get an exception because we haven't fixed up the reference:
1262
So for now, let's fix up the reference to use one of the parsers that we have already created,
such as jNumber :
jValueRef := jNumber
Now we can successfully test the jArray function, as long as we are careful to only use
numbers in our array!
run jArray "[ 1, 2 ]"
// Success (JArray [JNumber 1.0; JNumber 2.0],
run jArray "[ 1, 2, ]" |> printResult
// Line:0 Col:6 Error parsing array
// [ 1, 2, ]
// ^Unexpected ','
6. Parsing Object
The parser for Object is very similar to the one for Array .
First, the railway diagram:
Using this, we can create the parser directly, so I'll present it here without comment:
1263
let jObject =
// set up the "primitive" parsers
let left = pchar '{' .>> spaces
let right = pchar '}' .>> spaces
let colon = pchar ':' .>> spaces
let comma = pchar ',' .>> spaces
let key = quotedString .>> spaces
let value = jValue .>> spaces
// set up the list parser
let keyValue = (key .>> colon) .>>. value
let keyValues = sepBy1 keyValue comma
// set up the main parser
between left keyValues right
|>> Map.ofList // convert the list of keyValues into a Map
|>> JObject // wrap in JObject
<?> "object" // add label
A bit of testing to make sure it works (but remember, only numbers are supported as values
for now).
run jObject """{ "a":1, "b" : 2 }"""
// JObject (map [("a", JNumber 1.0); ("b", JNumber 2.0)]),
run jObject """{ "a":1, "b" : 2, }""" |> printResult
// Line:0 Col:18 Error parsing object
// { "a":1, "b" : 2, }
// ^Unexpected ','
1264
1265
1266
JObject(map
[("widget",JObject(map
[("debug", JString "on");
("image",JObject(map
[("alignment", JString "center");
("hOffset", JNumber 250.0); ("name", JString "sun1");
("src", JString "Images/Sun.png");
("vOffset", JNumber 250.0)]));
("text",JObject(map
[("alignment", JString "center");
("data", JString "Click Here");
("hOffset", JNumber 250.0);
("name", JString "text1");
("onMouseUp", JString "sun1.opacity = (sun1.opacity / 100) * 90;")
;
("size", JNumber 36.0);
("style", JString "bold");
("vOffset", JNumber 100.0)]));
("window",JObject(map
[("height", JNumber 500.0);
("name", JString "main_window");
("title", JString "Sample Konfabulator Widget");
("width", JNumber 500.0)]))]))]),
1267
// ======================================
// Forward reference
// ======================================
/// Create a forward reference
let createParserForwardedToRef<'a>() =
let dummyParser=
let innerFn input : Result<'a * Input> = failwith "unfixed forwarded parser"
{parseFn=innerFn; label="unknown"}
// ref to placeholder Parser
let parserRef = ref dummyParser
// wrapper Parser
let innerFn input =
// forward input to the placeholder
runOnInput !parserRef input
let wrapperParser = {parseFn=innerFn; label="unknown"}
wrapperParser, parserRef
let jValue,jValueRef = createParserForwardedToRef<JValue>()
// ======================================
// Utility function
// ======================================
// applies the parser p, ignores the result, and returns x.
let (>>%) p x =
1268
// ======================================
// Parsing a JString
// ======================================
/// Parse an unescaped char
let jUnescapedChar =
satisfy (fun ch -> ch <> '\\' && ch <> '\"') "char"
/// Parse an escaped char
let jEscapedChar =
[
// (stringToMatch, resultChar)
("\\\"",'\"') // quote
("\\\\",'\\') // reverse solidus
("\\/",'/') // solidus
("\\b",'\b') // backspace
("\\f",'\f') // formfeed
("\\n",'\n') // newline
("\\r",'\r') // cr
("\\t",'\t') // tab
]
// convert each pair into a parser
|> List.map (fun (toMatch,result) ->
pstring toMatch >>% result)
// and combine them into one
1269
|> choice
/// Parse a unicode char
let jUnicodeChar =
// set up the "primitive" parsers
let backslash = pchar '\\'
let uChar = pchar 'u'
let hexdigit = anyOf (['0'..'9'] @ ['A'..'F'] @ ['a'..'f'])
// convert the parser output (nested tuples)
// to a char
let convertToChar (((h1,h2),h3),h4) =
let str = sprintf "%c%c%c%c" h1 h2 h3 h4
Int32.Parse(str,Globalization.NumberStyles.HexNumber) |> char
// set up the main parser
backslash >>. uChar >>. hexdigit .>>. hexdigit .>>. hexdigit .>>. hexdigit
|>> convertToChar
1270
1271
let jArray =
// set up the "primitive" parsers
let left = pchar '[' .>> spaces
let right = pchar ']' .>> spaces
let comma = pchar ',' .>> spaces
let value = jValue .>> spaces
// set up the list parser
let values = sepBy1 value comma
// set up the main parser
between left values right
|>> JArray
<?> "array"
// ======================================
// Parsing a JObject
// ======================================
let jObject =
// set up the "primitive" parsers
let left = pchar '{' .>> spaces
let right = pchar '}' .>> spaces
let colon = pchar ':' .>> spaces
let comma = pchar ',' .>> spaces
let key = quotedString .>> spaces
let value = jValue .>> spaces
// set up the list parser
let keyValue = (key .>> colon) .>>. value
let keyValues = sepBy1 keyValue comma
// set up the main parser
between left keyValues right
|>> Map.ofList // convert the list of keyValues into a Map
|>> JObject // wrap in JObject
<?> "object" // add label
// ======================================
// Fixing up the jValue ref
// ======================================
// fixup the forward ref
jValueRef := choice
[
jNull
jBool
jNumber
jString
1272
jArray
jObject
]
Summary
In this post, we built a JSON parser using the parser library that we have developed over the
previous posts.
I hope that, by building both the parser library and a real-world parser from scratch, you
have gained a good appreciation for how parser combinators work, and how useful they are.
I'll repeat what I said in the first post: if you are interesting in using this technique in
production, be sure to investigate the FParsec library for F#, which is optimized for realworld usage.
And if you are using languages other than F#, there is almost certainly a parser combinator
library available to use.
For more information about parser combinators in general, search the internet for
"Parsec", the Haskell library that influenced FParsec.
For some more examples of using FParsec, try one of these posts:
Implementing a phrase search query for FogCreek's Kiln
A LOGO Parser
A Small Basic Parser
A C# Parser and building a C# compiler in F#
Write Yourself a Scheme in 48 Hours in F#
Parsing GLSL, the shading language of OpenGL
Thanks!
The source code for this post is available at this gist.
1273
In this series of posts, I'll look at how you can thread state through a series of pure functions
in a convenient way.
To start with, I'll tell the story of Dr Frankenfunctor and the Monadster, and how the Doctor
needed a way to create "recipes" that were activated when lightning struck.
The Doctor then devised ways to work with these recipes using functions such as map ,
bind and apply .
In the final post, we'll see how we can use a computation expression to make the coding
cleaner, and how these techniques can be generalized into the so-called "state monad".
Warning! These posts contains gruesome topics, strained analogies, discussion of monads
Dr Frankenfunctor and the Monadster. Or, how a 19th century scientist nearly invented
the state monad.
Completing the body of the Monadster. Dr Frankenfunctor and the Monadster, part 2.
Refactoring the Monadster. Dr Frankenfunctor and the Monadster, part 3.
1274
CAPTION: The terrible events at the 1990 ACM Conference on LISP and Functional
Programming.
I will not repeat the details here; the story is still too terrible to recall.
But in all the millions of words devoted to this tragedy, one topic has never been
satisfactorily addressed.
How was the creature assembled and brought to life?
We know that Dr Frankenfunctor built the creature from dead body parts, and then animated
them in a single instant, using a bolt of lightning to create the vital force.
But the various body parts had to be assembled into a whole, and the vital force had to be
transmitted through the assembly in the appropriate manner, and all this done in a split
second, in the moment that the lightning struck.
1275
I have devoted many years of research into this matter, and recently, at great expense, I
have managed to obtain Dr Frankenfunctor's personal laboratory notebooks.
So at last, I can present Dr Frankenfunctor's technique to the world. Use it as you will. I do
not make any judgements as to its morality, after all, it is not for mere developers to question
the real-world effects of what we build.
Background
To start with, you need to understand the fundamental process involved.
First, you must know that no whole body was available to Dr Frankenfunctor. Instead, the
creature was created from an assemblage of body parts -- arms, legs, brain, heart -- whose
provenances were murky and best left unspoken.
Dr Frankenfunctor started with a dead body part, and infused it with some amount of vital
force. The result was two things: a now live body part, and the remaining, diminished, vital
force, because of course some of the vital force was transferred to the live part.
Here is a diagram demonstrating the principle:
But this creates only one body part. How can we create more than one? This is the
challenge that faced Dr Frankenfunctor.
The first problem is that we only have a limited quantity of the vital force. This means that
when we need to animate a second body part, we have available only the remaining vital
force from a previous step.
How can we connect the two steps together so that the vital force from the first step is fed
into the input of the second step?
1276
Even if we have chained the steps correctly, we need to take the various live body parts and
combine them somehow. But we only have access to live body parts during the moment of
creation. How can we combine them in that split second?
It was Dr Frankenfunctor's genius that led to an elegant approach that solved both of these
problems, the approach that I will present to you now.
1277
Since we will be using vital force frequently, we will create a function that extracts one unit
and returns a tuple of the unit and remaining force.
let getVitalForce vitalForce =
let oneUnit = {units = 1}
let remaining = {units = vitalForce.units-1} // decrement
oneUnit, remaining // return both
From this leg, a live leg could be created with the same label and one unit of vital force.
type LiveLeftLeg = LiveLeftLeg of Label * VitalForce
The type signature for the creation function would thus look like this:
type MakeLiveLeftLeg =
DeadLeftLeg * VitalForce -> LiveLeftLeg * VitalForce
1278
As you can see, this implementation matched the earlier diagram precisely.
The second insight was that this same code can be interpreted as a function that in turn
returns a "becomeAlive" function.
That is, we have the dead part on hand, but we won't have any vital force until the final
moment, so why not process the dead part right now and return a function that can be used
when the vital force becomes available.
In other words, we pass in a dead part, and we get back a function that creates a live part
when given some vital force.
1279
These "become alive" functions can then be treated as "steps in a recipe", assuming we can
find some way of combining them.
The code looks like this now:
type MakeLiveLeftLeg =
DeadLeftLeg -> (VitalForce -> LiveLeftLeg * VitalForce)
let makeLiveLeftLeg deadLeftLeg =
// create an inner intermediate function
let becomeAlive vitalForce =
let (DeadLeftLeg label) = deadLeftLeg
let oneUnit, remainingVitalForce = getVitalForce vitalForce
let liveLeftLeg = LiveLeftLeg (label,oneUnit)
liveLeftLeg, remainingVitalForce
// return it
becomeAlive
It may not be obvious, but this is exactly the same code as the previous version, just written
slightly differently.
This curried function (with two parameters) can be interpreted as a normal two parameter
function, or it can be interpreted as a one parameter function that returns another one
parameter function.
If this is not clear, consider the much simpler example of a two parameter add function:
let add x y =
x + y
Because F# curries functions by default, that implementation is exactly the same as this one:
let add x =
fun y -> x + y
Which, if we define an intermediate function, is also exactly the same as this one:
let add x =
let addX y = x + y
addX // return the function
All those functions will return a function that has a signature like: VitalForce -> LiveBodyPart
* VitalForce .
To make our life easy, let's give that function signature a name, M , which stands for
"Monadster part generator", and give it a generic type parameter 'LiveBodyPart so that we
can use it with many different body parts.
type M<'LiveBodyPart> =
VitalForce -> 'LiveBodyPart * VitalForce
We can now explicitly annotate the return type of the makeLiveLeftLeg function with
:M<LiveLeftLeg> .
The rest of the function is unchanged because the becomeAlive return value is already
compatible with M<LiveLeftLeg> .
But I don't like having to explicitly annotate all the time. How about we wrap the function in a
single case union -- call it "M" -- to give it its own distinct type? Like this:
type M<'LiveBodyPart> =
M of (VitalForce -> 'LiveBodyPart * VitalForce)
That way, we can distinguish between a "Monadster part generator" and an ordinary function
returning a tuple.
To use this new definition, we need to tweak the code to wrap the intermediate function in
the single case union M when we return it, like this:
let makeLiveLeftLegM deadLeftLeg =
let becomeAlive vitalForce =
let (DeadLeftLeg label) = deadLeftLeg
let oneUnit, remainingVitalForce = getVitalForce vitalForce
let liveLeftLeg = LiveLeftLeg (label,oneUnit)
liveLeftLeg, remainingVitalForce
// changed!
M becomeAlive // wrap the function in a single case union
1281
For this last version, the type signature will be correctly inferred without having to specify it
explicitly: a function that takes a dead left leg and returns an "M" of a live leg:
val makeLiveLeftLegM : DeadLeftLeg -> M<LiveLeftLeg>
Note that I've renamed the function makeLiveLeftLegM to make it clear that it returns a M of
LiveLeftLeg .
The meaning of M
So what does this "M" type mean exactly? How can we make sense of it?
One helpful way is to think of a M<T> as a recipe for creating a T . You give me some vital
force and I'll give you back a T .
But how can an M<T> create a T out of nothing?
That's where functions like makeLiveLeftLegM are critically important. They take a parameter
and "bake" it into the result. As a result, you will see lots of "M-making" functions with similar
signatures, all looking something like this:
Or in code terms:
DeadPart -> M<LivePart>
What is leftLegM ? It's a recipe for creating a live left leg, given some vital force.
What's useful is that we can create this recipe up front, before the lightning strikes.
1282
Now let's pretend that the storm has arrived, the lightning has struck, and 10 units of vital
force are now available:
let vf = {units = 10}
Now, inside the leftLegM is a function which we can apply to the vital force. But first we
need to get the function out of the wrapper using pattern matching.
let (M innerFn) = leftLegM
And then we can run the inner function to get the live left leg and the remaining vital force:
let liveLeftLeg, remainingAfterLeftLeg = innerFn vf
You can see that a LiveLeftLeg was created successfully and that the remaining vital force
is reduced to 9 units now.
This pattern matching is awkward, so let's create a helper function that both unwraps the
inner function and calls it, all in one go.
We'll call it runM and it looks like this:
let runM (M f) vitalForce = f vitalForce
So now, finally, we have a function that can create a live left leg.
It took a while to get it working, but we've also built some useful tools and concepts that we
can use moving forwards.
Now that we know what we are doing, we should be able to use the same techniques for the
other body parts now.
How about a right leg then?
Unfortunately, according to the notebook, Dr Frankenfunctor could not find a right leg in the
laboratory. The problem was solved with a hack... but we'll come to that later.
The challenge was therefore this: how can we make a live left arm out the material we have
on hand?
First, we have to rule out creating a LiveLeftArm from a DeadLeftUnbrokenArm , as there isn't
any such thing. Nor can we convert a DeadLeftBrokenArm into a healthy LiveLeftArm
directly.
1284
But what we can do is turn the DeadLeftBrokenArm into a live broken arm and then heal the
live broken arm, yes?
No, I'm afraid that won't work. We can't create live parts directly, we can only create live
parts in the context of the M recipe.
What we need to do then is create a special version of healBrokenArm (call it
healBrokenArmM ) that converts a M<LiveBrokenArm> to a M<LiveArm> .
But how do we create such a function? And how can we reuse healBrokenArm as part of it?
Let's start with the most straightforward implementation.
First, since the function will return an M something, it will have the same form as the
makeLiveLeftLegM function that we saw earlier. We'll need to create an inner function that
Simple, just run it with some vitalForce. And where are we going to get the vitalForce from?
From the parameter to the inner function!
So our finished version will look like this:
1285
// implementation of HealBrokenArm
let healBrokenArm (LiveLeftBrokenArm (label,vf)) = LiveLeftArm (label,vf)
/// convert a M<LiveLeftBrokenArm> into a M<LiveLeftArm>
let makeHealedLeftArm brokenArmM =
// create a new inner function that takes a vitalForce parameter
let healWhileAlive vitalForce =
// run the incoming brokenArmM with the vitalForce
// to get a broken arm
let brokenArm,remainingVitalForce = runM brokenArmM vitalForce
// heal the broken arm
let healedArm = healBrokenArm brokenArm
// return the healed arm and the remaining VitalForce
healedArm, remainingVitalForce
// wrap the inner function and return it
M healWhileAlive
1286
What's amazing about this is that by parameterizing that one transformation with the f
parameter, the whole function becomes generic!
We haven't made any other changes, but the signature for makeGenericTransform no longer
refers to arms. It works with anything!
val makeGenericTransform : f:('a -> 'b) -> M<'a> -> M<'b>
Introducing mapM
Since it is so generic now, the names are confusing. Let's rename it. I'll call it mapM . It works
with any body part and any transformation.
Here's the implementation, with the internal names fixed up too.
let mapM f bodyPartM =
let transformWhileAlive vitalForce =
let bodyPart,remainingVitalForce = runM bodyPartM vitalForce
let updatedBodyPart = f bodyPart
updatedBodyPart, remainingVitalForce
M transformWhileAlive
In particular, it works with the healBrokenArm function, so to create a version of "heal" that
has been lifted to work with M s we can just write this:
let healBrokenArmM = mapM healBrokenArm
1287
Functions similar to mapM crop up in many situations. For example, Option.map transforms
a "normal" function into a function whose inputs and outputs are options. Similarly,
List.map transforms a "normal" function into a function whose inputs and outputs are lists.
What might be new to you is that the "wrapper" type M contains a function, not a simple
data structure like Option or List. That might make your head hurt!
In addition, the diagram above implies that M could wrap any normal type and mapM could
map any normal function.
Let's try it and see!
1288
Now we can use mapM and healBrokenArm to convert the M<BrokenLeftArm> into a
M<LeftArm> :
What we have now in leftArmM is a recipe for creating a unbroken and live left arm. All we
need to do is add some vital force.
As before, we can do all these things up front, before the lightning strikes.
Now when the storm arrives, and the lightning has struck, and vital force is available, we can
run leftArmM with the vital force...
1289
Dr Frankenfunctor decided to do surgery to join the two arm sections into a whole arm.
// define the whole arm
type LiveRightArm = {
lowerArm : LiveRightLowerArm
upperArm : LiveRightUpperArm
}
// surgery to combine the two arm parts
let armSurgery lowerArm upperArm =
{lowerArm=lowerArm; upperArm=upperArm}
As with the broken arm, the surgery could only be done with live parts. Doing that with dead
parts would be yucky and gross.
1290
But also, as with the broken arm, we don't have access to the live parts directly, only within
the context of an M wrapper.
In other words we need to convert our armSurgery function that works with normal live
parts, and convert it into a armSurgeryM function that works with M s.
1291
One big difference from the broken arm example is that we have two parameters, of course.
When we run the second parameter (to get the liveUpperArm ), we must be sure to pass in
the remaining vital force after the first step, not the original one.
And then, when we return from the inner function, we must be sure to return
remainingVitalForce2 (the remainder after the second step) not any other one.
Introducing map2M
But as before, why not make this more generic? We don't need to hard-code armSurgery -we can pass it as a parameter.
We'll call the more generic function map2M -- just like mapM but with two parameters.
Here's the implementation:
let map2M f m1 m2 =
let becomeAlive vitalForce =
let v1,remainingVitalForce = runM m1 vitalForce
let v2,remainingVitalForce2 = runM m2 remainingVitalForce
let v3 = f v1 v2
v3, remainingVitalForce2
M becomeAlive
Just as with mapM we can interpret this function as a "function converter" that converts a
"normal" two parameter function into a function in the world of M .
1292
By the way, are you noticing that there is a lot of duplication in these functions? Me too! We
will attempt to fix that later.
Next, we'll create the parts:
1293
As always, we can do all these things up front, before the lightning strikes, building a recipe
(or computation if you like) that will do everything we need when the time comes.
When the vital force is available, we can run rightArmM with the vital force...
let vf = {units = 10}
let liveRightArm, remainingFromRightArm = runM rightArmM vf
Summary
In this post, we saw how to create a M type that wrapped a "become alive" function that in
turn could only be activated when lightning struck.
We also saw how various M-values could be processed and combined using mapM (for the
broken arm) and map2M (for the arm in two parts).
The code samples used in this post are available on GitHub.
1294
Next time
This exciting tale has more shocks in store for you! Stay tuned for the next installment, when
I reveal how the head and body were created.
1295
The Head
First, the head.
Just like the right arm, the head is composed of two parts, a brain and a skull.
Dr Frankenfunctor started by defining the dead brain and skull:
type DeadBrain = DeadBrain of Label
type Skull = Skull of Label
Unlike the two-part right arm, only the brain needs to become alive. The skull can be used
as is and does not need to be transformed before being used in a live head.
type LiveBrain = LiveBrain of Label * VitalForce
type LiveHead = {
brain : LiveBrain
skull : Skull // not live
}
The live brain is combined with the skull to make a live head using a headSurgery function,
analogous to the armSurgery we had earlier.
1296
Now we are ready to create a live head -- but how should we do it?
It would be great if we could reuse map2M , but there's a catch -- for map2M to work, it needs
a skull wrapped in a M .
But the skull doesn't need to become alive or use vital force, so we will need to create a
special function that converts a Skull to a M<Skull> .
We can use the same approach as we did before:
create a inner function that takes a vitalForce parameter
in this case, we leave the vitalForce untouched
from the inner function return the original skull and the untouched vitalForce
wrap the inner function in an "M" and return it
Here's the code:
let wrapSkullInM skull =
let becomeAlive vitalForce =
skull, vitalForce
M becomeAlive
Introducing returnM
We've created a completely generic function that will turn anything into an M . So let's
rename it. I'm going to call it returnM , but in other contexts it might be called pure or
unit .
1297
let returnM x =
let becomeAlive vitalForce =
x, vitalForce
M becomeAlive
By the way, how this particular dead brain was obtained is an interesting story that I don't
have time to go into right now.
1298
Once again, we can do all these things up front, before the lightning strikes.
When the vital force is available, we can run headM with the vital force...
let vf = {units = 10}
let liveHead, remainingFromHead = runM headM vf
But the creature needs more than a live heart -- it needs a beating heart. A beating heart is
constructed from a live heart and some more vital force, like this:
type BeatingHeart = BeatingHeart of LiveHeart * VitalForce
The code that creates a live heart is very similar to the previous examples:
1299
The code that creates a beating heart is also very similar. It takes a live heart as a
parameter, uses up another unit of vital force, and returns the beating heart and the
remaining vital force.
let makeBeatingHeart liveHeart =
let becomeAlive vitalForce =
let oneUnit, remainingVitalForce = getVitalForce vitalForce
let beatingHeart = BeatingHeart (liveHeart, oneUnit)
beatingHeart, remainingVitalForce
M becomeAlive
If we look at the signatures for these functions, we see that they are very similar; both of the
form Something -> M<SomethingElse> .
val makeLiveHeart : DeadHeart -> M<LiveHeart>
val makeBeatingHeart : LiveHeart -> M<BeatingHeart>
1300
What we want then, is a function that, given a M<LiveHeart> as input, can convert it to a
M<BeatingHeart> .
And furthermore, we want to build it from the makeBeatingHeart function we already have.
Here's a first attempt, using the same pattern we've used many times before:
let makeBeatingHeartFromLiveHeartM liveHeartM =
let becomeAlive vitalForce =
// extract the liveHeart from liveHeartM
let liveHeart, remainingVitalForce = runM liveHeartM vitalForce
// use the liveHeart to create a beatingHeartM
let beatingHeartM = makeBeatingHeart liveHeart
// what goes here?
// return a beatingHeart and remaining vital force
beatingHeart, remainingVitalForce
M becomeAlive
But what goes in the middle? How can we get a beating heart from a beatingHeartM ? The
answer is to run it with some vital force (which we happen to have on hand, because we are
in the middle of the becomeAlive function).
What vital force though? It should be the remaining vital force after getting the liveHeart .
1301
Notice that we return remainingVitalForce2 at the end, the remainder after both steps are
run.
If we look at the signature for this function, it is:
M<LiveHeart> -> M<BeatingHeart>
Introducing bindM
Once again, we can make this function generic by passing in a function parameter rather
than hardcoding makeBeatingHeart .
I'll call it bindM . Here's the code:
let bindM f bodyPartM =
let becomeAlive vitalForce =
let bodyPart, remainingVitalForce = runM bodyPartM vitalForce
let newBodyPartM = f bodyPart
let newBodyPart, remainingVitalForce2 = runM newBodyPartM remainingVitalForce
newBodyPart, remainingVitalForce2
M becomeAlive
1302
In other words, given any function Something -> M<SomethingElse> , I can convert it to a
function M<Something> -> M<SomethingElse> that has an M as input and output.
By the way, functions with a signature like Something -> M<SomethingElse> are often called
monadic functions.
Anyway, once you understand what is going on in bindM , a slightly shorter version can be
implemented like this:
let bindM f bodyPartM =
let becomeAlive vitalForce =
let bodyPart, remainingVitalForce = runM bodyPartM vitalForce
runM (f bodyPart) remainingVitalForce
M becomeAlive
There are a lot of intermediate values in there, and it can be made simpler by using piping,
like this:
1303
let beatingHeartM =
DeadHeart "Anne"
|> makeLiveHeart
|> bindM makeBeatingHeart
1304
When the vital force is available, we can run beatingHeartM with the vital force...
let vf = {units = 10}
let beatingHeart, remainingFromHeart = runM beatingHeartM vf
Note that the remaining vital force is eight units, as we used up two units doing two steps.
1305
type LiveBody = {
leftLeg: LiveLeftLeg
rightLeg : LiveLeftLeg
leftArm : LiveLeftArm
rightArm : LiveRightArm
head : LiveHead
heart : BeatingHeart
}
You can see that it uses all the subcomponents that we have already developed.
0:00 / 2:04
The LiveBody type has six fields. How can we construct it from the various M<BodyPart> s
that we have?
One way would be to repeat the technique that we used with mapM and map2M . We could
create a map3M and map4M and so on.
For example, map3M could be defined like this:
let map3M f m1 m2 m3 =
let becomeAlive vitalForce =
let v1,remainingVitalForce = runM m1 vitalForce
let v2,remainingVitalForce2 = runM m2 remainingVitalForce
let v3,remainingVitalForce3 = runM m3 remainingVitalForce2
let v4 = f v1 v2 v3
v4, remainingVitalForce3
M becomeAlive
we can actually think of it as a one parameter function that returns a five parameter function,
like this:
val createBody :
leftLeg:LiveLeftLeg -> (five param function)
and then when we apply the function to the first parameter ("leftLeg") we get back that five
parameter function:
1307
(six param function) apply (first parameter) returns (five param function)
This five parameter function can in turn be thought of as a one parameter function that
returns a four parameter function:
rightLeg:LiveLeftLeg -> (four parameter function)
Again, we can apply a first parameter ("rightLeg") and get back that four parameter function:
(five param function) apply (first parameter) returns (four param function)
And so on and so on, until eventually we get a function with one parameter. The function will
have the signature BeatingHeart -> LiveBody .
When we apply the final parameter ("beatingHeart") then we get back our completed
LiveBody .
1308
// normal version
(six param function) apply (first parameter) returns (five param function)
// M-world version
M<six param function> applyM M<first parameter> returns M<five param function>
And then doing that again, we can apply the next M-parameter
// normal version
(five param function) apply (first parameter) returns (four param function)
// M-world version
M<five param function> applyM M<first parameter> returns M<four param function>
and so on, applying the parameters one by one until we get the final result.
Introducing applyM
This applyM function will have two parameters then, a function wrapped in an M, and a
parameter wrapped in an M. The output will be the result of the function wrapped in an M.
Here's the implementation:
let applyM mf mx =
let becomeAlive vitalForce =
let f,remainingVitalForce = runM mf vitalForce
let x,remainingVitalForce2 = runM mx remainingVitalForce
let y = f x
y, remainingVitalForce2
M becomeAlive
As you can see it is quite similar to map2M , except that the "f" comes from unwrapping the
first parameter itself.
Let's try it out!
First we need our six parameter function:
1309
And we're going to need to clone the left leg to use it for the right leg:
let rightLegM = leftLegM
let bodyM =
returnM createBody
<*> leftLegM
<*> rightLegM
<*> leftArmM
<*> rightArmM
<*> headM
<*> beatingHeartM
...we can get rid of the returnM as well and write the code like this:
let bodyM =
createBody
<!> leftLegM
<*> rightLegM
<*> leftArmM
<*> rightArmM
<*> headM
<*> beatingHeartM
What's nice about this is that it reads almost as if you were just calling the original function
(once you get used to the symbols!)
1311
Summary
In this post, we extended our repertoire of manipulation techniques to include:
1312
Next time
In the final installment, we'll refactor the code and review all the techniques used.
1313
The M type
We couldn't create an actual live body part until the vital force was available, yet we wanted
to manipulate them, combine them, etc. before the lightning struck. We did this by creating a
type M that wrapped a "become alive" function for each part. We could then think of
M<BodyPart> as a recipe or instructions for creating a BodyPart when the time came.
1314
mapM
Next, we wanted to transform the contents of an M without using any vital force. In our
specific case, we wanted to turn a broken arm recipe ( M<BrokenLeftArm> ) into a unbroken
arm recipe ( M<LeftArm> ). The solution was to implement a function mapM that took a
normal function 'a -> 'b and turned it into a M<'a> -> M<'b> function.
The signature of mapM was:
val mapM : f:('a -> 'b) -> M<'a> -> M<'b>
map2M
We also wanted to combine two M-recipes to make a new one. In that particular case, it was
combining an upper arm ( M<UpperRightArm> ) and lower arm ( M<LowerRightArm> ) into a
whole arm ( M<RightArm> ). The solution was map2M .
The signature of map2M was:
val map2M : f:('a -> 'b -> 'c) -> M<'a> -> M<'b> -> M<'c>
returnM
Another challenge was to lift a normal value into the world of M-recipes directly, without
using any vital force. In that particular case, it was turning a Skull into an M<Skull> so it
could be used with map2M to make a whole head.
The signature of returnM was:
val returnM : 'a -> M<'a>
Monadic functions
We created many functions that had a similar shape. They all take something as input and
return a M-recipe as output. In other words, they have this signature:
val monadicFunction : 'a -> M<'b>
1315
bindM
The functions up to now did not require access to the vital force. But then we found that we
needed to chain two monadic functions together. In particular, we needed to chain the output
of makeLiveHeart (with signature DeadHeart -> M<LiveHeart> ) to the input of
makeBeatingHeart (with signature LiveHeart -> M<BeatingHeart> ). The solution was bindM ,
which transforms monadic functions of the form 'a -> M<'b> into functions in the M-world
( M<'a> -> M<'b> ) which could then be composed together.
The signature of bindM was:
val bindM : f:('a -> M<'b>) -> M<'a> -> M<'b>
applyM
Finally, we needed a way to combine a large number of M-parameters to make the live body.
Rather than having to create special versions of map ( map4M , map5M , map6M , etc), we
implemented a generic applyM function that could apply an M-function to an M-parameter.
From that, we could work with a function of any size, step by step, using partial application to
apply one M-parameter at a time.
The signature of applyM was:
val applyM : M<('a -> 'b)> -> M<'a> -> M<'b>
1316
When this is done, we have access to the special syntax monster {...} , just like
async{...} , seq{...} , etc.
The let! x = xM syntax requires that the right side is an M-type, say M<X> .
let! unwraps the M<X> into an X and binds it to the left side -- "x" in this case.
The return y syntax requires that return value is a "normal" type, Y say.
return wraps it into a M<Y> (using returnM ) and returns it as the overall value of the
monster expression.
1317
monster {
let! x = xM // unwrap an M<X> into an X and bind to "x"
return y // wrap a Y and return an M<Y>
}
If you want more on computation expressions, I have an in-depth series of posts about them.
value.
Here is an implementation using monster :
let mapM f xM =
monster {
let! x = xM // unwrap the M<X>
return f x // return M of (f x)
}
map2M
map2M takes a function and two wrapped M-values and returns the function applied to both
the values.
It's also easy to write using monster expressions:
let map2M f xM yM =
monster {
let! x = xM // unwrap M<X>
let! y = yM // unwrap M<Y>
return f x y // return M of (f x y)
}
If we compile this implementation, we again get the same signature as the previous
implementation:
1318
val map2M : f:('a -> 'b -> 'c) -> M<'a> -> M<'b> -> M<'c>
applyM
applyM takes a wrapped function and a wrapped value and returns the function applied to
the value.
Again, it's trivial to write using monster expressions:
let applyM fM xM =
monster {
let! f = fM // unwrap M<F>
let! x = xM // unwrap M<X>
return f x // return M of (f x)
}
In other words, we're getting some of the vital force and then putting a new vital force to be
used for the next step.
We are familiar with "getters" and "setters" in object-oriented programming, so let's see if we
can write similar ones that will work in the monster context.
1319
Introducing getM
Let's start with the getter. How should we implement it?
Well, the vital force is only available in the context of becoming alive, so the function must
follow the familiar template:
let getM =
let doSomethingWhileLive vitalForce =
// what here ??
what to return??, vitalForce
M doSomethingWhileLive
Note that getting the vitalForce doesn't use any up, so the original amount can be
returned untouched.
But what should happen in the middle? And what should be returned as the first element of
the tuple?
The answer is simple: just return the vital force itself!
let getM =
let doSomethingWhileLive vitalForce =
// return the current vital force in the first element of the tuple
vitalForce, vitalForce
M doSomethingWhileLive
getM is a M<VitalForce> value, which means that we can unwrap it inside a monster
Introducing putM
For the putter, the implementation is a function with a parameter for the new vital force.
let putM newVitalForce =
let doSomethingWhileLive vitalForce =
what here ??
M doSomethingWhileLive
1320
With getM and putM in place, we can now create a function that
gets the current vital force from the context
extracts one unit from that
replaces the current vital force with the remaining vital force
returns the one unit of vital force to the caller
And here's the code:
let useUpOneUnitM =
monster {
let! vitalForce = getM
let oneUnit, remainingVitalForce = getVitalForce vitalForce
do! putM remainingVitalForce
return oneUnit
}
1321
The new version, using a monster expression, has implicit handling of the vital force, and
consequently looks much cleaner.
let makeLiveLeftLegM deadLeftLeg =
monster {
let (DeadLeftLeg label) = deadLeftLeg
let! oneUnit = useUpOneUnitM
return LiveLeftLeg (label,oneUnit)
}
Similarly, we can rewrite all the arm surgery code like this:
let makeLiveRightLowerArm (DeadRightLowerArm label) =
monster {
let! oneUnit = useUpOneUnitM
return LiveRightLowerArm (label,oneUnit)
}
let makeLiveRightUpperArm (DeadRightUpperArm label) =
monster {
let! oneUnit = useUpOneUnitM
return LiveRightUpperArm (label,oneUnit)
}
// create the M-parts
let lowerRightArmM = DeadRightLowerArm "Tom" |> makeLiveRightLowerArm
let upperRightArmM = DeadRightUpperArm "Jerry" |> makeLiveRightUpperArm
// turn armSurgery into an M-function
let armSurgeryM = map2M armSurgery
// do surgery to combine the two M-parts into a new M-part
let rightArmM = armSurgeryM lowerRightArmM upperRightArmM
1322
We can use this approach for the head as well. We don't need headSurgery or returnM any
more.
let headM = monster {
let! brain = makeLiveBrain deadBrain
return {brain=brain; skull=skull}
}
Finally, we can use a monster expression to create the whole body too:
// a function to create a M-body given all the M-parts
let createBodyM leftLegM rightLegM leftArmM rightArmM headM beatingHeartM =
monster {
let! leftLeg = leftLegM
let! rightLeg = rightLegM
let! leftArm = leftArmM
let! rightArm = rightArmM
let! head = headM
let! beatingHeart = beatingHeartM
// create the record
return {
leftLeg = leftLeg
rightLeg = rightLeg
leftArm = leftArm
rightArm = rightArm
head = head
heart = beatingHeart
}
}
// create the M-body
let bodyM = createBodyM leftLegM rightLegM leftArmM rightArmM headM beatingHeartM
1323
The applyM approach allows the parameters to be run independently or in parallel, while
the monster expression approach requires that the parameters are run in sequence, with
the output of one being fed into the input of the next.
That's not relevant for this scenario, but can be important for other situations such as
validation or async. For example, in a validation context, you may want to collect all the
validation errors at once, rather than only returning the first one that fails.
1324
Instead, I'll just focus on how the state monad might be defined and used in practice.
First off, to be truly reusable, we need to replace the VitalForce type with other types. So
our function-wrapping type (call it S ) must have two type parameters, one for the type of
the state, and one for the type of the value.
type S<'State,'Value> =
S of ('State -> 'Value * 'State)
With this defined, we can create the usual suspects: runS , returnS and bindS .
// encapsulate the function call that "runs" the state
let runS (S f) state = f state
// lift a value to the S-world
let returnS x =
let run state =
x, state
S run
// lift a monadic function to the S-world
let bindS f xS =
let run state =
let x, newState = runS xS state
runS (f x) newState
S run
Personlly, I'm glad that we got to understood how these worked in the M context before
making them completely generic. I don't know about you, but signatures like these
val runS : S<'a,'b> -> 'a -> 'b * 'a
val bindS : f:('a -> S<'b,'c>) -> S<'b,'a> -> S<'b,'c>
getS and putS are defined in a similar way as getM and putM for monster .
1325
let getS =
let run state =
// return the current state in the first element of the tuple
state, state
S run
// val getS : S<State>
let putS newState =
let run _ =
// return nothing in the first element of the tuple
// return the newState in the second element of the tuple
(), newState
S run
// val putS : 'State -> S<unit>
1326
As you can see, the state expression automatically picked up that VitalForce was being
used as the state -- we didn't need to specify it explicitly.
So, if you have a state expression type available to you, you don't need to create your own
expressions like monster at all!
For a more detailed and complex example of the state monad in F#, check out the FSharpx
library.
1327
Note that none of that code knows about or uses the state computation expression.
To make it work with state , we need to define a customized getter and putter for use in a
state context:
1328
Stack-based calculator
Here's a simple stack-based calculator:
let one = state {do! push 1}
let two = state {do! push 2}
let add = state {
let! top1 = pop()
let! top2 = pop()
do! push (top1 + top2)
}
And now we can combine these basic states to build more complex ones:
1329
Remember that, just as with the vital force, all we have now is a recipe for building a stack.
We still need to run it to execute the recipe and get the result.
Let's add a helper to run all the operations and return the top of the stack:
let calculate stackOperations = state {
do! stackOperations
let! top = pop()
return top
}
1330
Finally, a monad (in a programming sense) is a data structure (such as Option, or List, or
State) which has two functions associated with it: bind (often written as >>= ) and
return . And again, these functions have some properties that they must satisfy (the
"monad laws").
Of these three, the monad is most "powerful" in a sense, because the bind function allows
you to chain M-producing functions together, and as we have seen, map and apply can be
written in terms of bind and return .
So you can see that both our original M type and the more generic State type, in
conjunction with their supporting functions, are monads, (assuming that our bind and
return implementations satisfy the monad laws).
For a visual version of these definitions, there is a great post called Functors, Applicatives,
And Monads In Pictures.
Further reading
There are a lot of posts about state monads on the web, of course. Most of them are Haskell
oriented, but I hope that those explanations will make more sense after reading this series of
posts, so I'm only going to mention a few follow up links.
State monad in pictures
"A few monads more", from "Learn You A Haskell"
Much Ado About Monads. Discussion about state monad in F#.
And for another important use of "bind", you might find my talk on functional error handling
useful.
If you want to see F# implementations of other monads, look no further than the FSharpx
project.
Summary
Dr Frankenfunctor was a groundbreaking experimentalist, and I'm glad that I have been able
to share insights on her way of working.
We've seen how she discovered a primitive monad-like type, M<BodyPart> , and how mapM ,
map2M , returnM , bindM and applyM were all developed to solve specific problems.
We've also seen how the need to solve the same problems led to the modern-day state
monad and computation expression.
1331
Anyway, I hope that this series of posts has been enlightening. My not-so-secret wish is that
monads and their associated combinators will no longer be so shocking to you now...
...and that you can use them wisely in your own projects. Good luck!
NOTE: The code samples used in this post are available on GitHub.
1332
In this series of posts, I'll attempt to describe some of the core functions for dealing with
generic data types (such as Option and List ). This is a follow-up post to my talk on
functional patterns.
Yes, I know that I promised not to do this kind of thing, but for this post I thought I'd take a
different approach from most people. Rather than talking about abstractions such as type
classes, I thought it might be useful to focus on the core functions themselves and how they
are used in practice.
In other words, a sort of "man page" for map , return , apply , and bind .
So, there is a section for each function, describing their name (and common aliases),
common operators, their type signature, and then a detailed description of why they are
needed and how they are used, along with some visuals (which I always find helpful).
Understanding map and apply. A toolset for working with elevated worlds.
Understanding bind. Or, how to compose world-crossing functions.
Using the core functions in practice. Working with independent and dependent data.
Understanding traverse and sequence. Mixing lists and elevated values.
Using map, apply, bind and sequence in practice. A real-world example that uses all the
techniques.
Reinventing the Reader monad. Or, designing your own elevated world.
Map and Bind and Apply, a summary. .
1333
Background
To start with, let me provide the background and some terminology.
Imagine that there are two worlds that we could program in: a "normal" everyday world and a
world that I will call the "elevated world" (for reasons that I will explain shortly).
The elevated world is very similar to the normal world. In fact, every thing in the normal
world has a corresponding thing in the elevated world.
So, for example, we have the set of values called Int in the normal world, and in the
elevated world there is a parallel set of values called, say, E<Int> . Similarly, we have the
set of values called String in the normal world, and in the elevated world there is a parallel
1334
Also, just as there are functions between Int s and String s in the normal world, so there
are functions between E<Int> s and E<String> s in the elevated world.
Note that I am deliberately using the term "world" rather than "type" to emphasis that the
relationships between values in the world are just as important as the underlying data type.
1335
But even though the various elevated worlds have nothing in common specifically, there are
commonalities in the way they can be worked with. We find that certain issues occur over
and over again in different elevated worlds, and we can use standard tools and patterns to
deal with these issues.
The rest of this series will attempt to document these tools and patterns.
Series contents
This series is developed as follows:
First, I'll discuss the tools we have for lifting normal things into the elevated world. This
includes functions such as map , return , apply and bind .
Next, I'll discuss how you can combine elevated values in different ways, based on
whether the values are independent or dependent.
Next, we'll look at some ways of mixing lists with other elevated values.
Finally, we'll look at two real-world examples that put all these techniques to use, and
we'll find ourselves accidentally inventing the Reader monad.
Here's a list of shortcuts to the various functions:
Part 1: Lifting to the elevated world
The map function
The return function
The apply function
The liftN family of functions
The zip function and ZipList world
Part 2: How to compose world-crossing functions
The bind function
List is not a monad. Option is not a monad.
Part 3: Using the core functions in practice
Independent and dependent data
Example: Validation using applicative style and monadic style
Lifting to a consistent world
Kleisli world
Part 4: Mixing lists and elevated values
Mixing lists and elevated values
The traverse / MapM function
The sequence function
"Sequence" as a recipe for ad-hoc implementations
Readability vs. performance
1336
1337
Description
"map" is the generic name for something that takes a function in the normal world and
transforms it into a corresponding function in the elevated world.
Alternative interpretation
An alternative interpretation of map is that it is a two parameter function that takes an
elevated value ( E<a> ) and a normal function ( a->b ), and returns a new elevated value
( E<b> ) generated by applying the function a->b to the internal elements of E<a> .
In languages where functions are curried by default, such as F#, both these interpretation
are the same. In other languages, you may need to curry/uncurry to switch between the two
uses.
Note that the two parameter version often has the signature E<a> -> (a->b) -> E<b> , with
the elevated value first and the normal function second. From an abstract point of view,
there's no difference between them -- the map concept is the same -- but obviously, the
parameter order affects how you might use map functions in practice.
1338
Implementation examples
Here are two examples of how map can be defined for options and lists in F#.
/// map for Options
let mapOption f opt =
match opt with
| None ->
None
| Some x ->
Some (f x)
// has type : ('a -> 'b) -> 'a option -> 'b option
/// map for Lists
let rec mapList f list =
match list with
| [] ->
[]
| head::tail ->
// new head + new tail
(f head) :: (mapList f tail)
// has type : ('a -> 'b) -> 'a list -> 'b list
These functions are built-in of course, so we don't need to define them, I've done it just to
show what they might look for some common types.
Usage examples
Here are some examples of how map can be used in F#:
// Define a function in the normal world
let add1 x = x + 1
// has type : int -> int
// A function lifted to the world of Options
let add1IfSomething = Option.map add1
// has type : int option -> int option
// A function lifted to the world of Lists
let add1ToEachElement = List.map add1
// has type : int list -> int list
With these mapped versions in place you can write code like this:
Some 2 |> add1IfSomething // Some 3
[1;2;3] |> add1ToEachElement // [2; 3; 4]
1339
Next, if you take two functions f and g in the normal world, and you compose them (into
h , say), and then lift the resulting function using map , the resulting function should be the
same as if you lifted f and g into the elevated world first, and then composed them there
afterwards.
1340
These two properties are the so-called "Functor Laws", and a Functor (in the programming
sense) is defined as a generic data type -- E<T> in our case -- plus a map function that
obeys the functor laws.
NOTE: "Functor" is a confusing word. There is "functor" in the category theory sense, and
there is "functor" in the programming sense (as defined above). There are also things called
"functors" defined in libraries, such as the Functor type class in Haskell, and the Functor
trait in Scalaz, not to mention functors in SML and OCaml (and C++), which are different yet
again!
Conseqently, I prefer to talk about "mappable" worlds. In practical programming, you will
almost never run into a elevated world that does not support being mapped over somehow.
Variants of map
There are some variants of map that are common:
Const map. A const or "replace-by" map replaces all values with a constant rather than
the output of a function. In some cases, a specialized function like this can allow for a
more efficient implementation.
Maps that work with cross-world functions. The map function a->b lives entirely in
the normal world. But what if the function you want to map with does not return
something in the normal world, but a value in another, different, enhanced world? We'll
see how to address this challenge in a later post.
1341
Description
"return" (also known as "unit" or "pure") simply creates a elevated value from a normal
value.
This function goes by many names, but I'm going to be consistent and call it return as that
is the common term for it in F#, and is the term used in computation expressions.
NOTE: I'm ignoring the difference between pure and return , because type classes are
not the focus of this post.
Implementation examples
Here are two examples of return implementations in F#:
// A value lifted to the world of Options
let returnOption x = Some x
// has type : 'a -> 'a option
// A value lifted to the world of Lists
let returnList x = [x]
// has type : 'a -> 'a list
Obviously, we don't need to define special functions like this for options and lists. Again, I've
just done it to show what return might look for some common types.
1342
What it does: Unpacks a function wrapped inside a elevated value into a lifted function
E<a> -> E<b>
Description
"apply" unpacks a function wrapped inside a elevated value ( E<(a->b)> ) into a lifted
function E<a> -> E<b>
This might seem unimportant, but is actually very valuable, as it allows you to lift a multiparameter function in the normal world into a multi-parameter function in the elevated world,
as we'll see shortly.
Alternative interpretation
An alternative interpretation of apply is that it is a two parameter function that takes a
elevated value ( E<a> ) and a elevated function ( E<(a->b)> ), and returns a new elevated
value ( E<b> ) generated by applying the function a->b to the internal elements of E<a> .
For example, if you have a one-parameter function ( E<(a->b)> ), you can apply it to a single
elevated parameter to get the output as another elevated value.
If you have a two-parameter function ( E<(a->b->c)> ), you can use apply twice in
succession with two elevated parameters to get the elevated output.
You can continue to use this technique to work with as many parameters as you wish.
Implementation examples
Here are some examples of defining apply for two different types in F#:
1343
module Option =
// The apply function for Options
let apply fOpt xOpt =
match fOpt,xOpt with
| Some f, Some x -> Some (f x)
| _ -> None
module List =
// The apply function for lists
// [f;g] apply [x;y] becomes [f x; f y; g x; g y]
let apply (fList: ('a->'b) list) (xList: 'a list) =
[ for f in fList do
for x in xList do
yield f x ]
In this case, rather than have names like applyOption and applyList , I have given the
functions the same name but put them in a per-type module.
Note that in the List.apply implementation, each function in the first list is applied to each
value in the second list, resulting in a "cross-product" style result. That is, the list of functions
[f; g] applied to the list of values [x; y] becomes the four-element list [f x; f y; g x;
g y] . We'll see shortly that this is not the only way to do it.
Also, of course, I'm cheating by building this implementation on a for..in..do loop -functionality that already exists!
I did this for clarity in showing how apply works. It's easy enough to create a "from scratch"
recursive implementation, (though it is not so easy to create one that is properly tailrecursive!) but I want to focus on the concepts not on the implementation for now.
1344
This trick also means that our infix notation can be simplified a little. The initial return then
apply can be replaced with map , and we so typically create an infix operator for map as
This makes the code look more like using the function normally. That is, instead of the
normal add x y , we can use the similar looking add <!> x <*> y , but now x and y can
be elevated values rather than normal values. Some people have even called this style
"overloaded whitespace"!
Here's one more for fun:
1345
let batman =
let (<!>) = List.map
let (<*>) = List.apply
// string concatenation using +
(+) <!> ["bam"; "kapow"; "zap"] <*> ["!"; "!!"]
// result =
// ["bam!"; "bam!!"; "kapow!"; "kapow!!"; "zap!"; "zap!!"]
The second law says that if you take a function f and a value x in the normal world, and
you apply f to x to get a result ( y , say), and then lift the result using return , the
resulting value should be the same as if you lifted f and x into the elevated world first,
and then applied them there afterwards.
1346
The other two laws are not so easily diagrammed, so I won't document them here, but
together the laws ensure that any implementation is sensible.
Description
The apply and return functions can be used to define a series of helper functions liftN
( lift2 , lift3 , lift4 , etc) that take a normal function with N parameters (where
N=2,3,4, etc) and transform it to a corresponding elevated function.
Note that lift1 is just map , and so it is not usually defined as a separate function.
Here's what an implementation might look like:
1347
module Option =
let (<*>) = apply
let (<!>) = Option.map
let lift2 f x y =
f <!> x <*> y
let lift3 f x y z =
f <!> x <*> y <*> z
let lift4 f x y z w =
f <!> x <*> y <*> z <*> w
The lift series of functions can be used to make code a bit more readable because, by
using one of the pre-made lift functions, we can avoid the <*> syntax.
First, here's an example of lifting a two-parameter function:
// define a two-parameter function to test with
let addPair x y = x + y
// lift a two-param function
let addPairOpt = Option.lift2 addPair
// call as normal
addPairOpt (Some 1) (Some 2)
// result => Some 3
1348
Going further, can we eliminate the need for this first function parameter and have a generic
way of combining the values?
Why, yes we can! We can just use a tuple constructor to combine the values. When we do
this we are combining the values without making any decision about how they will be used
yet.
Here's what it looks like in a diagram:
and here's how you might implement it for options and lists:
1349
Now that we have an elevated tuple, we can work with the pair in any way we want, we just
need to use map to do the actual combining.
Want to add the values? Just use + in the map function:
combineOpt (Some 2) (Some 3)
|> Option.map (fun (x,y) -> x + y)
// Result => // Some 5
combineList [1;2] [100;200]
|> List.map (fun (x,y) -> x + y)
// Result => [101; 201; 102; 202]
1350
Interestingly, the lift2 function above can be actually used as an alternative basis for
defining apply .
That is, we can define apply in terms of the lift2 function by setting the combining
function to be just function application.
Here's a demonstration of how this works for Option :
module Option =
/// define lift2 from scratch
let lift2 f xOpt yOpt =
match xOpt,yOpt with
| Some x,Some y -> Some (f x y)
| _ -> None
/// define apply in terms of lift2
let apply fOpt xOpt =
lift2 (fun f x -> f x) fOpt xOpt
This alternative approach is worth knowing about because for some types it's easier to
define lift2 than apply .
It's possible to create an alternative kind of combiner that ignores missing or bad values, just
as adding "0" to a number is ignored. For more information, see my post on "Monoids
without tears".
In some cases you might have two elevated values, and want to discard the value from one
side or the other.
Here's an example for lists:
let ( <* ) x y =
List.lift2 (fun left right -> left) x y
let ( *> ) x y =
List.lift2 (fun left right -> right) x y
We can then combine a 2-element list and a 3-element list to get a 6-element list as
expected, but the contents come from only one side or the other.
[1;2] <* [3;4;5] // [1; 1; 1; 2; 2; 2]
[1;2] *> [3;4;5] // [3; 4; 5; 3; 4; 5]
We can turn this into a feature! We can replicate a value N times by crossing it with [1..n] .
let repeat n pattern =
[1..n] *> pattern
let replicate n x =
[1..n] *> [x]
repeat 3 ["a";"b"]
// ["a"; "b"; "a"; "b"; "a"; "b"]
replicate 5 "A"
// ["A"; "A"; "A"; "A"; "A"]
Of course, this is by no means an efficient way to replicate a value, but it does show that
starting with just the two functions apply and return , you can build up some quite
complex behavior.
On a more practical note though, why might this "throwing away data" be useful? Well in
many cases, we might not want the values, but we do want the effects.
For example, in a parser, you might see code like this:
let readQuotedString =
readQuoteChar *> readNonQuoteChars <* readQuoteChar
1352
In this snippet, readQuoteChar means "match and read a quote character from the input
stream" and readNonQuoteChars means "read a series of non-quote characters from the
input stream".
When we are parsing a quoted string we want ensure the input stream that contains the
quote character is read, but we don't care about the quote characters themselves, just the
inner content.
Hence the use of *> to ignore the leading quote and <* to ignore the trailing quote.
Description
Some data types might have more than one valid implementation of apply . For example,
there is another possible implementation of apply for lists, commonly called ZipList or
some variant of that.
In this implementation, the corresponding elements in each list are processed at the same
time, and then both lists are shifted to get the next element. That is, the list of functions [f;
g] applied to the list of values [x; y] becomes the two-element list [f x; g y]
1353
WARNING: This implementation is just for demonstration. It's not tail-recursive, so don't use
it for large lists!
If the lists are of different lengths, some implementations throw an exception (as the F#
library functions List.map2 and List.zip do), while others silently ignore the extra data
(as the implementation above does).
Ok, let's see it in use:
let add10 x = x + 10
let add20 x = x + 20
let add30 x = x + 30
let result =
let (<*>) = zipList
[add10; add20; add30] <*> [1; 2; 3]
// result => [11; 22; 33]
Note that the result is [11; 22; 33] -- only three elements. If we had used the standard
List.apply , there would have been nine elements.
Note that we can't just have one add function in the first list -- we have to have one add
for every element in the second and third lists!
That could get annoying, so often, a "tupled" version of zip is used, whereby you don't
specify a combining function at all, and just get back a list of tuples instead, which you can
then process later using map . This is the same approach as was used in the combine
functions discussed above, but for zipList .
ZipList world
1354
In standard List world, there is an apply and a return . But with our different version of
apply we can create a different version of List world called ZipList world.
module ZipSeq =
// define "return" for ZipSeqWorld
let retn x = Seq.initInfinite (fun _ -> x)
// define "apply" for ZipSeqWorld
// (where we can define apply in terms of "lift2", aka "map2")
let apply fSeq xSeq =
Seq.map2 (fun f x -> f x) fSeq xSeq
// has type : ('a -> 'b) seq -> 'a seq -> 'b seq
// define a sequence that is a combination of two others
let triangularNumbers =
let (<*>) = apply
let addAndDivideByTwo x y = (x + y) / 2
let numbers = Seq.initInfinite id
let squareNumbers = Seq.initInfinite (fun i -> i * i)
(retn addAndDivideByTwo) <*> numbers <*> squareNumbers
// evaulate first 10 elements
// and display result
triangularNumbers |> Seq.take 10 |> List.ofSeq |> printfn "%A"
// Result =>
// [0; 1; 3; 6; 10; 15; 21; 28; 36; 45]
This example demonstrates that an elevated world is not just a data type (like the List type)
but consists of the datatype and the functions that work with it. In this particular case, "List
world" and "ZipList world" share the same data type but have quite different environments.
1355
So far we have defined all these useful functions in an abstract way. But how easy is it to
find real types that have implementations of them, including all the various laws?
The answer is: very easy! In fact almost all types support these set of functions. You'd be
hard-pressed to find a useful type that didn't.
That means that map and apply and return are available (or can be easily implemented)
for standard types such as Option , List , Seq , Async , etc., and also any types you are
likely to define yourself.
Summary
In this post, I described three core functions for lifting simple "normal" values to elevated
worlds: map , return , and apply , plus some derived functions like liftN and zip .
In practice however, things are not that simple. We frequently have to work with functions
that cross between the worlds. Their input is in the normal world but their output is in the
elevated world.
In the next post we'll show how these world-crossing functions can be lifted to the elevated
world as well.
1356
Understanding bind
Understanding bind
This post is the second in a series. In the previous post, I described some of the core
functions for lifting a value from a normal world to an elevated world.
In this post, we'll look at "world-crossing" functions, and how they can be tamed with the
bind function.
Series contents
Here's a list of shortcuts to the various functions mentioned in this series:
Part 1: Lifting to the elevated world
The map function
The return function
The apply function
The liftN family of functions
The zip function and ZipList world
Part 2: How to compose world-crossing functions
The bind function
List is not a monad. Option is not a monad.
Part 3: Using the core functions in practice
Independent and dependent data
Example: Validation using applicative style and monadic style
Lifting to a consistent world
Kleisli world
Part 4: Mixing lists and elevated values
Mixing lists and elevated values
The traverse / MapM function
The sequence function
"Sequence" as a recipe for ad-hoc implementations
Readability vs. performance
Dude, where's my filter ?
Part 5: A real-world example that uses all the techniques
Example: Downloading and processing a list of websites
Treating two worlds as one
Part 6: Designing your own elevated world
Designing your own elevated world
1357
Understanding bind
Description
We frequently have to deal with functions that cross between the normal world and the
elevated world.
For example: a function that parses a string to an int might return an Option<int>
rather than a normal int , a function that reads lines from a file might return
IEnumerable<string> , a function that fetches a web page might return Async<string> , and
so on.
These kinds of "world-crossing" functions are recognizable by their signature a -> E<b> ;
their input is in the normal world but their output is in the elevated world. Unfortunately, this
means that these kinds of functions cannot be linked together using standard composition.
1358
Understanding bind
The benefit of doing this is that the resulting lifted functions live purely in the elevated world,
and so can be combined easily by composition.
For example, a function of type a -> E<b> cannot be directly composed with a function of
type b -> E<c> , but after bind is used, the second function becomes of type E<b> ->
E<c> , which can be composed.
In this way, bind lets us chain together any number of monadic functions.
Alternative interpretation
An alternative interpretation of bind is that it is a two parameter function that takes a
elevated value ( E<a> ) and a "monadic function" ( a -> E<b> ), and returns a new elevated
value ( E<b> ) generated by "unwrapping" the value inside the input, and running the function
a -> E<b> against it. Of course, the "unwrapping" metaphor does not work for every
elevated world, but still it can often be useful to think of it this way.
Implementation examples
Here are some examples of defining bind for two different types in F#:
1359
Understanding bind
module Option =
// The bind function for Options
let bind f xOpt =
match xOpt with
| Some x -> f x
| _ -> None
// has type : ('a -> 'b option) -> 'a option -> 'b option
module List =
// The bind function for lists
let bindList (f: 'a->'b list) (xList: 'a list) =
[ for x in xList do
for y in f x do
yield y ]
// has type : ('a -> 'b list) -> 'a list -> 'b list
Notes:
Of course, in these two particular cases, the functions already exist in F#, called
Option.bind and List.collect .
For List.bind I'm cheating again and using for..in..do , but I think that this particular
implementation shows clearly how bind works with lists. There is a purer recursive
implementation, but I won't show that here.
Usage example
As explained at the beginning of this section, bind can be used to compose cross-world
functions. Let's see how this works in practice with a simple example.
First let's say we have a function that parses certain string s into int s. Here's a very
simple implementation:
let parseInt str =
match str with
| "-1" -> Some -1
| "0" -> Some 0
| "1" -> Some 1
| "2" -> Some 2
// etc
| _ -> None
// signature is string -> int option
1360
Understanding bind
Sometimes it returns a int, sometimes not. So the signature is string -> int option -- a
cross-world function.
And let's say we have another function that takes an int as input and returns a OrderQty
type:
type OrderQty = OrderQty of int
let toOrderQty qty =
if qty >= 1 then
Some (OrderQty qty)
else
// only positive numbers allowed
None
// signature is int -> OrderQty option
Again, it might not return an OrderQty if the input is not positive. The signature is therefore
int -> OrderQty option -- another cross-world function.
Now, how can we create a function that starts with an string and returns an OrderQty in one
step?
The output of parseInt cannot be fed directly into toOrderQty , so this is where bind
comes to the rescue!
Doing Option.bind toOrderQty lifts it to a int option -> OrderQty option function and so
the output of parseInt can be used as input, just as we need.
let parseOrderQty str =
parseInt str
|> Option.bind toOrderQty
// signature is string -> OrderQty option
The signature of our new parseOrderQty is string -> OrderQty option , yet another crossworld function. So if we want to do something with the OrderQty that is output we may well
have to use bind again on the next function in the chain.
1361
Understanding bind
You can see that >>= performs the same kind of role as pipe ( |> ) does, except that it
works to pipe "elevated" values into cross-world functions.
This is not too different from how an imperative program might look if you replace the >>=
with a ; :
statement1;
statement2;
statement3;
statement4;
1362
Understanding bind
elevated {
let! x = initialExpression
let! y = expressionUsingX x
let! z = expressionUsingY y
return x+y+z }
It's important to emphasize that you do not have to use the special syntax when using
bind/return. You can always use bind or >>= in the same way as any other function.
Next, convert this world-crossing function into a lifted function using bind . This gives
you the same result as if you had simply done map in the first place.
1363
Understanding bind
Similarly, bind can emulate apply . Here is how map and apply can be defined using
bind and return for Options in F#:
At this point, people often ask "why should I use apply instead of bind when bind is
more powerful?"
The answer is that just because apply can be emulated by bind , doesn't mean it should
be. For example, it is possible to implement apply in a way that cannot be emulated by a
bind implementation.
In fact, using apply ("applicative style") or bind ("monadic style") can have a profound
effect on how your program works! We'll discuss these two approaches in more detail in part
3 of this post.
only way to define a monad, and mathematicians typically use a slightly different definition,
but this one is most useful to programmers.
1364
Understanding bind
Just as with the the Functor and Applicative laws we saw earlier, these laws are quite
sensible.
First, note that return function is itself a cross-world function:
That means that we can use bind to lift it into a function in the elevated world. And what
does this lifted function do? Hopefully, nothing! It should just return its input.
So that is exactly the first monad law: it says that this lifted function must be the same as the
id function in the elevated world.
The second law is similar but with bind and return reversed. Say that we have a normal
value a and cross-world function f that turns an a into a E<b> .
Let's lift both of them to the elevated world, using bind on f and return on a .
1365
Understanding bind
Now if we apply the elevated version of f to the elevated verson of a we get some value
E<b> .
On the other hand if we apply the normal version of f to the normal verson of a we also
get some value E<b> .
The second monad law says that these two elevated values ( E<b> ) should be the same. In
other words, all this binding and returning should not distort the data.
The third monad law is about associativity.
In the normal world, function composition is associative. For example, we could pipe a value
into a function f and then take that result and pipe it into another function g . Alternatively,
we can compose f and g first into a single function and then pipe a into it.
let groupFromTheLeft = (a |> f) |> g
let groupFromTheRight = a |> (f >> g)
In the normal world, we expect both of these alternatives to give the same answer.
1366
Understanding bind
The third monad law says that, after using bind and return , the grouping doesn't matter
either. The two examples below correspond to the examples above:
let groupFromTheLeft = (a >>= f) >>= g
let groupFromTheRight = a >>= (fun x -> f x >>= g)
1367
Understanding bind
Summary
We now have a set of four core functions: map , return , apply , and bind , and I hope
that you are clear on what each one does.
But there are some questions that have not been addressed yet, such as "why should I
choose apply instead of bind ?", or "how can I deal with multiple elevated worlds at the
same time?"
In the next post we'll address these questions and demonstrate how to use the toolset with a
series of practical examples.
UPDATE: Fixed error in monad laws pointed out by @joseanpg. Thanks!
1368
Series contents
Here's a list of shortcuts to the various functions mentioned in this series:
Part 1: Lifting to the elevated world
The map function
The return function
The apply function
The liftN family of functions
The zip function and ZipList world
Part 2: How to compose world-crossing functions
The bind function
List is not a monad. Option is not a monad.
Part 3: Using the core functions in practice
Independent and dependent data
Example: Validation using applicative style and monadic style
Lifting to a consistent world
Kleisli world
Part 4: Mixing lists and elevated values
Mixing lists and elevated values
The traverse / MapM function
The sequence function
"Sequence" as a recipe for ad-hoc implementations
Readability vs. performance
Dude, where's my filter ?
Part 5: A real-world example that uses all the techniques
Example: Downloading and processing a list of websites
Treating two worlds as one
Part 6: Designing your own elevated world
Designing your own elevated world
1369
When using apply , you can see that each parameter ( E<a> , E<b> ) is completely
independent of the other. The value of E<b> does not depend on what E<a> is.
On the other hand, when using bind , the value of E<b> does depend on what E<a> is.
The distinction between working with independent values or dependent values leads to two
different styles:
The so-called "applicative" style uses functions such as apply , lift , and combine
where each elevated value is independent.
The so-called "monadic" style uses functions such as bind to chain together functions
1370
monadically.
In the example above, the implementation does not have to run the downloads in parallel. It
could run them serially instead. By using applicative style, you're just saying that you don't
care about dependencies and so they could be downloaded in parallel.
1371
On the other hand, the monadic style means that only the initial action is known up front.
The remainder of the actions are determined dynamically, based on the output of previous
actions. This is more flexible, but also limits our ability to see the big picture in advance.
1372
And let's say that there is some validation around creating a CustomerId . For example, that
the inner int must be positive. And of course, there will be some validation around
creating a EmailAddress too. For example, that it must contain an "@" sign at least.
How would we do this?
First we create a type to represent the success/failure of validation.
type Result<'a> =
| Success of 'a
| Failure of string list
Note that I have defined the Failure case to contain a list of strings, not just one. This will
become important later.
With Result in hand, we can go ahead and define the two constructor/validation functions
as required:
let createCustomerId id =
if id > 0 then
Success (CustomerId id)
else
Failure ["CustomerId must be positive"]
// int -> Result<CustomerId>
let createEmailAddress str =
if System.String.IsNullOrEmpty(str) then
Failure ["Email must not be empty"]
elif str.Contains("@") then
Success (EmailAddress str)
else
Failure ["Email must contain @-sign"]
// string -> Result<EmailAddress>
That means that both of these validation functions are world-crossing functions, going from
the normal world to the Result<_> world.
1373
module Result =
let map f xResult =
match xResult with
| Success x ->
Success (f x)
| Failure errs ->
Failure errs
// Signature: ('a -> 'b) -> Result<'a> -> Result<'b>
// "return" is a keyword in F#, so abbreviate it
let retn x =
Success x
// Signature: 'a -> Result<'a>
let apply fResult xResult =
match fResult,xResult with
| Success f, Success x ->
Success (f x)
| Failure errs, Success x ->
Failure errs
| Success f, Failure errs ->
Failure errs
| Failure errs1, Failure errs2 ->
// concat both lists of errors
Failure (List.concat [errs1; errs2])
// Signature: Result<('a -> 'b)> -> Result<'a> -> Result<'b>
let bind f xResult =
match xResult with
| Success x ->
f x
| Failure errs ->
Failure errs
// Signature: ('a -> Result<'b>) -> Result<'a> -> Result<'b>
If we check the signatures, we can see that they are exactly as we want:
map has signature: ('a -> 'b) -> Result<'a> -> Result<'b>
retn has signature: 'a -> Result<'a>
apply has signature: Result<('a -> 'b)> -> Result<'a> -> Result<'b>
bind has signature: ('a -> Result<'b>) -> Result<'a> -> Result<'b>
I defined a retn function in the module to be consistent, but I don't bother to use it very
often. The concept of return is important, but in practice, I'll probably just use the Success
constructor directly. In languages with type classes, such as Haskell, return is used much
more.
1374
Also note that apply will concat the error messages from each side if both parameters are
failures. This allows us to collect all the failures without discarding any. This is the reason
why I made the Failure case have a list of strings, rather than a single string.
NOTE: I'm using string for the failure case to make the demonstration easier. In a more
sophisticated design I would list the possible failures explicitly. See my functional error
handling talk for more details.
The signature of this shows that we start with a normal int and string and return a
Result<CustomerInfo>
1375
The goodCustomerA is a Success and contains the right data, but the badCustomerA is a
Failure and contains two validation error messages. Excellent!
1376
let goodCustomerM =
createCustomerResultM goodId goodEmail
// Result<CustomerInfo> =
// Success {id = CustomerId 1; email = EmailAddress "test@example.com";}
let badCustomerM =
createCustomerResultM badId badEmail
// Result<CustomerInfo> =
// Failure ["CustomerId must be positive"]
In the good customer case, the end result is the same, but in the bad customer case, only
one error is returned, the first one. The rest of the validation was short circuited after the
CustomerId creation failed.
1377
On the other hand, the monadic example did one validation at a time, chained together.
The benefit was that we short-circuited the rest of the chain as soon as an error
occurred and avoided extra work. The downside was that we only got the first error.
1378
This computation expression version looks almost like using an imperative language.
Note that F# computation expressions are always monadic, as is Haskell do-notation and
Scala for-comprehensions. That's not generally a problem, because if you need applicative
style it is very easy to write without any language support.
As before, we want to create a function in the normal world that we will later lift to the
Result world.
Now we are ready to update the lifted createCustomer with the extra parameter:
1379
But this won't compile! In the series of parameters idResult <*> name <*> emailResult one
of them is not like the others. The problem is that idResult and emailResult are both
Results, but name is still a string.
The fix is just to lift name into the world of results (say nameResult ) by using return , which
for Result is just Success . Here is the corrected version of the function that does work:
let createCustomerResultA id name email =
let idResult = createCustomerId id
let emailResult = createEmailAddress email
let nameResult = Success name // lift name to Result
createCustomer <!> idResult <*> nameResult <*> emailResult
1380
The problem is that these functions cannot be glued together; they are all different shapes.
The solution is to convert all of them to the same shape, in this case the two-track model
with success and failure on different tracks. Let's call this Two-Track world!
The DbFetch function is a world-crossing function. We can turn it into a wholly two-track
function using bind .
The DbUpdate function is more complicated. We don't like dead-end functions, so first we
need to transform it to a function where the data keeps flowing. I'll call this function tee .
The output of tee has one track in and one track out, so we need to convert it to a twotrack function, again using map .
After all these transformations, we can reassemble the new versions of these functions. The
result looks like this:
And of course, these functions can now be composed together very easily, so that we end
up with a single function looking like this, with one input and a success/failure output:
1381
This combined function is yet another world-crossing function of the form a->Result<b> , and
so it in turn can be used as a component part of a even bigger function.
For more examples of this "elevating everything to the same world" approach, see my posts
on functional error handling and threading state.
Kleisli world
There is an alternative world which can be used as a basic for consistency which I will call
"Kleisli" world, named after Professor Kleisli -- a mathematician, of course!
In Kleisli world everything is a cross-world function! Or, using the railway track analogy,
everything is a switch (or points).
In Kleisli world, the cross-world functions can be composed directly, using an operator called
>=> for left-to-right composition or <=< for right-to-left composition.
Using the same example as before, we can lift all our functions to Kleisli world.
The Validate and DbFetch functions are already in the right form so they don't need
to be changed.
The one-track Canonicalize function can be lifted to a switch just by lifting the output to
a two-track value. Let's call this toSwitch .
The tee-d DbUpdate function can be also lifted to a switch just by doing toSwitch after
the tee.
1382
Once all the functions have been lifted to Kleisli world, they can be composed with Kleisli
composition:
Kleisli world has some nice properties that Two-Track world doesn't but on the other hand, I
find it hard to get my head around it! So I generally stick to using Two-Track world as my
foundation for things like this.
Summary
In this post, we learned about "applicative" vs "monadic" style, and why the choice could
have an important effect on which actions are executed, and what results are returned.
We also saw how to lift different kinds values and functions to a a consistent world so that
the could be worked with easily.
In the next post we'll look at a common problem: working with lists of elevated values.
1383
Series contents
Here's a list of shortcuts to the various functions mentioned in this series:
Part 1: Lifting to the elevated world
The map function
The return function
The apply function
The liftN family of functions
The zip function and ZipList world
Part 2: How to compose world-crossing functions
The bind function
List is not a monad. Option is not a monad.
Part 3: Using the core functions in practice
Independent and dependent data
Example: Validation using applicative style and monadic style
Lifting to a consistent world
Kleisli world
Part 4: Mixing lists and elevated values
Mixing lists and elevated values
The traverse / MapM function
The sequence function
"Sequence" as a recipe for ad-hoc implementations
Readability vs. performance
Dude, where's my filter ?
Part 5: A real-world example that uses all the techniques
Example: Downloading and processing a list of websites
Treating two worlds as one
Part 6: Designing your own elevated world
1384
customers at once. Again, we can use map to convert the list of ids to a list of results.
But what we really want is not a list of Result<Customer> , but a Result containing a
Customer list , with the Failure case in case of errors.
1385
Yes! If we think about how a list is built, there is a "cons" function ( :: in F#) that is used to
join the head to the tail. If we elevate this to the Option world, we can use Option.apply to
join a head Option to a tail Option using the lifted version of cons .
let (<*>) = Option.apply
let retn = Some
let rec mapOption f list =
let cons head tail = head :: tail
match list with
| [] ->
retn []
| head::tail ->
retn cons <*> (f head) <*> (mapOption f tail)
NOTE: I defined cons explicitly because :: is not a function and List.Cons takes a tuple
and is thus not usable in this context.
Here is the implementation as a diagram:
If you are confused as to how this works, please read the section on apply in the first post
in this series.
Note also that I am explicitly defining retn and using it in the implementation rather than
just using Some . You'll see why in the next section.
Now let's test it!
let parseInt str =
match (System.Int32.TryParse str) with
| true,i -> Some i
| false,_ -> None
// string -> int option
let good = ["1";"2";"3"] |> mapOption parseInt
// Some [1; 2; 3]
let bad = ["1";"x";"y"] |> mapOption parseInt
// None
1386
We start by defining parseInt of type string -> int option (piggybacking on the existing
.NET library).
We use mapOption to run it against a list of good values, and we get Some [1; 2; 3] , with
the list inside the option, just as we want.
And if we use a list where some of the values are bad, we get None for the entire result.
Again I am explicitly defining a retn rather than just using Success . And because of this,
the body of the code for mapResult and mapOption is exactly the same!
Now let's change parseInt to return a Result rather than an Option :
let parseInt str =
match (System.Int32.TryParse str) with
| true,i -> Success i
| false,_ -> Failure [str + " is not an int"]
And then we can rerun the tests again, but this time getting more informative errors in the
failure case:
let good = ["1";"2";"3"] |> mapResult parseInt
// Success [1; 2; 3]
let bad = ["1";"x";"y"] |> mapResult parseInt
// Failure ["x is not an int"; "y is not an int"]
The implementations of mapOption and mapResult have exactly the same code, the only
difference is the different retn and <*> functions (from Option and Result, respectively).
So the question naturally arises, rather than having mapResult , mapOption , and other
specific implementations for each elevated type, can we make a completely generic version
of mapXXX that works for all elevated types?
The obvious thing would be able to pass these two functions in as an extra parameter, like
this:
let rec mapE (retn,ap) f list =
let cons head tail = head :: tail
let (<*>) = ap
match list with
| [] ->
retn []
| head::tail ->
(retn cons) <*> (f head) <*> (mapE retn ap f tail)
There are some problems with this though. First, this code doesn't compile in F#! But even if
it did, we'd want to make sure that the same two parameters were passed around
everywhere.
We might attempt this by creating a record structure containing the two parameters, and
then create one instance for each type of elevated world:
type Applicative<'a,'b> = {
retn: 'a -> E<'a>
apply: E<'a->'b> -> E<'a> -> E<'b>
}
// functions for applying Option
let applOption = {retn = Option.Some; apply=Option.apply}
// functions for applying Result
let applResult = {retn = Result.Success; apply=Result.apply}
The instance of the Applicative record ( appl say) would be an extra parameter to our
generic mapE function, like this:
1388
In use, we would pass in the specific applicative instance that we want, like this:
// build an Option specific version...
let mapOption = mapE applOption
// ...and use it
let good = ["1";"2";"3"] |> mapOption parseInt
Unfortunately, none of this works either, at least in F#. The Applicative type, as defined,
won't compile. This is because F# does not support "higher-kinded types". That is, we can't
parameterize the Applicative type with a generic type, only with concrete types.
In Haskell and languages that do support "higher-kinded types", the Applicative type that
we've defined is similar to a "type class". What's more, with type classes, we don't have to
pass around the functions explicitly -- the compiler will do that for us.
There is actually a clever (and hacky) way of getting the same effect in F# though using
static type constraints. I'm not going to discuss it here, but you can see it used in the
FSharpx library.
The alternative to all this abstraction is just creating a mapXXX function for each elevated
world that we want to work with: mapOption , mapResult , mapAsync and so on.
Personally I am OK with this cruder approach. There are not that many elevated worlds that
you work with regularly, and even though you lose out on abstraction, you gain on
explicitness, which is often useful when working in a team of mixed abilities.
So let's look at these mapXXX functions, also called traverse .
1389
Description
We saw above that we can define a set of mapXXX functions, where XXX stands for an
applicative world -- a world that has apply and return . Each of these mapXXX functions
transforms a world-crossing function into a world-crossing function that works with
collections.
And as we noted above, if the language supports type classes, we can get away with a
single implementation, called mapM or traverse . I'm going to call the general concept
traverse from now on to make it clear that is different from map .
1390
If a Traversable world is on top, that produces a type such as List<a> , and if an Applicative
world is on top, that produces a type such as Result<a> .
IMPORTANT: I will be using the syntax List<_> to represent "List world" for consistency
with Result<_> , etc. This is not meant to be the same as the .NET List class! In F#, this
would be implemented by the immutable list type.
But from now on we are going to be dealing with both kinds of elevated worlds in the same
"stack".
The Traversable world can be stacked on top of the Applicative world, which produces a
type such as List<Result<a>> , or alternatively, the Applicative world world can be stacked
on top of the Traversable world, which produces a type such as Result<List<a>> .
Now let's see what the different kinds of functions look like using this notation.
Let's start with a plain cross-world function such as a -> Result<b> , where the target world
is an applicative world. In the diagram, the input is a normal world (on the left), and the
output (on the right) is an applicative world stacked on top of the normal world.
Now if we have a list of normal a values, and then we use map to transform each a
value using a function like a -> Result<b> , the result will also be a list, but where the
contents are Result<b> values instead of a values.
1391
When it comes to traverse the effect is quite different. If we use traverse to transform a
list of a values using that function, the output will be a Result , not a list. And the contents
of the Result will be a List<b> .
In other words, with traverse , the List stays attached to the normal world, and the
Applicative world (such as Result ) is added at the top.
Ok, I know this all sounds very abstract, but it is actually a very useful technique. We'll see
an example of this is used in practice below.
1392
module List =
/// Map a Result producing function over a list to get a new Result
/// using applicative style
/// ('a -> Result<'b>) -> 'a list -> Result<'b list>
let rec traverseResultA f list =
// define the applicative functions
let (<*>) = Result.apply
let retn = Result.Success
// define a "cons" function
let cons head tail = head :: tail
// loop through the list
match list with
| [] ->
// if empty, lift [] to a Result
retn []
| head::tail ->
// otherwise lift the head to a Result using f
// and cons it with the lifted version of the remaining list
retn cons <*> (f head) <*> (traverseResultA f tail)
/// Map a Result producing function over a list to get a new Result
/// using monadic style
/// ('a -> Result<'b>) -> 'a list -> Result<'b list>
let rec traverseResultM f list =
// define the monadic functions
let (>>=) x f = Result.bind f x
let retn = Result.Success
// define a "cons" function
let cons head tail = head :: tail
// loop through the list
match list with
| [] ->
// if empty, lift [] to a Result
retn []
| head::tail ->
// otherwise lift the head to a Result using f
// then lift the tail to a Result using traverse
// then cons the head and tail and return it
f head >>= (fun h ->
traverseResultM f tail >>= (fun t ->
retn (cons h t) ))
1393
The monadic version applies the function f to the first element and then passes it to
bind . As always with monadic style, if the result is bad the rest of the list will be skipped.
On the other hand, if the result is good, the next element in the list is processed, and so on.
Then the results are cons'ed back together again.
NOTE: These implementations are for demonstration only! Neither of these implementations
are tail-recursive, and so they will fail on large lists!
Alright, let's test the two functions and see how they differ. First we need our parseInt
function:
/// parse an int and return a Result
/// string -> Result<int>
let parseInt str =
match (System.Int32.TryParse str) with
| true,i -> Result.Success i
| false,_ -> Result.Failure [str + " is not an int"]
Now if we pass in a list of good values (all parsable), the result for both implementations is
the same.
// pass in strings wrapped in a List
// (applicative version)
let goodA = ["1"; "2"; "3"] |> List.traverseResultA parseInt
// get back a Result containing a list of ints
// Success [1; 2; 3]
// pass in strings wrapped in a List
// (monadic version)
let goodM = ["1"; "2"; "3"] |> List.traverseResultM parseInt
// get back a Result containing a list of ints
// Success [1; 2; 3]
But if we pass in a list with some bad values, the results differ.
// pass in strings wrapped in a List
// (applicative version)
let badA = ["1"; "x"; "y"] |> List.traverseResultA parseInt
// get back a Result containing a list of ints
// Failure ["x is not an int"; "y is not an int"]
// pass in strings wrapped in a List
// (monadic version)
let badM = ["1"; "x"; "y"] |> List.traverseResultM parseInt
// get back a Result containing a list of ints
// Failure ["x is not an int"]
1394
The applicative version returns all the errors, while the monadic version returns only the first
error.
1395
/// Map a Result producing function over a list to get a new Result
/// using applicative style
/// ('a -> Result<'b>) -> 'a list -> Result<'b list>
let traverseResultA f list =
// define the applicative functions
let (<*>) = Result.apply
let retn = Result.Success
// define a "cons" function
let cons head tail = head :: tail
// right fold over the list
let initState = retn []
let folder head tail =
retn cons <*> (f head) <*> tail
List.foldBack folder list initState
/// Map a Result producing function over a list to get a new Result
/// using monadic style
/// ('a -> Result<'b>) -> 'a list -> Result<'b list>
let traverseResultM f list =
// define the monadic functions
let (>>=) x f = Result.bind f x
let retn = Result.Success
// define a "cons" function
let cons head tail = head :: tail
// right fold over the list
let initState = retn []
let folder head tail =
f head >>= (fun h ->
tail >>= (fun t ->
retn (cons h t) ))
List.foldBack folder list initState
Note that this approach will not work for all collection classes. Some types do not have a
right fold, so traverse must be implemented differently.
1396
Yes. For example, an Option can be considered a one-element list, and we can use the
same trick.
For example, here's an implementation of traverseResultA for Option
module Option =
/// Map a Result producing function over an Option to get a new Result
/// ('a -> Result<'b>) -> 'a option -> Result<'b option>
let traverseResultA f opt =
// define the applicative functions
let (<*>) = Result.apply
let retn = Result.Success
// loop through the option
match opt with
| None ->
// if empty, lift None to an Result
retn None
| Some x ->
// lift value to an Result
(retn Some) <*> (f x)
Now we can wrap a string in an Option and use parseInt on it. Rather than getting a
Option of Result , we invert the stack and get a Result of Option .
1397
This last result might be surprising at first glance, but think of it this way, the parsing didn't
fail, so there was no Failure at all.
Traversables
Types that can implement a function like mapXXX or traverseXXX are called Traversable.
For example, collection types are Traversables as well as some others.
As we saw above, in a language with type classes a Traversable type can get away with just
one implementation of traverse , but in a language without type classes a Traversable type
will need one implementation per elevated type.
Also note that, unlike all the generic functions we have created before, the type being acted
on (inside the collection) must have appropriate apply and return functions in order for
traverse to be implemented. That is, the inner type must be an Applicative.
Description
1398
We saw above how you can use the traverse function as a substitute for map when you
have a function that generates an applicative type such as Result .
But what happens if you are just handed a List<Result> and you need to change it to a
Result<List> . That is, you need to swap the order of the worlds on the stack:
This is where sequence is useful -- that's exactly what it does! The sequence function
"swaps layers".
The order of swapping is fixed:
The Traversable world starts higher and is swapped down.
The Applicative world starts lower and is swapped up.
Note that if you aleady have an implementation of traverse , then sequence can be derived
from it easily. In fact, you can think of sequence as traverse with the id function baked
in.
A simple example
Let's implement and test a sequence implementation for Result :
1399
module List =
/// Transform a "list<Result>" into a "Result<list>"
/// and collect the results using apply
/// Result<'a> list -> Result<'a list>
let sequenceResultA x = traverseResultA id x
/// Transform a "list<Result>" into a "Result<list>"
/// and collect the results using bind.
/// Result<'a> list -> Result<'a list>
let sequenceResultM x = traverseResultM id x
Ok, that was too easy! Now let's test it, starting with the applicative version:
let goodSequenceA =
["1"; "2"; "3"]
|> List.map parseInt
|> List.sequenceResultA
// Success [1; 2; 3]
let badSequenceA =
["1"; "x"; "y"]
|> List.map parseInt
|> List.sequenceResultA
// Failure ["x is not an int"; "y is not an int"]
As before, we get back a Result<List> , and as before the monadic version stops on the
first error, while the applicative version accumulates all the errors.
1400
This data is in the form List<Option<Tuple<int>>> . And now say, that for some reason, you
need to turn it into a tuple of two lists, where each list contains options, like this:
let desiredOutput = [Some 1; Some 3; None; Some 7],[Some 2; Some 4; None; Some 8]
// Tuple<List<Option<int>>>
1401
Do these helper functions need to be in a library? No. It's unlikely that I will need them again,
and even I need them occasionally, I'd prefer to write them scratch to avoid having to take a
dependency.
On the other hand, the List.sequenceResult function implemented earlier that converts a
List<Result<a>> to a Result<List<a>> is something I do use frequently, and so that one is
worth centralizing.
1402
Next, define listSequenceTuple using exactly the same right fold template as we did before,
with List as the traversable and tuple as the applicative:
let listSequenceTuple list =
// define the applicative functions
let (<*>) = tupleApply
let retn = tupleReturn
// define a "cons" function
let cons head tail = head :: tail
// right fold over the list
let initState = retn []
let folder head tail = retn cons <*> head <*> tail
List.foldBack folder list initState
1403
Ok, this solution is more work than having one reusable function, but because it is
mechanical, it only takes a few minutes to code, and is still easier than trying to come up
with your own solution!
Want more? For an example of using sequence in a real-world problem, please read this
post.
1404
But of course, this does involve two passes over the list, and we saw how traverse could
combine the map and the sequence in one step, making only one pass over the list, like
this:
["1"; "2"; "3"]
|> List.traverseResultM parseInt
So if traverse is more compact and potentially faster, why ever use sequence ?
Well, sometimes you are given a certain structure and you have no choice, but in other
situations I might still prefer the two-step map-sequence approach just because it is easier to
understand. The mental model for "map" then "swap" seems easier to grasp for most people
than the one-step traverse.
In other words, I would always go for readability unless you can prove that performance is
impacted. Many people are still learning FP, and being overly cryptic is not helpful, in my
experience.
1405
Summary
In this post, we learned about traverse and sequence as a way of working with lists of
elevated values.
In the next post we'll finish up by working through a practical example that uses all the
techniques that have been discussed.
1406
Series contents
Here's a list of shortcuts to the various functions mentioned in this series:
Part 1: Lifting to the elevated world
The map function
The return function
The apply function
The liftN family of functions
The zip function and ZipList world
Part 2: How to compose world-crossing functions
The bind function
List is not a monad. Option is not a monad.
Part 3: Using the core functions in practice
Independent and dependent data
Example: Validation using applicative style and monadic style
Lifting to a consistent world
Kleisli world
Part 4: Mixing lists and elevated values
Mixing lists and elevated values
The traverse / MapM function
The sequence function
"Sequence" as a recipe for ad-hoc implementations
Readability vs. performance
Dude, where's my filter ?
Part 5: A real-world example that uses all the techniques
1407
The downloader
First we need to create a downloader. I would use the built-in System.Net.WebClient class,
but for some reason it doesn't allow override of the timeout. I'm going to want to have a
small timeout for the later tests on bad uris, so this is important.
One trick is to just subclass WebClient and intercept the method that builds a request. So
here it is:
1408
Notice that I'm using units of measure for the timeout value. I find that units of measure are
invaluable to distiguish seconds from milliseconds. I once accidentally set a timeout to 2000
seconds rather than 2000 milliseconds and I don't want to make that mistake again!
The next bit of code defines our domain types. We want to be able to keep the url and the
size together as we process them. We could use a tuple, but I am a proponent of using
types to model your domain, if only for documentation.
// The content of a downloaded page
type UriContent =
UriContent of System.Uri * string
// The content size of a downloaded page
type UriContentSize =
UriContentSize of System.Uri * int
Yes, this might be overkill for a trivial example like this, but in a more serious project I think it
is very much worth doing.
Now for the code that does the downloading:
1409
Notes:
The .NET library will throw on various errors, so I am catching that and turning it into a
Failure .
The use client = section ensures that the client will be correctly disposed at the end
of the block.
The whole operation is wrapped in an async workflow, and the let! html =
client.AsyncDownloadString is where the download happens asynchronously.
I've added some printfn s for tracing, just for this example. In real code, I wouldn't do
this of course!
Before moving on, let's test this code interactively. First we need a helper to print the result:
let showContentResult result =
match result with
| Success (UriContent (uri, html)) ->
printfn "SUCCESS: [%s] First 100 chars: %s" uri.Host (html.Substring(0,100))
| Failure errs ->
printfn "FAILURE: %A" errs
1410
System.Uri ("http://google.com")
|> getUriContent
|> Async.RunSynchronously
|> showContentResult
// [google.com] Started ...
// [google.com] ... finished
// SUCCESS: [google.com] First 100 chars: <!doctype html><html itemscope="" itemtype="
http://schema.org/WebPage" lang="en-GB"><head><meta cont
1411
module Async =
let map f xAsync = async {
// get the contents of xAsync
let! x = xAsync
// apply the function and lift the result
return f x
}
let retn x = async {
// lift x to an Async
return x
}
let apply fAsync xAsync = async {
// start the two asyncs in parallel
let! fChild = Async.StartChild fAsync
let! xChild = Async.StartChild xAsync
// wait for the results
let! f = fChild
let! x = xChild
// apply the function to the results
return f x
}
let bind f xAsync = async {
// get the contents of xAsync
let! x = xAsync
// apply the function but don't lift the result
// as f will return an Async
return! f x
}
The apply function runs the two parameters in parallel using a fork/join pattern. If I had
instead written let! fChild = ... followed by a let! xChild = ... that would have
been monadic and sequential, which is not what I wanted.
The return! syntax in bind means that the value is already lifted and not to call
return on it.
1412
If the input html is null or empty we'll treat this an error, otherwise we'll return a
UriContentSize .
Now we have two functions and we want to combine them into one "get UriContentSize
given a Uri" function. The problem is that the outputs and inputs don't match:
getUriContent is Uri -> Async<Result<UriContent>>
makeContentSize is UriContent -> Result<UriContentSize>
Next, use Async.map to convert it from an a -> b function to a Async<a> -> Async<b>
function. In this case, Result<UriContent> -> Result<UriContentSize> becomes
Async<Result<UriContent>> -> Async<Result<UriContentSize>> .
1413
And now that it has the right kind of input, so we can compose it with getUriContent :
/// Get the size of the contents of the page at the given Uri
/// Uri -> Async<Result<UriContentSize>>
let getUriContentSize uri =
getUriContent uri
|> Async.map (Result.bind makeContentSize)
That's some gnarly type signature, and it's only going to get worse! It's at times like these
that I really appreciate type inference.
Let's test again. First a helper to format the result:
let showContentSizeResult result =
match result with
| Success (UriContentSize (uri, len)) ->
printfn "SUCCESS: [%s] Content size is %i" uri.Host len
| Failure errs ->
printfn "FAILURE: %A" errs
1414
System.Uri ("http://google.com")
|> getUriContentSize
|> Async.RunSynchronously
|> showContentSizeResult
// [google.com] Started ...
// [google.com] ... finished
//SUCCESS: [google.com] Content size is 44293
1415
into a Async<List> .
Next we need to swap the bottom two parts of the stack -- transform a List<Result>
into a Result<List> . But the two bottom parts of the stack are wrapped in an Async so
we need to use Async.map to do this.
Finally we need to use List.maxBy on the bottom List to convert it into a single
value. That is, transform a List<UriContentSize> into a UriContentSize . But the
bottom of the stack is wrapped in a Result wrapped in an Async so we need to use
Async.map and Result.map to do this.
1416
This function has signature string list -> Async<Result<UriContentSize>> , which is just
what we wanted!
There are two sequence functions involved here: sequenceAsyncA and sequenceResultA .
The implementations are as you would expect from all the previous discussion, but I'll show
the code anyway:
1417
module List =
/// Map a Async producing function over a list to get a new Async
/// using applicative style
/// ('a -> Async<'b>) -> 'a list -> Async<'b list>
let rec traverseAsyncA f list =
// define the applicative functions
let (<*>) = Async.apply
let retn = Async.retn
// define a "cons" function
let cons head tail = head :: tail
// right fold over the list
let initState = retn []
let folder head tail =
retn cons <*> (f head) <*> tail
List.foldBack folder list initState
/// Transform a "list<Async>" into a "Async<list>"
/// and collect the results using apply.
let sequenceAsyncA x = traverseAsyncA id x
/// Map a Result producing function over a list to get a new Result
/// using applicative style
/// ('a -> Result<'b>) -> 'a list -> Result<'b list>
let rec traverseResultA f list =
// define the applicative functions
let (<*>) = Result.apply
let retn = Result.Success
// define a "cons" function
let cons head tail = head :: tail
// right fold over the list
let initState = retn []
let folder head tail =
retn cons <*> (f head) <*> tail
List.foldBack folder list initState
/// Transform a "list<Result>" into a "Result<list>"
/// and collect the results using apply.
let sequenceResultA x = traverseResultA id x
Adding a timer
1418
It will be interesting to see how long the download takes for different scenarios, so let's
create a little timer that runs a function a certain number of times and takes the average:
/// Do countN repetitions of the function f and print the time per run
let time countN label f =
let stopwatch = System.Diagnostics.Stopwatch()
// do a full GC at the start but not thereafter
// allow garbage to collect for each iteration
System.GC.Collect()
printfn "======================="
printfn "%s" label
printfn "======================="
let mutable totalMs = 0L
for iteration in [1..countN] do
stopwatch.Restart()
f()
stopwatch.Stop()
printfn "#%2i elapsed:%6ims " iteration stopwatch.ElapsedMilliseconds
totalMs <- totalMs + stopwatch.ElapsedMilliseconds
let avgTimePerRun = totalMs / int64 countN
printfn "%s: Average time per run:%6ims " label avgTimePerRun
1419
Let's start by running largestPageSizeA 10 times with the good sites list:
let f() =
largestPageSizeA goodSites
|> Async.RunSynchronously
|> showContentSizeResult
time 10 "largestPageSizeA_Good" f
We can see immediately that the downloads are happening in parallel -- they have all started
before the first one has finished.
Now what about if some of the sites are bad?
let f() =
largestPageSizeA badSites
|> Async.RunSynchronously
|> showContentSizeResult
time 10 "largestPageSizeA_Bad" f
1420
Again, all the downloads are happening in parallel, and all four failures are returned.
Optimizations
The largestPageSizeA has a series of maps and sequences in it which means that the list is
being iterated over three times and the async mapped over twice.
As I said earlier, I prefer clarity over micro-optimizations unless there is proof otherwise, and
so this does not bother me.
However, let's look at what you could do if you wanted to.
Here's the original version, with comments removed:
let largestPageSizeA urls =
urls
|> List.map (fun s -> System.Uri(s))
|> List.map getUriContentSize
|> List.sequenceAsyncA
|> Async.map List.sequenceResultA
|> Async.map (Result.map maxContentSize)
1421
Personally, I think we've gone too far here. I prefer the original version to this one!
As an aside, one way to get the best of both worlds is to use a "streams" library that
automatically merges the maps for you. In F#, a good one is Nessos Streams. Here is a blog
post showing the difference between streams and the standard seq .
This one uses the monadic sequence functions (I won't show them -- the implementation is
as you expect).
1422
Let's run largestPageSizeM 10 times with the good sites list and see if there is any
difference from the applicative version:
let f() =
largestPageSizeM goodSites
|> Async.RunSynchronously
|> showContentSizeResult
time 10 "largestPageSizeM_Good" f
There is a big difference now -- it is obvious that the downloads are happening in series -each one starts only when the previous one has finished.
As a result, the average time is 955ms per run, almost twice that of the applicative version.
Now what about if some of the sites are bad? What should we expect? Well, because it's
monadic, we should expect that after the first error, the remaining sites are skipped, right?
Let's see if that happens!
let f() =
largestPageSizeM badSites
|> Async.RunSynchronously
|> showContentSizeResult
time 10 "largestPageSizeM_Bad" f
1423
Well that was unexpected! All of the sites were visited in series, even though the first one
had an error. But in that case, why is only the first error returned, rather than all the the
errors?
Can you see what went wrong?
If you step through this in a debugger you can see what is happening:
The first Async in the list was run, resulting in a failure.
Async.bind was used with the next Async in the list. But Async.bind has no concept
1424
And in order to do that, we need to treat the Async and the Result as a single type -- let's
imaginatively call it AsyncResult .
If they are a single type, then bind looks like this:
meaning that the previous value will determine the next value.
And also, the "swapping" becomes much simpler:
1425
Notes:
The type alias is optional. We can use Async<Result<'a>> directly in the code and it wil
work fine. The point is that conceptually AsyncResult is a separate type.
The bind implementation is new. The continuation function f is now crossing two
worlds, and has the signature 'a -> Async<Result<'b>> .
If the inner Result is successful, the continuation function f is evaluated with the
result. The return! syntax means that the return value is already lifted.
If the inner Result is a failure, we have to lift the failure to an Async.
1426
module List =
/// Map an AsyncResult producing function over a list to get a new AsyncResult
/// using monadic style
/// ('a -> AsyncResult<'b>) -> 'a list -> AsyncResult<'b list>
let rec traverseAsyncResultM f list =
// define the monadic functions
let (>>=) x f = AsyncResult.bind f x
let retn = AsyncResult.retn
// define a "cons" function
let cons head tail = head :: tail
// right fold over the list
let initState = retn []
let folder head tail =
f head >>= (fun h ->
tail >>= (fun t ->
retn (cons h t) ))
List.foldBack folder list initState
/// Transform a "list<AsyncResult>" into a "AsyncResult<list>"
/// and collect the results using bind.
let sequenceAsyncResultM x = traverseAsyncResultM id x
Let's run largestPageSizeM_AR 10 times with the good sites list and see if there is any
difference from the applicative version:
let f() =
largestPageSizeM_AR goodSites
|> Async.RunSynchronously
|> showContentSizeResult
time 10 "largestPageSizeM_AR_Good" f
1427
Again, the downloads are happening in series. And again, the time per run is almost twice
that of the applicative version.
And now the moment we've been waiting for! Will it skip the downloading after the first bad
site?
let f() =
largestPageSizeM_AR badSites
|> Async.RunSynchronously
|> showContentSizeResult
time 10 "largestPageSizeM_AR_Bad" f
Success! The error from the first bad site prevented the rest of the downloads, and the short
run time is proof of that.
Summary
In this post, we worked through a small practical example. I hope that this example
demonstrated that map , apply , bind , traverse , and sequence are not just academic
abstractions but essential tools in your toolbelt.
In the next post we'll working through another practical example, but this time we will end up
creating our own elevated world. See you then!
1428
1429
Series contents
Here's a list of shortcuts to the various functions mentioned in this series:
Part 1: Lifting to the elevated world
The map function
The return function
The apply function
The liftN family of functions
The zip function and ZipList world
Part 2: How to compose world-crossing functions
The bind function
List is not a monad. Option is not a monad.
Part 3: Using the core functions in practice
Independent and dependent data
Example: Validation using applicative style and monadic style
Lifting to a consistent world
Kleisli world
Part 4: Mixing lists and elevated values
Mixing lists and elevated values
The traverse / MapM function
The sequence function
"Sequence" as a recipe for ad-hoc implementations
Readability vs. performance
Dude, where's my filter ?
1430
1431
For the product information, we'll just define a simple ProductInfo with a ProductName
field.
Here are the types:
type CustId = CustId of string
type ProductId = ProductId of string
type ProductInfo = {ProductName: string; }
For testing our api, let's create an ApiClient class with some Get and Set methods,
backed by a static mutable dictionary. This is based on similar APIs such as the Redis client.
Notes:
The Get and Set both work with objects, so I've added a casting mechanism.
In case of errors such as a failed cast, or a missing key, I'm using the Result type that
we've been using throughout this series. Therefore, both Get and Set return
Result s rather than plain objects.
To make it more realistic, I've also added dummy methods for Open , Close and
Dispose .
1432
type ApiClient() =
// static storage
static let mutable data = Map.empty<string,obj>
/// Try casting a value
/// Return Success of the value or Failure on failure
member private this.TryCast<'a> key (value:obj) =
match value with
| :? 'a as a ->
Result.Success a
| _ ->
let typeName = typeof<'a>.Name
Result.Failure [sprintf "Can't cast value at %s to %s" key typeName]
/// Get a value
member this.Get<'a> (id:obj) =
let key = sprintf "%A" id
printfn "[API] Get %s" key
match Map.tryFind key data with
| Some o ->
this.TryCast<'a> key o
| None ->
Result.Failure [sprintf "Key %s not found" key]
/// Set a value
member this.Set (id:obj) (value:obj) =
let key = sprintf "%A" id
printfn "[API] Set %s" key
if key = "bad" then // for testing failure paths
Result.Failure [sprintf "Bad Key %s " key]
else
data <- Map.add key value data
Result.Success ()
member this.Open() =
printfn "[API] Opening"
member this.Close() =
printfn "[API] Closing"
interface System.IDisposable with
member this.Dispose() =
printfn "[API] Disposing"
1433
do
use api = new ApiClient()
api.Get "K1" |> printfn "[K1] %A"
api.Set "K2" "hello" |> ignore
api.Get<string> "K2" |> printfn "[K2] %A"
api.Set "K3" "hello" |> ignore
api.Get<int> "K3" |> printfn "[K3] %A"
The getPurchaseInfo function takes a CustId as input, but it can't just output a list of
ProductInfo s, because there might be a failure. That means that the return type needs to
be Result<ProductInfo list> .
Ok, how do we create our productInfosResult ?
Well that should be easy. If the productIdsResult is Success, then loop through each id and
get the info for each id. If the productIdsResult is Failure, then just return that failure.
let getPurchaseInfo (custId:CustId) : Result<ProductInfo list> =
// Open api connection
use api = new ApiClient()
api.Open()
// Get product ids purchased by customer id
let productIdsResult = api.Get<ProductId list> custId
let productInfosResult =
match productIdsResult with
| Success productIds ->
let productInfos = ResizeArray() // Same as .NET List<T>
for productId in productIds do
let productInfo = api.Get<ProductInfo> productId
productInfos.Add productInfo // mutation!
Success productInfos
| Failure err ->
Failure err
// Close api connection
api.Close()
// Return the list of product infos
productInfosResult
Hmmm. It's looking a bit ugly. And I'm having to use a mutable data structure
( productInfos ) to accumulate each product info and then wrap it in Success .
And there's a worse problem The productInfo that I'm getting from api.Get<ProductInfo>
is not a ProductInfo at all, but a Result<ProductInfo> , so productInfos is not the right
type at all!
Let's add code to test each ProductInfo result. If it's a success, then add it to the list of
product infos, and if it's a failure, then return the failure.
1435
Um, no. That won't work at all. The code above will not compile. We can't do an "early
return" in the loop when a failure happens.
So what do we have so far? Some really ugly code that won't even compile.
There has to be a better way.
1436
1437
module Result =
let bind f xResult = ...
type ResultBuilder() =
member this.Return x = retn x
member this.ReturnFrom(m: Result<'T>) = m
member this.Bind(x,f) = bind f x
member this.Zero() = Failure []
member this.Combine (x,f) = bind f x
member this.Delay(f: unit -> _) = f
member this.Run(f) = f()
member this.TryFinally(m, compensation) =
try this.ReturnFrom(m)
finally compensation()
member this.Using(res:#System.IDisposable, body) =
this.TryFinally(body res, fun () ->
match res with
| null -> ()
| disp -> disp.Dispose())
member this.While(guard, f) =
if not (guard()) then
this.Zero()
else
this.Bind(f(), fun _ -> this.While(guard, f))
member this.For(sequence:seq<_>, body) =
this.Using(sequence.GetEnumerator(), fun enum ->
this.While(enum.MoveNext, this.Delay(fun () ->
body enum.Current)))
let result = new ResultBuilder()
I have a series about the internals of computation expressions, so I don't want to explain all
that code here. Instead, for the rest of the post we'll work on refactoring getPurchaseInfo ,
and by the end of it we'll see that we don't need the result computation expression at all.
1438
The action function that is passed in would look like this, with a parameter for the ApiClient
as well as for the CustId :
/// CustId -> ApiClient -> Result<ProductInfo list>
let getPurchaseInfo (custId:CustId) (api:ApiClient) =
let productInfosResult = Result.result {
let! productIds = api.Get<ProductId list> custId
let productInfos = ResizeArray() // Same as .NET List<T>
for productId in productIds do
let! productInfo = api.Get<ProductInfo> productId
productInfos.Add productInfo
return productInfos |> List.ofSeq
}
// return result
productInfosResult
Note that getPurchaseInfo has two parameters, but executeApiAction expects a function
with only one.
No problem! Just use partial application to bake in the first parameter:
1439
That's why the ApiClient is the second parameter in the parameter list -- so that we can do
partial application.
More refactoring
We might need to get the product ids for some other purpose, and also the productInfo, so
let's refactor those out into separate functions too:
/// CustId -> ApiClient -> Result<ProductId list>
let getPurchaseIds (custId:CustId) (api:ApiClient) =
api.Get<ProductId list> custId
/// ProductId -> ApiClient -> Result<ProductInfo>
let getProductInfo (productId:ProductId) (api:ApiClient) =
api.Get<ProductInfo> productId
/// CustId -> ApiClient -> Result<ProductInfo list>
let getPurchaseInfo (custId:CustId) (api:ApiClient) =
let result = Result.result {
let! productIds = getPurchaseIds custId api
let productInfos = ResizeArray()
for productId in productIds do
let! productInfo = getProductInfo productId api
productInfos.Add productInfo
return productInfos |> List.ofSeq
}
// return result
result
Now, we have these nice core functions getPurchaseIds and getProductInfo , but I'm
annoyed that I have to write messy code to glue them together in getPurchaseInfo .
Ideally, what I'd like to do is pipe the output of getPurchaseIds into getProductInfo like this:
let getPurchaseInfo (custId:CustId) =
custId
|> getPurchaseIds
|> List.map getProductInfo
Or as a diagram:
1440
If we look at the type signature we see this, a function with two parameters:
But another way to interpret this function is as a function with one parameter that returns
another function. The returned function has an ApiClient parameter and returns the final
ouput.
You might think of it like this: I have an input right now, but I won't have an actual ApiClient
until later, so let me use the input to create a api-consuming function that can I glue together
in various ways right now, without needing a ApiClient at all.
Let's give this api-consuming function a name. Let's call it ApiAction .
1441
Unfortunately, as it stands, this is just a type alias for a function, not a separate type. We
need to wrap it in a single case union to make it a distinct type.
type ApiAction<'a> = ApiAction of (ApiClient -> 'a)
The signature is now CustId -> ApiAction<Result<ProductId list>> , which you can interpret
as meaning: "give me a CustId and I will give a you a ApiAction that, when given an api, will
make a list of ProductIds".
Similarly, getProductInfo can be rewritten to return an ApiAction :
1442
This is starting to look awfully familiar. Didn't we see something just like this in the previous
post, with Async<Result<_>> ?
Here's getPurchaseIds as a stack diagram. The input is a CustId and the output is an
ApiAction<Result<List<ProductId>>> :
1443
The combined function that we want, getPurchaseInfo , should look like this:
And now the problem in composing the two functions is very clear: the output of
getPurchaseIds can not be used as the input for getProductInfo :
But I think that you can see that we have some hope! There should be some way of
manipulating these layers so that they do match up, and then we can compose them easily.
So that's what we will work on next.
Introducting ApiActionResult
In the last post we merged Async and Result into the compound type AsyncResult . We
can do the same here, and create the type ApiActionResult .
When we make this change, our two functions become slightly simpler:
1444
module ApiAction =
/// Evaluate the action with a given api
/// ApiClient -> ApiAction<'a> -> 'a
let run api (ApiAction action) =
let resultOfAction = action api
resultOfAction
/// ('a -> 'b) -> ApiAction<'a> -> ApiAction<'b>
let map f action =
let newAction api =
let x = run api action
f x
ApiAction newAction
/// 'a -> ApiAction<'a>
let retn x =
let newAction api =
x
ApiAction newAction
/// ApiAction<('a -> 'b)> -> ApiAction<'a> -> ApiAction<'b>
let apply fAction xAction =
let newAction api =
let f = run api fAction
let x = run api xAction
f x
ApiAction newAction
/// ('a -> ApiAction<'b>) -> ApiAction<'a> -> ApiAction<'b>
let bind f xAction =
let newAction api =
let x = run api xAction
run api (f x)
ApiAction newAction
/// Create an ApiClient and run the action on it
/// ApiAction<'a> -> 'a
let execute action =
use api = new ApiClient()
api.Open()
let result = run api action
api.Close()
result
Note that all the functions use a helper function called run which unwraps an ApiAction to
get the function inside, and applies this to the api that is also passed in. The result is the
value wrapped in the ApiAction .
For example, if we had an ApiAction<int> then run api myAction would result in an int .
1445
And at the bottom, there is a execute function that creates an ApiClient , opens the
connection, runs the action, and then closes the connection.
And with the core functions for ApiAction defined, we can go ahead and define the
functions for the compound type ApiActionResult , just as we did for AsyncResult in the
previous post:
module ApiActionResult =
let map f =
ApiAction.map (Result.map f)
let retn x =
ApiAction.retn (Result.retn x)
let apply fActionResult xActionResult =
let newAction api =
let fResult = ApiAction.run api fActionResult
let xResult = ApiAction.run api xActionResult
Result.apply fResult xResult
ApiAction newAction
let bind f xActionResult =
let newAction api =
let xResult = ApiAction.run api xActionResult
// create a new action based on what xResult is
let yAction =
match xResult with
| Success x ->
// Success? Run the function
f x
| Failure err ->
// Failure? wrap the error in an ApiAction
(Failure err) |> ApiAction.retn
ApiAction.run api yAction
ApiAction newAction
1446
Map
As a reminder, map adds a new stack on both sides. So if we start with a generic worldcrossing function like this:
Then, after List.map say, we will have a new List stack on each site.
1447
This might seem promising -- we have a List of ProductId as input now, and if we can
stack a ApiActionResult on top we would match the output of getPurchaseId .
But the output is all wrong. We want the ApiActionResult to stay on the top. That is, we
don't want a List of ApiActionResult but a ApiActionResult of List .
Bind
Ok, what about bind ?
If you recall, bind turns a "diagonal" function into a horizontal function by adding a new
stack on the left sides. So for example, whatever the top elevated world is on the right, that
will be added to the left.
And here is what our getProductInfo would look like after using ApiActionResult.bind
Traverse
Finally, let's try traverse .
1448
traverse turns a diagonal function of values into diagonal function with lists wrapping the
values. That is, List is added as the top stack on the left hand side, and the second-fromtop stack on the right hand side.
1449
let getPurchaseInfo =
let getProductInfoLifted =
getProductInfo
|> traverse
|> ApiActionResult.bind
getPurchaseIds >> getProductInfoLifted
Latest function
Uses "traverse"
No intermediate values
needed. Just a data pipeline.
Implementing traverse
The code above uses traverse , but we haven't implemented it yet. As I noted earlier, it can
be implemented mechanically, following a template.
Here it is:
1450
1451
As expected, we get a nice "key not found" failure, and the rest of the operations are skipped
as soon as the key is not found.
[API] Opening
[API] Get CustId "CX"
[API] Closing
[API] Disposing
FAILURE: ["Key CustId "CX" not found"]
What about if one of the purchased products has no info? For example, customer C2
purchased PX and P2, but there is no info for PX.
1452
CustId "C2"
|> getPurchaseInfo
|> ApiAction.execute
|> showResult
The overall result is a failure. Any bad product causes the whole operation to fail.
[API] Opening
[API] Get CustId "C2"
[API] Get ProductId "PX"
[API] Get ProductId "P2"
[API] Closing
[API] Disposing
FAILURE: ["Key ProductId "PX" not found"]
But note that the data for product P2 is fetched even though product PX failed. Why?
Because we are using the applicative version of traverse , so every element of the list is
fetched "in parallel".
If we wanted to only fetch P2 once we knew that PX existed, then we should be using
monadic style instead. We already seen how to write a monadic version of traverse , so I
leave that as an exercise for you!
1453
module ApiActionResult =
let map = ...
let retn = ...
let apply = ...
let bind = ...
let either onSuccess onFailure xActionResult =
let newAction api =
let xResult = ApiAction.run api xActionResult
let yAction =
match xResult with
| Result.Success x -> onSuccess x
| Result.Failure err -> onFailure err
ApiAction.run api yAction
ApiAction newAction
This helper function helps us match both cases inside a ApiAction without doing
complicated unwrapping. We will need this for our traverse that skips failures.
By the way, note that ApiActionResult.bind can be defined in terms of either :
let bind f =
either
// Success? Run the function
(fun x -> f x)
// Failure? wrap the error in an ApiAction
(fun err -> (Failure err) |> ApiAction.retn)
1454
The only difference between this and the previous implementation is this bit:
let folder head tail =
(f head)
|> ApiActionResult.either
(fun h -> retn cons <*> retn h <*> tail)
(fun errs -> log errs; tail)
The result is a Success now, but only one ProductInfo , for P2, is returned. The log shows
that PX was skipped.
[API] Opening
[API] Get CustId "C2"
[API] Get ProductId "PX"
SKIPPED ["Key ProductId "PX" not found"]
[API] Get ProductId "P2"
[API] Closing
[API] Disposing
SUCCESS: [{ProductName = "P2-Name";}]
1455
But if it can be anything, why call it ApiAction any more? It could represent any set of
things that depend on an object (such as an api ) being passed in to them.
We are not the first people to discover this! This type is commonly called the Reader type
and is defined like this:
type Reader<'environment,'a> = Reader of ('environment -> 'a)
The extra type 'environment plays the same role that ApiClient did in our definition of
ApiAction . There is some environment that is passed around as an extra parameter to all
The set of functions for Reader are exactly the same as for ApiAction . I have just taken
the code and replaced ApiAction with Reader and api with environment !
1456
module Reader =
/// Evaluate the action with a given environment
/// 'env -> Reader<'env,'a> -> 'a
let run environment (Reader action) =
let resultOfAction = action environment
resultOfAction
/// ('a -> 'b) -> Reader<'env,'a> -> Reader<'env,'b>
let map f action =
let newAction environment =
let x = run environment action
f x
Reader newAction
/// 'a -> Reader<'env,'a>
let retn x =
let newAction environment =
x
Reader newAction
/// Reader<'env,('a -> 'b)> -> Reader<'env,'a> -> Reader<'env,'b>
let apply fAction xAction =
let newAction environment =
let f = run environment fAction
let x = run environment xAction
f x
Reader newAction
/// ('a -> Reader<'env,'b>) -> Reader<'env,'a> -> Reader<'env,'b>
let bind f xAction =
let newAction environment =
let x = run environment xAction
run environment (f x)
Reader newAction
1457
Personally, I think that while understanding the concept behind the Reader monad is
important and useful, I prefer the actual implementation of ApiAction as I defined it
originally, an explicit type rather than an alias for Reader<ApiClient,'a> .
Why? Well, F# doesn't have typeclasses, F# doesn't have partial application of type
constructors, F# doesn't have "newtype". Basically, F# isn't Haskell! I don't think that idioms
that work well in Haskell should be carried over to F# directly when the language does not
offer support for it.
If you understand the concepts, you can implement all the necessary transformations in a
few lines of code. Yes, it's a little extra work, but the upside is less abstraction and fewer
dependencies.
I would make an exception, perhaps, if your team were all Haskell experts, and the Reader
monad was familiar to everyone. But for teams of different abilities, I would err on being too
concrete rather than too abstract.
Summary
In this post, we worked through another practical example, created our own elevated world
which made things much easier, and in the process, accidentally re-invented the reader
monad.
If you liked this, you can see a similar practical example, this time for the State monad, in my
series on "Dr Frankenfunctor and the Monadster".
The next and final post has a quick summary of the series, and some further reading.
1458
Series contents
1459
1460
Equivalent
function
Operator
Discussion
>>
Left-to-right
composition
<<
Right-to-left
composition
As above
|>
Left-to-right
piping
As above
<|
Right-to-left
piping
As above
<!>
map
Discussed here
<$>
map
<*>
apply
Discussed here
<*
*>
>>=
Left-to-right
Discussed here
bind
=<<
Right-to-left
bind
As above
>=>
Left-to-right
Kleisli
composition
Discussed here
<=<
Right-to-left
Kleisli
composition
As above
1461
papers.
Applicative Programming with Effects (PDF), by Conor McBride and Ross Paterson.
The Essence of the Iterator Pattern (PDF), by Jeremy Gibbons and Bruno Oliveira.
F# examples:
F# ExtCore and FSharpx.Extras have lots of useful code.
FSharpx.Async has map , apply , liftN (called "Parallel"), bind , and other useful
extensions for Async .
Applicatives are very well suited for parsing, as explained in these posts:
Parsing with applicative functors in F#.
Dive into parser combinators: parsing search queries with F# and FParsec in Kiln.
1462
In this series, we'll look at recursive types and how to use them, and on the way, we'll look at
catamorphisms, tail recursion, the difference between left and right folds, and more.
Introduction to recursive types. Don't fear the catamorphism....
Catamorphism examples. Applying the rules to other domains.
Introducing Folds. Threading state through a recursive data structure.
Understanding Folds. Recursion vs. iteration.
Generic recursive types. Implementing a domain in three ways.
Trees in the real world. Examples using databases, JSON and error handling.
1463
Series contents
Here's the contents of this series:
Part 1: Introduction to recursive types and catamorphisms
A simple recursive type
Parameterize all the things
Introducing catamorphisms
Benefits of catamorphisms
Rules for creating a catamorphism
Part 2: Catamorphism examples
Catamorphism example: File system domain
Catamorphism example: Product domain
Part 3: Introducing folds
A flaw in our catamorphism implementation
Introducing fold
Problems with fold
Using functions as accumulators
Introducing foldback
Rules for creating a fold
Part 4: Understanding folds
Iteration vs. recursion
Fold example: File system domain
Common questions about "fold"
Part 5: Generic recursive types
LinkedList: A generic recursive type
Making the Gift domain generic
Defining a generic Container type
A third way to implement the gift domain
Abstract or concrete? Comparing the three designs
Part 6: Trees in the real world
Defining a generic Tree type
The Tree type in the real world
1464
You can see that three of the cases are "containers" that refer to another Gift . The
Wrapped case has some paper and a inner gift, as does the Boxed case, as does the
WithACard case. The two other cases, Book and Chocolate , do not refer to a gift and can
1465
It is possible to create such types if you allow laziness, mutation, or reflection. But in
general, in a non-lazy language like F#, it's a good idea to avoid such types.
1466
First, say that we want a description of the gift. The logic will be:
For the two non-recursive cases, return a string describing that case.
For the three recursive cases, return a string that describes the case, but also includes
the description of the inner gift. This means that description function is going to refer
to itself, and therefore it must be marked with the rec keyword.
Here's an example implementation:
let rec description gift =
match gift with
| Book book ->
sprintf "'%s'" book.title
| Chocolate choc ->
sprintf "%A chocolate" choc.chocType
| Wrapped (innerGift,style) ->
sprintf "%s wrapped in %A paper" (description innerGift) style
| Boxed innerGift ->
sprintf "%s in a box" (description innerGift)
| WithACard (innerGift,message) ->
sprintf "%s with a card saying '%s'" (description innerGift) message
Note the recursive calls like this one in the Boxed case:
| Boxed innerGift ->
sprintf "%s in a box" (description innerGift)
~~~~~~~~~~~ <= recursive call
If we try this with our example values, let's see what we get:
birthdayPresent |> description
// "'Wolf Hall' wrapped in HappyBirthday paper with a card saying 'Happy Birthday'"
christmasPresent |> description
// "SeventyPercent chocolate in a box wrapped in HappyHolidays paper"
That looks pretty good to me. Things like HappyHolidays look a bit funny without spaces, but
it's good enough to demonstrate the idea.
What about creating another function? For example, what is the total cost of a gift?
For totalCost , the logic will be:
Books and chocolate capture the price in the case-specific data, so use that.
Wrapping adds 0.5 to the cost.
A box adds 1.0 to the cost.
1467
Sometimes, people ask what is inside the box or wrapping paper. A whatsInside function is
easy to implement -- just ignore the container cases and return something for the nonrecursive cases.
let rec whatsInside gift =
match gift with
| Book book ->
"A book"
| Chocolate choc ->
"Some chocolate"
| Wrapped (innerGift,style) ->
whatsInside innerGift
| Boxed innerGift ->
whatsInside innerGift
| WithACard (innerGift,message) ->
whatsInside innerGift
1468
You can see this function is created using a purely mechanical process:
Each function parameter ( fBook , fChocolate , etc) corresponds to a case.
For the two non-recursive cases, the function parameter is passed all the data
associated with that case.
For the three recursive cases, there are two steps:
First, the cataGift function is called recursively on the innerGift to get an
innerGiftResult
Then the appropriate handler is passed all the data associated with that case, but
with innerGiftResult replacing innerGift .
Let's rewrite total cost using the generic cataGift function.
1469
Notes:
The innerGiftResult is now the total cost of the inner gift, so I have renamed it to
innerCost .
The totalCostUsingCata function itself is not recursive, because it uses the cataGift
function, and so no longer needs the rec keyword.
And this function gives the same result as before:
birthdayPresent |> totalCostUsingCata
// 22.5m
We can rewrite the description function using cataGift in the same way, changing
innerGiftResult to innerText .
1470
Introducing catamorphisms
The cataGift function we wrote above is called a "catamorphism", from the Greek
components "down + shape". In normal usage, a catamorphism is a function that "collapses"
a recursive type into a new value based on its structure. In fact, you can think of a
catamorphism as a sort of "visitor pattern".
A catamorphism is very powerful concept, because it is the most fundamental function that
you can define for a structure like this. Any other function can be defined in terms of it.
That is, if we want to create a function with signature Gift -> string or Gift -> int , we
can use a catamorphism to create it by specifying a function for each case in the Gift
structure.
We saw above how we could rewrite totalCost as totalCostUsingCata using the
catamorphism, and we'll see lots of other examples later.
1471
The recurse function has the simple signature Gift -> 'a -- that is, it converts a Gift to
the return type we need, and so we can use it to work with the various innerGift values.
The other thing is to replace innerGift with just gift in the recursive cases -- this is
called "shadowing". The benefit is that the "outer" gift is no longer visible to the casehandling code, and so we can't accidentally recurse into it, which would cause an infinite
loop.
Generally I avoid shadowing, but this is one case where it actually is a good practice,
because it eliminates a particularly nasty kind of bug.
Here's the version after the clean up:
let rec cataGift fBook fChocolate fWrapped fBox fCard gift =
let recurse = cataGift fBook fChocolate fWrapped fBox fCard
match gift with
| Book book ->
fBook book
| Chocolate choc ->
fChocolate choc
| Wrapped (gift,style) ->
fWrapped (recurse gift,style)
| Boxed gift ->
fBox (recurse gift)
| WithACard (gift,message) ->
fCard (recurse gift,message)
One more thing. I'm going to explicitly annotate the return type and call it 'r . Later on in
this series we'll be dealing with other generic types such as 'a and 'b , so it will be helpful
to be consistent and always have a standard name for the return type.
1472
let rec cataGift fBook fChocolate fWrapped fBox fCard gift :'r =
// name the return type => ~~~~
It's much simpler than the original implementation, and also demonstrates the symmetry
between a case constructor like Wrapped (gift,style) and the corresponding handler
fWrapped (recurse gift,style) . Which leads us nicely to...
Another way to think about this is that: everywhere that there is a Gift type in the
constructor, it has been replaced with an 'r .
For example:
1473
The Gift.Book constructor takes a Book and returns a Gift . The fBook handler
takes a Book and returns an 'r .
The Gift.Wrapped constructor takes a Gift * WrappingPaperStyle and returns a Gift .
The fWrapped handler takes an 'r * WrappingPaperStyle as input and returns an 'r .
Here is that relationship expressed through type signatures:
// The Gift.Book constructor
Book -> Gift
// The fBook handler
Book -> 'r
// The Gift.Wrapped constructor
Gift * WrappingPaperStyle -> Gift
// The fWrapped handler
'r * WrappingPaperStyle -> 'r
// The Gift.Boxed constructor
Gift -> Gift
// The fBox handler
'r -> 'r
Benefits of catamorphisms
There is a lot of theory behind catamorphisms, but what are the benefits in practice?
Why bother to create a special function like cataGift ? Why not just leave the original
functions alone?
There are number of reasons, including:
Reuse. Later, we will be creating quite complex catamorphisms. It's nice if you only
have to get the logic right once.
Encapsulation. By exposing functions only, you hide the internal structure of the data
type.
Flexibility. Functions are more flexible than pattern matching -- they can be composed,
partially applied, etc.
Mapping. With a catamorphism in hand you can easily create functions that map the
various cases to new structures.
1474
It's true that most of these benefits apply to non-recursive types as well, but recursive types
tend to be more complex so the benefits of encapsulation, flexibility, etc. are correspondingly
stronger.
In the following sections, we'll look at the last three points in more detail.
type Gift =
| Book of Book
| Chocolate of Chocolate
| Wrapped of Gift * WrappingPaperStyle
| Boxed of Gift
And say that we built and published a catamorphism function for that structure:
let rec cataGift fBook fChocolate fWrapped fBox gift :'r =
let recurse = cataGift fBook fChocolate fWrapped fBox
match gift with
| Book book ->
fBook book
| Chocolate choc ->
fChocolate choc
| Wrapped (gift,style) ->
fWrapped (recurse gift,style)
| Boxed gift ->
fBox (recurse gift)
1475
type Gift =
| Book of Book
| Chocolate of Chocolate
| Wrapped of Gift * WrappingPaperStyle
| Boxed of Gift
| WithACard of Gift * message:string
This function still has only four function parameters -- there is no special behavior for the
WithACard case.
There are a number of alternative ways of being compatible, such as returning a default
value. The important point is that the clients are not aware of the change.
Aside: Using active patterns to hide data
While we're on the topic of hiding the structure of a type, I should mention that you can also
use active patterns to do this.
For example, we could create a active pattern for the first four cases, and ignore the
WithACard case.
1476
The clients can pattern match on the four cases without knowing that the new case even
exists:
let rec whatsInside gift =
match gift with
| Book book ->
"A book"
| Chocolate choc ->
"Some chocolate"
| Wrapped (gift,style) ->
whatsInside gift
| Boxed gift ->
whatsInside gift
1477
And here it is in use, with the two remaining cases handled "inline" using piping:
birthdayPresent
|> handleContents
(fun book -> "The book you wanted for your birthday")
(fun choc -> "Your fave chocolate")
// Result => "The book you wanted for your birthday"
christmasPresent
|> handleContents
(fun book -> "The book you wanted for Christmas")
(fun choc -> "Don't eat too much over the holidays!")
// Result => "Don't eat too much over the holidays!"
Of course this could be done with pattern matching, but being able to work with the existing
cataGift function directly makes life easier.
We can easily map from a Gift to a GiftMinusChocolate , because the cases are almost
parallel.
A Book is passed through untouched.
Chocolate is eaten and replaced with an Apology .
And if we test...
birthdayPresent |> removeChocolate
// GiftMinusChocolate =
// Wrapped (Book {title = "Wolf Hall"; price = 20M}, HappyBirthday)
christmasPresent |> removeChocolate
// GiftMinusChocolate =
// Wrapped (Apology "sorry I ate your chocolate", HappyHolidays)
Deep copying
One more thing. Remember that each case-handling function takes the data associated with
that case? That means that we can just use the original case constructors as the functions!
To see what I mean, let's define a function called deepCopy that clones the original value.
Each case handler is just the corresponding case constructor:
1479
We can simplify this further by removing the redundant parameters for each handler:
let deepCopy gift =
let fBook = Book
let fChocolate = Chocolate
let fWrapped = Wrapped
let fBox = Boxed
let fCard = WithACard
// call the catamorphism
cataGift fBook fChocolate fWrapped fBox fCard gift
1480
If you are thinking that this is beginning to smell like a map , you'd be right. We'll look at
generic maps in the sixth post, as part of a discussion of generic recursive types.
Summary
We've seen in this post how to define a recursive type, and been introduced to
catamorphisms.
1481
In the next post we'll uses these rules to create catamorphisms for some other domains.
See you then!
The source code for this post is available at this gist.
1482
Catamorphism examples
Catamorphism examples
This post is the second in a series.
In the previous post, I introduced "catamorphisms", a way of creating functions for recursive
types, and listed some rules which can be used to implement them mechanically. In this
post, we'll use these rules to implement catamorphisms for some other domains.
Series contents
Here's the contents of this series:
Part 1: Introduction to recursive types and catamorphisms
A simple recursive type
Parameterize all the things
Introducing catamorphisms
Benefits of catamorphisms
Rules for creating a catamorphism
Part 2: Catamorphism examples
Catamorphism example: File system domain
Catamorphism example: Product domain
Part 3: Introducing folds
A flaw in our catamorphism implementation
Introducing fold
Problems with fold
Using functions as accumulators
Introducing foldback
Rules for creating a fold
Part 4: Understanding folds
Iteration vs. recursion
Fold example: File system domain
Common questions about "fold"
Part 5: Generic recursive types
LinkedList: A generic recursive type
Making the Gift domain generic
Defining a generic Container type
A third way to implement the gift domain
Abstract or concrete? Comparing the three designs
1483
Catamorphism examples
1484
Catamorphism examples
type FileSystemItem =
| File of File
| Directory of Directory
and File = {name:string; fileSize:int}
and Directory = {name:string; dirSize:int; subitems:FileSystemItem list}
I admit it's a pretty bad model, but it's just good enough for this example!
Ok, here are some sample files and directories:
let readme = File {name="readme.txt"; fileSize=1}
let config = File {name="config.xml"; fileSize=2}
let build = File {name="build.bat"; fileSize=3}
let src = Directory {name="src"; dirSize=10; subitems=[readme; config; build]}
let bin = Directory {name="bin"; dirSize=10; subitems=[]}
let root = Directory {name="root"; dirSize=5; subitems=[src; bin]}
That's simple enough. Let's put together an initial skeleton of cataFS , as I'll call it:
let rec cataFS fFile fDir item :'r =
let recurse = cataFS fFile fDir
match item with
| File file ->
fFile file
| Directory dir ->
// to do
The Directory case is more complicated. If we naively applied the guidelines above, the
handler for the Directory case would have the signature Directory -> 'r , but that would
be incorrect, because the Directory record itself contains a FileSystemItem that needs to
be replaced with an 'r too. How can we do this?
1485
Catamorphism examples
Another issue is that the data associated with the Directory case is a list of
FileSystemItem s. How can we convert that into a list of 'r s?
Well, the recurse helper turns a FileSystemItem into an 'r , so we can just use List.map
passing in recurse as the mapping function, and that will give us the list of 'r s we need!
Putting it all together, we get this implementation:
let rec cataFS fFile fDir item :'r =
let recurse = cataFS fFile fDir
match item with
| File file ->
fFile file
| Directory dir ->
let listOfRs = dir.subitems |> List.map recurse
fDir (dir.name,dir.dirSize,listOfRs)
and if we look at the type signature, we can see that it is just what we want:
val cataFS :
fFile : (File -> 'r) ->
fDir : (string * int * 'r list -> 'r) ->
// input value
FileSystemItem ->
// return value
'r
So we're done. It's a bit complicated to set up, but once built, we have a nice reusable
function that can be the basis for many others.
Catamorphism examples
1487
Catamorphism examples
But remember that 'r is not a File but a File option . So that means that subfiles is
not a list of files, but a list of File option .
Now, how can we find the largest one of these? We probably want to use List.maxBy and
pass in the size. But what is the size of a File option ?
Let's write a helper function to provide the size of a File option , using this logic:
If the File option is None , return 0
Else return the size of the file inside the option
Here's the code:
// helper to provide a default if missing
let ifNone deflt opt =
defaultArg opt deflt
// get the file size of an option
let fileSize fileOpt =
fileOpt
|> Option.map (fun file -> file.fileSize)
|> ifNone 0
1488
Catamorphism examples
Again, a little bit tricky to set up, but no more than if we had to write it from scratch without
using a catamorphism at all.
1489
Catamorphism examples
Note that the types are mutally recursive. Product references MadeProduct which
references Component which in turn references Product again.
Here are some example products:
1490
Catamorphism examples
let label =
Bought {name="label"; weight=1; vendor=Some "ACME"}
let bottle =
Bought {name="bottle"; weight=2; vendor=Some "ACME"}
let formulation =
Bought {name="formulation"; weight=3; vendor=None}
let shampoo =
Made {name="shampoo"; weight=10; components=
[
{qty=1; product=formulation}
{qty=1; product=bottle}
{qty=2; product=label}
]}
let twoPack =
Made {name="twoPack"; weight=5; components=
[
{qty=2; product=shampoo}
]}
Now to design the catamorphism, we need to do is replace the Product type with 'r in all
the constructors.
Just as with the previous example, the Bought case is easy:
// case constructor
Bought : BoughtProduct -> Product
// function parameter to handle Bought case
fBought : BoughtProduct -> 'r
The Made case is trickier. We need to expand the MadeProduct into a tuple. But that tuple
contains a Component , so we need to expand that as well. Finally we get to the inner
Product , and we can then mechanically replace that with 'r .
1491
Catamorphism examples
// case constructor
Made : MadeProduct -> Product
// case constructor (MadeProduct unpacked as tuple)
Made : (string,int,Component list) -> Product
// case constructor (Component unpacked as tuple)
Made : (string,int,(int,Product) list) -> Product
// replace with 'r ===> ~~~~~~~ ~~~~~~~
// function parameter to handle Made case
fMade : (string,int,(int,'r) list) -> 'r
When implementing the cataProduct function we need to the same kind of mapping as
before, turning a list of Component into a list of (int,'r) .
We'll need a helper for that:
// Converts a Component into a (int * 'r) tuple
let convertComponentToTuple comp =
(comp.qty,recurse comp.product)
You can see that this uses the recurse function to turn the inner product ( comp.product )
into an 'r and then make a tuple int * 'r .
With convertComponentToTuple available, we can convert all the components to tuples using
List.map :
let componentTuples =
made.components
|> List.map convertComponentToTuple
componentTuples is a list of (int * 'r) , which is just what we need for the fMade function.
1492
Catamorphism examples
1493
Catamorphism examples
That's as we expect.
Try implementing productWeight from scratch, without using a helper function like
cataProduct . Again, it's do-able, but you'll probably waste quite bit of time getting the
We'll also define some helpers to get data from a VendorScore easily:
let vendor vs = vs.vendor
let score vs = vs.score
Now, you can't determine the most used vendor over until you have results from the entire
tree, so both the Bought case and the Made case need to return a list which can added to
as we recurse up the tree. And then, after getting all the scores, we'll sort descending to find
the vendor with the highest one.
So we have to make 'r a VendorScore list , not just an option!
The logic for the Bought case is then:
If the vendor is present, return a VendorScore with score = 1, but as a one-element list
rather than as a single item.
If the vendor is missing, return an empty list.
1494
Catamorphism examples
could be empty.
Here's the complete mostUsedVendor function:
1495
Catamorphism examples
1496
Catamorphism examples
This isn't the only possible implementation of fMade , of course. I could have used
List.fold and done the whole thing in one pass, but this version seems like the most
approach, because the generic catamorphism creates intermediate values (such as the list
of qty * VendorScore option ) which are over general and potentially wasteful.
On other hand, by using the catamorphism, I could focus on the counting logic only and
ignore the recursion logic.
So as always, you should consider the pros and cons of reuse vs. creating from scratch; the
benefits of writing common code once and using it in a standardized way, versus the
performance but extra effort (and potential bugginess) of custom code.
Summary
We've seen in this post how to define a recursive type, and been introduced to
catamorphisms.
And we have also seen some uses for catamorphisms:
Any function that "collapses" a recursive type, such as Gift -> 'r , can be written in
terms of the catamorphism for that type.
Catamorphisms can be used to hide the internal structure of the type.
Catamorphisms can be used to create mappings from one type to another by tweaking
the functions that handle each case.
Catamorphisms can be used to create a clone of the original value by passing in the
type's case constructors.
But all is not perfect in the land of catamorphisms. In fact, all the catamorphism
implementations on this page have a potentially serious flaw.
In the next post we'll see what can go wrong with them, how to fix them, and in the process
look at the various kinds of "fold".
See you then!
The source code for this post is available at this gist.
UPDATE: Fixed logic error in mostUsedVendor as pointed out by Paul Schnapp in comments.
Thanks, Paul!
1497
Catamorphism examples
1498
Introducing Folds
Introducing Folds
This post is the third in a series.
In the first post, I introduced "catamorphisms", a way of creating functions for recursive
types, and in the second post, we created a few catamorphism implementations.
But at the end of the previous post, I noted that all the catamorphism implementations so far
have had a potentially serious flaw.
In this post, we'll look at the flaw and how to work around it, and in the process look at folds,
tail-recursion and the difference between "left fold" and "right fold".
Series contents
Here's the contents of this series:
Part 1: Introduction to recursive types and catamorphisms
A simple recursive type
Parameterize all the things
Introducing catamorphisms
Benefits of catamorphisms
Rules for creating a catamorphism
Part 2: Catamorphism examples
Catamorphism example: File system domain
Catamorphism example: Product domain
Part 3: Introducing folds
A flaw in our catamorphism implementation
Introducing fold
Problems with fold
Using functions as accumulators
Introducing foldback
Rules for creating a fold
Part 4: Understanding folds
Iteration vs. recursion
Fold example: File system domain
Common questions about "fold"
Part 5: Generic recursive types
LinkedList: A generic recursive type
1499
Introducing Folds
Here are some example values that we'll be using in this post:
1500
Introducing Folds
// A Book
let wolfHall = {title="Wolf Hall"; price=20m}
// A Chocolate
let yummyChoc = {chocType=SeventyPercent; price=5m}
// A Gift
let birthdayPresent = WithACard (Wrapped (Book wolfHall, HappyBirthday), "Happy Birthd
ay")
// A Gift
let christmasPresent = Wrapped (Boxed (Chocolate yummyChoc), HappyHolidays)
1501
Introducing Folds
What we'll do is create a Box inside a Box inside a Box a very large number of times,
and see what happens.
Here's a little helper function to create nested boxes:
let deeplyNestedBox depth =
let rec loop depth boxSoFar =
match depth with
| 0 -> boxSoFar
| n -> loop (n-1) (Boxed boxSoFar)
loop depth (Book wolfHall)
So far so good.
But if we use much larger numbers, we soon run into a stack overflow exception:
deeplyNestedBox 10000 |> totalCostUsingCata // Stack overflow?
deeplyNestedBox 100000 |> totalCostUsingCata // Stack overflow?
The exact number which causes an error depends on the environment, available memory,
and so on. But it is a certainty that you will run into it when you start using largish numbers.
Why is this happening?
1502
Introducing Folds
The big advantange of this approach is that all calculations at a particular level are
completely finished before the next lowel level is called. Which means that the level and its
associated data can be safely discarded from the stack. Which means no stack overflow!
An implementation like this, where the higher levels can be safely discarded, is called tail
recursive.
1503
Introducing Folds
Let's rewrite the total cost function from scratch, using an accumulator called costSoFar :
let rec totalCostUsingAcc costSoFar gift =
match gift with
| Book book ->
costSoFar + book.price // final result
| Chocolate choc ->
costSoFar + choc.price // final result
| Wrapped (innerGift,style) ->
let newCostSoFar = costSoFar + 0.5m
totalCostUsingAcc newCostSoFar innerGift
| Boxed innerGift ->
let newCostSoFar = costSoFar + 1.0m
totalCostUsingAcc newCostSoFar innerGift
| WithACard (innerGift,message) ->
let newCostSoFar = costSoFar + 2.0m
totalCostUsingAcc newCostSoFar innerGift
Introducing "fold"
Now let's apply the same design principle to the catamorphism implementation.
We'll create a new function foldGift . We'll introduce an accumulator acc that we will
thread through each level, and the non-recursive cases will return the final accumulator.
1504
Introducing Folds
let rec foldGift fBook fChocolate fWrapped fBox fCard acc gift :'r =
let recurse = foldGift fBook fChocolate fWrapped fBox fCard
match gift with
| Book book ->
let finalAcc = fBook acc book
finalAcc // final result
| Chocolate choc ->
let finalAcc = fChocolate acc choc
finalAcc // final result
| Wrapped (innerGift,style) ->
let newAcc = fWrapped acc style
recurse newAcc innerGift
| Boxed innerGift ->
let newAcc = fBox acc
recurse newAcc innerGift
| WithACard (innerGift,message) ->
let newAcc = fCard acc message
recurse newAcc innerGift
If we look at the type signature, we can see that it is subtly different. The type of the
accumulator 'a is being used everywhere now. The only time where the final return type is
used is in the two non-recursive cases ( fBook and fChocolate ).
val foldGift :
fBook:('a -> Book -> 'r) ->
fChocolate:('a -> Chocolate -> 'r) ->
fWrapped:('a -> WrappingPaperStyle -> 'a) ->
fBox:('a -> 'a) ->
fCard:('a -> string -> 'a) ->
// accumulator
acc:'a ->
// input value
gift:Gift ->
// return value
'r
Let's look at this more closely, and compare the signatures of the original catamorphism
from the last post with the signatures of the new fold function.
First of all, the non-recursive cases:
// original catamorphism
fBook:(Book -> 'r)
fChocolate:(Chocolate -> 'r)
// fold
fBook:('a -> Book -> 'r)
fChocolate:('a -> Chocolate -> 'r)
1505
Introducing Folds
As you can see, with "fold", the non-recursive cases take an extra parameter (the
accumulator) and return the 'r type.
This is a very important point: the type of the accumulator does not need to be the same as
the return type. We will need to take advantage of this shortly.
What about the recursive cases? How did their signature change?
// original catamorphism
fWrapped:('r -> WrappingPaperStyle -> 'r)
fBox:('r -> 'r)
// fold
fWrapped:('a -> WrappingPaperStyle -> 'a)
fBox:('a -> 'a)
For the recursive cases, the structure is identical but all use of the 'r type has been
replaced with the 'a type. The recursive cases do not use the 'r type at all.
And again, we can process very large numbers of nested boxes without a stack overflow:
1506
Introducing Folds
1507
Introducing Folds
These outputs are wrong! The order of the decorations has been mixed up.
It's supposed to be a wrapped book with a card, not a book and a card wrapped together.
And it's supposed to be chocolate in a box, then wrapped, not wrapped chocolate in a box!
// OUTPUT: "'Wolf Hall' with a card saying 'Happy Birthday' wrapped in HappyBirthday
paper"
// CORRECT "'Wolf Hall' wrapped in HappyBirthday paper with a card saying 'Happy Birth
day'"
// OUTPUT: "SeventyPercent chocolate wrapped in HappyHolidays paper in a box"
// CORRECT "SeventyPercent chocolate in a box wrapped in HappyHolidays paper"
1508
Introducing Folds
It's more complicated to talk about than to demonstrate, so here are implementations for two
of the cases:
let fWrapped descriptionGenerator style =
let newDescriptionGenerator innerText =
let newInnerText = sprintf "%s wrapped in %A paper" innerText style
descriptionGenerator newInnerText
newDescriptionGenerator
let fBox descriptionGenerator =
let newDescriptionGenerator innerText =
let newInnerText = sprintf "%s in a box" innerText
descriptionGenerator newInnerText
newDescriptionGenerator
We could continue to make it more compact using piping and other things, but I think that
what we have here is a good balance between conciseness and obscurity.
1509
Introducing Folds
Again, I'm using overly descriptive intermediate values to make it clear what is going on.
If we try descriptionUsingFoldWithGenerator now, we get the correct answers again:
birthdayPresent |> descriptionUsingFoldWithGenerator
// CORRECT "'Wolf Hall' wrapped in HappyBirthday paper with a card saying 'Happy Birth
day'"
christmasPresent |> descriptionUsingFoldWithGenerator
// CORRECT "SeventyPercent chocolate in a box wrapped in HappyHolidays paper"
Introducing "foldback"
Now that we understand what to do, let's make a generic version that that handles the
generator function logic for us. This one we will call "foldback":
1510
Introducing Folds
By the way, I'm going to use term "generator" here. In other places, it is commonly referred
to as a "continuation" function, often abbreviated to just "k".
Here's the implementation:
let rec foldbackGift fBook fChocolate fWrapped fBox fCard generator gift :'r =
let recurse = foldbackGift fBook fChocolate fWrapped fBox fCard
match gift with
| Book book ->
generator (fBook book)
| Chocolate choc ->
generator (fChocolate choc)
| Wrapped (innerGift,style) ->
let newGenerator innerVal =
let newInnerVal = fWrapped innerVal style
generator newInnerVal
recurse newGenerator innerGift
| Boxed innerGift ->
let newGenerator innerVal =
let newInnerVal = fBox innerVal
generator newInnerVal
recurse newGenerator innerGift
| WithACard (innerGift,message) ->
let newGenerator innerVal =
let newInnerVal = fCard innerVal message
generator newInnerVal
recurse newGenerator innerGift
The foldback implementation above is written from scratch. If you want a fun exercise, see
if you can write foldback in terms of fold .
1511
Introducing Folds
1512
Introducing Folds
All the handler functions are basically identical. The only change is the addition of an initial
generator function, which is just id in this case.
However, although the code looks the same in both cases, they differ in their recursion
safety. The foldbackGift version is still tail recursive, and can handle very large nesting
depths, unlike the cataGift version.
But this implementation is not perfect either. The chain of nested functions can get very slow
and generate a lot of garbage, and for this particular example, there is an even faster way,
which we'll look at in the next post.
Introducing Folds
It is therefore common to change the signature of the foldBack version so that the
accumulator always comes last, while in the normal fold function, the accumulator always
comes first.
let rec foldbackGift fBook fChocolate fWrapped fBox fCard gift generator :'r =
//swapped => ~~~~~~~~~~~~~~
let recurse = foldbackGiftWithAccLast fBook fChocolate fWrapped fBox fCard
match gift with
| Book book ->
generator (fBook book)
| Chocolate choc ->
generator (fChocolate choc)
| Wrapped (innerGift,style) ->
let newGenerator innerVal =
let newInnerVal = fWrapped style innerVal
//swapped => ~~~~~~~~~~~~~~
generator newInnerVal
recurse innerGift newGenerator
//swapped => ~~~~~~~~~~~~~~~~~~~~~~
| Boxed innerGift ->
let newGenerator innerVal =
let newInnerVal = fBox innerVal
generator newInnerVal
recurse innerGift newGenerator
//swapped => ~~~~~~~~~~~~~~~~~~~~~~
| WithACard (innerGift,message) ->
let newGenerator innerVal =
let newInnerVal = fCard message innerVal
//swapped => ~~~~~~~~~~~~~~~~
generator newInnerVal
recurse innerGift newGenerator
//swapped => ~~~~~~~~~~~~~~~~~~~~~~
This change shows up in the type signature. The Gift value comes before the accumulator
now:
1514
Introducing Folds
val foldbackGift :
fBook:(Book -> 'a) ->
fChocolate:(Chocolate -> 'a) ->
fWrapped:(WrappingPaperStyle -> 'a -> 'a) ->
fBox:('a -> 'a) ->
fCard:(string -> 'a -> 'a) ->
// input value
gift:Gift ->
// accumulator
generator:('a -> 'r) ->
// return value
'r
Summary
1515
Introducing Folds
1516
Understanding Folds
Understanding Folds
This post is the fourth in a series.
In the previous post, I introduced "folds", a way of creating top-down iterative functions for
recursive types.
In this post, we'll spend some time understanding folds in more detail.
Series contents
Here's the contents of this series:
Part 1: Introduction to recursive types and catamorphisms
A simple recursive type
Parameterize all the things
Introducing catamorphisms
Benefits of catamorphisms
Rules for creating a catamorphism
Part 2: Catamorphism examples
Catamorphism example: File system domain
Catamorphism example: Product domain
Part 3: Introducing folds
A flaw in our catamorphism implementation
Introducing fold
Problems with fold
Using functions as accumulators
Introducing foldback
Rules for creating a fold
Part 4: Understanding folds
Iteration vs. recursion
Fold example: File system domain
Common questions about "fold"
Part 5: Generic recursive types
LinkedList: A generic recursive type
Making the Gift domain generic
Defining a generic Container type
A third way to implement the gift domain
Abstract or concrete? Comparing the three designs
1517
Understanding Folds
In an imperative language, this is exactly a "for loop" with a mutable variable storing the
accumulator.
1518
Understanding Folds
So, this kind of top-to-bottom folding can be thought of as iteration (and in fact, the F#
compiler will turn a tail-recursive function like this into an iteration behind the scenes).
On the other hand, in cata , the accumulator started at the bottom level, and was passed
up to each higher level until the top level was reached.
In code terms, each level did this:
accumulatorFromLowerLevel, combined with
stuffFromThisLevel
=> stuffToSendUpToNextHigherLevel
1519
Understanding Folds
Note that each directory contains a list of subitems, so this is not a linear structure like
Gift , but a tree-like structure. Out implementation of fold will have to take this into account.
We want to create a fold, foldFS , say. So, following the rules, let's add an extra
accumulator parameter acc and pass it to the File case:
let rec foldFS fFile fDir acc item :'r =
let recurse = foldFS fFile fDir
match item with
| File file ->
fFile acc file
| Directory dir ->
// to do
The Directory case is trickier. We are not supposed to know about the subitems, so that
means that the only data we can use is the name , dirSize , and the accumulator passed in
from a higher level. These are combined to make a new accumulator.
| Directory dir ->
let newAcc = fDir acc (dir.name,dir.dirSize)
// to do
1520
Understanding Folds
NOTE: I'm keeping the name and dirSize as a tuple for grouping purposes, but of course
you could pass them in as separate parameters.
Now we need to pass this new accumulator down to each subitem in turn, but each subitem
will return a new accumulator of its own, so we need to use the following approach:
Take the newly created accumulator and pass it to the first subitem.
Take the output of that (another accumulator) and pass it to the second subitem.
Take the output of that (another accumulator) and pass it to the third subitem.
And so on. The output of the last subitem is the final result.
That approach is already available to us though. It's exactly what List.fold does! So
here's the code for the Directory case:
| Directory dir ->
let newAcc = fDir acc (dir.name,dir.dirSize)
dir.subitems |> List.fold recurse newAcc
With this in place, we can rewrite the same two functions we implemented in the last post.
First, the totalSize function, which just sums up all the sizes:
let totalSize fileSystemItem =
let fFile acc (file:File) =
acc + file.fileSize
let fDir acc (name,size) =
acc + size
foldFS fFile fDir 0 fileSystemItem
1521
Understanding Folds
On the other hand, the Directory handler is trivial -- just pass the "largest so far"
accumulator down to the next level
let fDir largestSoFarOpt (name,size) =
largestSoFarOpt
1522
Understanding Folds
It is interesting to compare this implementation with the recursive version in the second post.
I think that this one is easier to implement, myself.
1523
Understanding Folds
Do we need foldback ?
Do we need to implement a foldback function for the FileSystem domain?
I don't think so. If we need access to the inner data, we can just use the original "naive"
catamorphism implementation in the previous post.
But, hey wait, didn't I say at the beginning that we had to watch out for stack overflows?
Yes, if the recursive type is deeply nested. But consider a file system with only two
subdirectories per directory. How many directories would there be if there were 64 nested
levels? (Hint: you may be familiar with a similar problem. Something to do with grains on a
chessboard).
We saw earlier that the stack overflow issue only occurs with more than 1000 nested levels,
and that level of nesting generally only occurs with linear recursive types, not trees like the
FileSystem domain.
1524
Understanding Folds
If your recursive type is not going to be too deeply nested (less than 100 levels deep,
say), then the naive cata catamorphism we described in the first post is fine. It's really
easy to implement mechanically -- just replace the main recursive type with 'r .
If your recursive type is going to be deeply nested and you want to prevent stack
overflows, use the iterative fold .
If you are using an iterative fold but you need to have access to the inner data, pass a
continuation function as an accumulator.
Finally, the iterative approach is generally faster and uses less memory than the
recursive approach (but that advantage is lost if you pass around too many nested
continuations).
Another way to think about it is to look at your "combiner" function. At each step, you are
combining data from the different levels:
level1 data [combined with] level2 data [combined with] level3 data [combined with] le
vel4 data
then use the iterative approach, but if your combiner function is "right associative" like this:
(level1 combineWith (level2 combineWith (level3 combineWith level4)))
1525
Understanding Folds
1526
Understanding Folds
The short answer is, you can't! A fold is designed to visit all elements in turn. The Visitor
Pattern has the same constraint.
There are three workarounds.
The first one is to not use fold at all and create your own recursive function that terminates
on the required condition:
In this example, the loop exits when the sum is larger than 100:
let rec firstSumBiggerThan100 sumSoFar listOfInts =
match listOfInts with
| [] ->
sumSoFar // exhausted all the ints!
| head::tail ->
let newSumSoFar = head + sumSoFar
if newSumSoFar > 100 then
newSumSoFar
else
firstSumBiggerThan100 newSumSoFar tail
// test
[30;40;50;60] |> firstSumBiggerThan100 0 // 120
[1..3..100] |> firstSumBiggerThan100 0 // 117
The second approach is to use fold but to add some kind of "ignore" flag to the
accumulator that is passed around. Once this flag is set, the remaining iterations do nothing.
Here's an example of calculating the sum, but the accumulator is actually a tuple with an
ignoreFlag in addition to the sumSoFar :
1527
Understanding Folds
The third version is a variant of the second -- create a special value to signal that the
remaining data should be ignored, but wrap it in a computation expression so that it looks
more natural.
This approach is documented on Tomas Petricek's blog and the code looks like this:
let firstSumBiggerThan100 listOfInts =
let mutable sumSoFar = 0
imperative {
for x in listOfInts do
sumSoFar <- x + sumSoFar
if sumSoFar > 100 then do! break
}
sumSoFar
Summary
The goal of this post was to help you understand folds better, and to show how they could be
applied to a tree structure like the file system. I hope it was helpful!
Up to this point in the series all the examples have been very concrete; we have
implemented custom folds for each domain we have encountered. Can we be a bit more
generic and build some reusable fold implementations?
1528
Understanding Folds
In the next post we'll look at generic recursive types, and how to work with them.
The source code for this post is available at this gist.
1529
Series contents
Here's the contents of this series:
Part 1: Introduction to recursive types and catamorphisms
A simple recursive type
Parameterize all the things
Introducing catamorphisms
Benefits of catamorphisms
Rules for creating a catamorphism
Part 2: Catamorphism examples
Catamorphism example: File system domain
Catamorphism example: Product domain
Part 3: Introducing folds
A flaw in our catamorphism implementation
Introducing fold
Problems with fold
Using functions as accumulators
Introducing foldback
Rules for creating a fold
Part 4: Understanding folds
Iteration vs. recursion
Fold example: File system domain
Common questions about "fold"
Part 5: Generic recursive types
LinkedList: A generic recursive type
Making the Gift domain generic
Defining a generic Container type
A third way to implement the gift domain
Abstract or concrete? Comparing the three designs
Part 6: Trees in the real world
1530
The Empty case represents an empty list. The Cons case has a tuple: the head element,
and the tail, which is another list.
We can then define a particular LinkedList value like this:
let linkedList = Cons (1, Cons (2, Cons(3, Empty)))
Using the native F# list type, the equivalent definition would be:
let linkedList = 1 :: 2 :: 3 :: []
1531
Note: We will be putting all the functions associated with LinkedList<'a> in a module called
LinkedList . One nice thing about using generic types is that the type name does not clash
1532
module LinkedList =
let rec cata ...
let rec foldWithEmpty fCons fEmpty acc list :'r=
let recurse = foldWithEmpty fCons fEmpty
match list with
| Empty ->
fEmpty acc
| Cons (element,list) ->
let newAcc = fCons acc element
recurse newAcc list
This foldWithEmpty function is not quite the same as the standard List.fold function,
because it has an extra function parameter for the empty case ( fEmpty ). However, if we
eliminate that parameter and just return the accumulator we get this variant:
module LinkedList =
let rec fold fCons acc list :'r=
let recurse = fold fCons
match list with
| Empty ->
acc
| Cons (element,list) ->
let newAcc = fCons acc element
recurse newAcc list
If we compare the signature with the List.fold documentation we can see that they are
equivalent, with 'State replaced by 'r and 'T list replaced by LinkedList<'a> :
LinkedList.fold : ('r -> 'a -> 'r ) -> 'r -> LinkedList<'a> -> 'r
List.fold : ('State -> 'T -> 'State) -> 'State -> 'T list -> 'State
1533
module LinkedList =
let rec cata ...
let rec fold ...
let foldBack fCons list acc :'r=
let fEmpty' generator =
generator acc
let fCons' generator element=
fun innerResult ->
let newResult = fCons element innerResult
generator newResult
let initialGenerator = id
foldWithEmpty fCons' fEmpty' initialGenerator list
Again, if we compare the signature with the List.foldBack documentation, they are also
equivalent, with 'State replaced by 'r and 'T list replaced by LinkedList<'a> :
LinkedList.foldBack : ('a -> 'r -> 'r ) -> LinkedList<'a> -> 'r -> 'r
List.foldBack : ('T -> 'State -> 'State) -> 'T list -> 'State -> 'State
module LinkedList =
let toList linkedList =
let fCons head tail = head::tail
let initialState = []
foldBack fCons linkedList initialState
To convert the other way, we need to replace :: with Cons and [] with Empty :
1534
module LinkedList =
let ofList list =
let fCons head tail = Cons(head,tail)
let initialState = Empty
List.foldBack fCons list initialState
and ofList :
let list = [1;2;3]
list |> LinkedList.ofList
// Result => Cons (1,Cons (2,Cons (3,Empty)))
1535
1536
It involves making an extra copy of the list, but on the other hand there is no longer a large
set of pending continuations. It might be worth comparing the profile of the two versions in
your environment if performance is an issue.
1537
To make this ready for reuse, then, let's collapse all the non-recursive cases into one case,
say GiftContents , and all the recursive cases into another case, say GiftDecoration , like
this:
// unified data for non-recursive cases
type GiftContents =
| Book of Book
| Chocolate of Chocolate
// unified data for recursive cases
type GiftDecoration =
| Wrapped of WrappingPaperStyle
| Boxed
| WithACard of string
type Gift =
// non-recursive case
| Contents of GiftContents
// recursive case
| Decoration of Gift * GiftDecoration
The main Gift type has only two cases now: the non-recursive one and the recursive one.
And as before, we can mechanically create a cata and fold and foldBack for it, using
the standard process:
module Container =
let rec cata fContents fDecoration (container:Container<'ContentData,'DecorationDa
ta>) :'r =
let recurse = cata fContents fDecoration
match container with
| Contents contentData ->
fContents contentData
| Decoration (decorationData,subContainer) ->
fDecoration decorationData (recurse subContainer)
1538
(*
val cata :
// function parameters
fContents:('ContentData -> 'r) ->
fDecoration:('DecorationData -> 'r -> 'r) ->
// input
container:Container<'ContentData,'DecorationData> ->
// return value
'r
*)
let rec fold fContents fDecoration acc (container:Container<'ContentData,'Decorati
onData>) :'r =
let recurse = fold fContents fDecoration
match container with
| Contents contentData ->
fContents acc contentData
| Decoration (decorationData,subContainer) ->
let newAcc = fDecoration acc decorationData
recurse newAcc subContainer
(*
val fold :
// function parameters
fContents:('a -> 'ContentData -> 'r) ->
fDecoration:('a -> 'DecorationData -> 'a) ->
// accumulator
acc:'a ->
// input
container:Container<'ContentData,'DecorationData> ->
// return value
'r
*)
let foldBack fContents fDecoration (container:Container<'ContentData,'DecorationDa
ta>) :'r =
let fContents' generator contentData =
generator (fContents contentData)
let fDecoration' generator decorationData =
let newGenerator innerValue =
let newInnerValue = fDecoration decorationData innerValue
generator newInnerValue
newGenerator
fold fContents' fDecoration' id container
(*
val foldBack :
// function parameters
fContents:('ContentData -> 'r) ->
fDecoration:('DecorationData -> 'r -> 'r) ->
// input
container:Container<'ContentData,'DecorationData> ->
// return value
1539
'r
*)
Now we need some helper methods to construct values while hiding the "real" cases of the
generic type:
let fromBook book =
Contents (Book book)
let fromChoc choc =
Contents (Chocolate choc)
let wrapInPaper paperStyle innerGift =
let container = Wrapped paperStyle
Decoration (container, innerGift)
let putInBox innerGift =
let container = Boxed
Decoration (container, innerGift)
let withCard message innerGift =
let container = WithACard message
Decoration (container, innerGift)
1540
1541
With these helper functions, the way the values are constructed is identical to the previous
version. This is why it is good to hide your raw constructors, folks!
let wolfHall = {title="Wolf Hall"; price=20m}
let yummyChoc = {chocType=SeventyPercent; price=5m}
let birthdayPresent =
wolfHall
|> fromBook
|> wrapInPaper HappyBirthday
|> withCard "Happy Birthday"
let christmasPresent =
yummyChoc
|> fromChoc
|> putInBox
|> wrapInPaper HappyHolidays
1543
1544
1545
If this is not obvious, it might be helpful to read my post on data type sizes. It explains how
two types can be "equivalent", even though they appear to be completely different at first
glance.
Picking a design
So which design is best? The answer is, as always, "it depends".
For modelling and documenting a domain, I like the first design with the five explicit cases.
Being easy for other people to understand is more important to me than introducing
abstraction for the sake of reusability.
If I wanted a reusable model that was applicable in many situations, I'd probably choose the
second ("Container") design. It seems to me that this type does represent a commonly
encountered situation, where the contents are one kind of thing and the wrappers are
another kind of thing. This abstraction is therefore likely to get some use.
The final "pair" model is fine, but by separating the two components, we've over-abstracted
the design for this scenario. In other situations, this design might be a great fit (e.g. the
decorator pattern), but not here, in my opinion.
There is one further choice which gives you the best of all worlds.
As I noted above, all the designs are logically equivalent, which means there are "lossless"
mappings between them. In that case, your "public" design can be the domain-oriented one,
like the first one, but behind the scenes you can map it to a more efficient and reusable
"private" type.
Even the F# list implementation itself does this. For example, some of the functions in the
List module, such foldBack and sort , convert the list into an array, do the operations,
Summary
In this post we looked at some ways of modelling the Gift as a generic type, and the pros
and cons of each approach.
In the next post we'll look at real-world examples of using a generic recursive type.
The source code for this post is available at this gist.
1546
1547
Series contents
Here's the contents of this series:
Part 1: Introduction to recursive types and catamorphisms
A simple recursive type
Parameterize all the things
Introducing catamorphisms
Benefits of catamorphisms
Rules for creating a catamorphism
Part 2: Catamorphism examples
Catamorphism example: File system domain
Catamorphism example: Product domain
Part 3: Introducing folds
A flaw in our catamorphism implementation
Introducing fold
Problems with fold
Using functions as accumulators
Introducing foldback
Rules for creating a fold
Part 4: Understanding folds
Iteration vs. recursion
Fold example: File system domain
Common questions about "fold"
Part 5: Generic recursive types
LinkedList: A generic recursive type
Making the Gift domain generic
Defining a generic Container type
A third way to implement the gift domain
Abstract or concrete? Comparing the three designs
1548
We can separate out the data from the recursion, and create a generic Tree type like this:
type Tree<'LeafData,'INodeData> =
| LeafNode of 'LeafData
| InternalNode of 'INodeData * Tree<'LeafData,'INodeData> seq
Notice that I have used seq to represent the subitems rather than list . The reason for
this will become apparent shortly.
The file system domain can then be modelled using Tree by specifying FileInfo as data
associated with a leaf node and DirectoryInfo as data associated with an internal node:
type FileInfo = {name:string; fileSize:int}
type DirectoryInfo = {name:string; dirSize:int}
type FileSystemItem = Tree<FileInfo,DirectoryInfo>
1549
Note that I am not going to implement foldBack for the Tree type, because it's unlikely
that the tree will get so deep as to cause a stack overflow. Functions that need inner data
can use cata .
1550
The totalSize function is almost identical to the one in the previous post:
let totalSize fileSystemItem =
let fFile acc (file:FileInfo) =
acc + file.fileSize
let fDir acc (dir:DirectoryInfo)=
acc + dir.dirSize
Tree.fold fFile fDir 0 fileSystemItem
readme |> totalSize // 1
src |> totalSize // 16 = 10 + (1 + 2 + 3)
root |> totalSize // 31 = 5 + 16 + 10
We can use the Tree to model the real file system too! To do this, just set the leaf node
type to System.IO.FileInfo and the internal node type to System.IO.DirectoryInfo .
open System
open System.IO
type FileSystemTree = Tree<IO.FileInfo,IO.DirectoryInfo>
And let's create some helper methods to create the various nodes:
let fromFile (fileInfo:FileInfo) =
LeafNode fileInfo
let rec fromDir (dirInfo:DirectoryInfo) =
let subItems = seq{
yield! dirInfo.EnumerateFiles() |> Seq.map fromFile
yield! dirInfo.EnumerateDirectories() |> Seq.map fromDir
}
InternalNode (dirInfo,subItems)
Now you can see why I used seq rather than list for the subitems. The seq is lazy,
which means that we can create nodes without actually hitting the disk.
Here's the totalSize function again, this time using the real file information:
let totalSize fileSystemItem =
let fFile acc (file:FileInfo) =
acc + file.Length
let fDir acc (dir:DirectoryInfo)=
acc
Tree.fold fFile fDir 0L fileSystemItem
1552
So that's one big benefit of using generic recursive types. If we can turn a real-world
hierarchy into our tree structure, we can get all the benefits of fold "for free".
1553
module Tree =
let rec cata ...
let rec fold ...
let rec map fLeaf fNode (tree:Tree<'LeafData,'INodeData>) =
let recurse = map fLeaf fNode
match tree with
| LeafNode leafInfo ->
let newLeafInfo = fLeaf leafInfo
LeafNode newLeafInfo
| InternalNode (nodeInfo,subtrees) ->
let newNodeInfo = fNode nodeInfo
let newSubtrees = subtrees |> Seq.map recurse
InternalNode (newNodeInfo, newSubtrees)
If we look at the signature of Tree.map , we can see that all the leaf data is transformed to
type 'a , all the node data is transformed to type 'b , and the final result is a Tree<'a,'b> .
val map :
fLeaf:('LeafData -> 'a) ->
fNode:('INodeData -> 'b) ->
tree:Tree<'LeafData,'INodeData> ->
Tree<'a,'b>
1554
Let's say we want to use map to transform the file system into a directory listing - a tree of
strings where each string has information about the corresponding file or directory. Here's
how we could do it:
let dirListing fileSystemItem =
let printDate (d:DateTime) = d.ToString()
let mapFile (fi:FileInfo) =
sprintf "%10i %s %-s" fi.Length (printDate fi.LastWriteTime) fi.Name
let mapDir (di:DirectoryInfo) =
di.FullName
Tree.map mapFile mapDir fileSystemItem
1555
Before we start writing the main code, we'll need some helper functions.
First, a generic function that folds over the lines in a file asynchronously. This will be the
basis of the pattern matching.
/// Fold over the lines in a file asynchronously
/// passing in the current line and line number tothe folder function.
///
/// Signature:
/// folder:('a -> int -> string -> 'a) ->
/// acc:'a ->
/// fi:FileInfo ->
/// Async<'a>
let foldLinesAsync folder acc (fi:FileInfo) =
async {
let mutable acc = acc
let mutable lineNo = 1
use sr = new StreamReader(path=fi.FullName)
while not sr.EndOfStream do
let! lineText = sr.ReadLineAsync() |> Async.AwaitTask
acc <- folder acc lineNo lineText
lineNo <- lineNo + 1
return acc
}
Now for the central logic. We will create a function that, given a textPattern and a
FileInfo , will return a list of lines that match the textPattern, but asynchronously:
1556
1557
currentDir
|> grep "fsx" "LinkedList"
|> Async.RunSynchronously
That's not bad for about 40 lines of code. This conciseness is because we are using various
kinds of fold and map which hide the recursion, allowing us to focus on the pattern
matching logic itself.
Of course, this is not at all efficient or optimized (an async for every line!), and so I wouldn't
use it as a real implementation, but it does give you an idea of the power of fold.
The source code for this example is available at this gist.
1558
That's simple enough. But note that in order to save a directory completely along with its
relationships to its child items, we first need the ids of all its children, and each child
directory needs the ids of its children, and so on.
This implies that we should use cata instead of fold , so that we have access to the data
from the lower levels of the hierarchy.
In a real database, the identity column would be automatically generated for you, but for this
example, I'll use a little helper function nextIdentity :
1559
let nextIdentity =
let id = ref 0
fun () ->
id := !id + 1
!id
// test
nextIdentity() // 1
nextIdentity() // 2
nextIdentity() // 3
Now in order to insert a directory, we need to first know all the ids of the files in the directory.
This implies that the insertDbFile function should return the id that was generated.
/// Insert a DbFile record and return the new file id
let insertDbFile name (fileSize:int64) =
let id = nextIdentity()
printfn "%10s: inserting id:%i name:%s size:%i" "DbFile" id name fileSize
id
But that's still not good enough. When the child ids are passed to the parent directory, it
needs to distinguish between files and directories, because the relations are stored in
different tables.
No problem -- we'll just use a choice type to distinguish between them!
type PrimaryKey =
| FileId of int
| DirId of int
With this in place, we can complete the implementation of the database functions:
1560
The function to handle the Directory case will be passed the DirectoryInfo and a
sequence of PrimaryKey s from the children that have already been inserted.
It should insert the main directory record, then insert the children, and then return the
PrimaryKey for the next higher level:
After inserting the directory record and getting its id, for each child id, we insert either into
the DbDir_File table or the DbDir_Dir , depending on the type of the childId .
1561
Note that I've also created a little helper function pkToInt that extracts the integer id from
the PrimaryKey type.
Here is all the code in one chunk:
open System
open System.IO
let nextIdentity =
let id = ref 0
fun () ->
id := !id + 1
!id
type PrimaryKey =
| FileId of int
| DirId of int
/// Insert a DbFile record and return the new PrimaryKey
let insertDbFile name (fileSize:int64) =
let id = nextIdentity()
printfn "%10s: inserting id:%i name:%s size:%i" "DbFile" id name fileSize
FileId id
/// Insert a DbDir record and return the new PrimaryKey
let insertDbDir name =
let id = nextIdentity()
printfn "%10s: inserting id:%i name:%s" "DbDir" id name
DirId id
/// Insert a DbDir_File record
let insertDbDir_File dirId fileId =
printfn "%10s: inserting parentDir:%i childFile:%i" "DbDir_File" dirId fileId
/// Insert a DbDir_Dir record
let insertDbDir_Dir parentDirId childDirId =
printfn "%10s: inserting parentDir:%i childDir:%i" "DbDir_Dir" parentDirId childDi
rId
1562
1563
You can see that the ids are being generated as the files are iterated over, and that each
DbFile insert is followed by a DbDir_File insert.
As usual, we can create some helper functions to assist with constructing a Gift :
1564
1565
Functions like description now need to handle a list of inner texts, rather than one. We'll
just concat the strings together with an & separator:
let description gift =
let fLeaf leafData =
match leafData with
| Book book ->
sprintf "'%s'" book.title
| Chocolate choc ->
sprintf "%A chocolate" choc.chocType
let fNode nodeData innerTexts =
let innerText = String.concat " & " innerTexts
match nodeData with
| Wrapped style ->
sprintf "%s wrapped in %A paper" innerText style
| Boxed ->
sprintf "%s in a box" innerText
| WithACard message ->
sprintf "%s with a card saying '%s'" innerText message
// main call
Tree.cata fLeaf fNode gift
Finally, we can check that the function still works as before, and that multiple items are
handled correctly:
birthdayPresent |> description
// "'Wolf Hall' wrapped in HappyBirthday paper with a card saying 'Happy Birthday'"
christmasPresent |> description
// "SeventyPercent chocolate in a box wrapped in HappyHolidays paper"
twoBirthdayPresents |> description
// "'Wolf Hall' & SeventyPercent chocolate in a box
// wrapped in HappyBirthday paper"
twoWrappedPresentsInBox |> description
// "'Wolf Hall' wrapped in HappyHolidays paper
// & SeventyPercent chocolate wrapped in HappyBirthday paper
// in a box"
1566
So what I like to do is define DTO types that are explicitly designed to be serialized well. In
practice this means that the DTO types are constrained as follows:
Only record types should be used.
The record fields should consist only primitive values such as int , string and bool .
By doing this, we also get some other advantages:
We gain control of the serialization output. These kinds of data types are handled the
same by most serializers, while "strange" things such as unions can be interpreted
differently by different libraries.
We have better control of error handling. My number one rule when dealing with
serialized data is "trust no one". It's very common that the data is structured correctly but is
invalid for the domain: supposedly non-null strings are null, strings are too long, integers are
outside the correct bounds, and so on.
By using DTOs, we can be sure that the deserialization step itself will work. Then, when we
convert the DTO to a domain type, we can do proper validation.
So, let's define some DTO types for out domain. Each DTO type will correspond to a domain
type, so let's start with GiftContents . We'll define a corresponding DTO type called
GiftContentsDto as follows:
[<CLIMutableAttribute>]
type GiftContentsDto = {
discriminator : string // "Book" or "Chocolate"
// for "Book" case only
bookTitle: string
// for "Chocolate" case only
chocolateType : string // one of "Dark" "Milk" "SeventyPercent"
// for all cases
price: decimal
}
Obviously, this quite different from the original GiftContents , so let's look at the differences:
First, it has the CLIMutableAttribute , which allows deserializers to construct them using
reflection.
Second, it has a discriminator which indicates which case of the original union type is
being used. Obviously, this string could be set to anything, so when converting from the
DTO back to the domain type, we'll have to check that carefully!
Next is a series of fields, one for every possible item of data that needs to be stored. For
example, in the Book case, we need a bookTitle , while in the Chocolate case, we
need the chocolate type. And finally the price field which is in both types. Note that
the chocolate type is stored as a string as well, and so will also need special treatment
1567
Finally, we can define a GiftDto type as being a tree that is composed of the two DTO
types:
type GiftDto = Tree<GiftContentsDto,GiftDecorationDto>
1568
You can see that the case ( Book , Chocolate , etc.) is turned into a discriminator string
and the chocolateType is also turned into a string, just as explained above.
1569
As before, the type has the CLIMutableAttribute . And as before, the type has fields to store
the data from all possible choices. The subtrees are stored as an array rather than a seq -this makes the serializer happy!
To create a TreeDto , we use our old friend cata to assemble the record from a regular
Tree .
1570
For this example, I am using the built-in DataContractJsonSerializer so that I don't need to
take a dependency on a NuGet package. There are other JSON serializers that might be
better for a serious project.
#r "System.Runtime.Serialization.dll"
open System.Runtime.Serialization
open System.Runtime.Serialization.Json
let toJson (o:'a) =
let serializer = new DataContractJsonSerializer(typeof<'a>)
let encoding = System.Text.UTF8Encoding()
use stream = new System.IO.MemoryStream()
serializer.WriteObject(stream,o)
stream.Close()
encoding.GetString(stream.ToArray())
1571
{
"leafData@": null,
"nodeData@": {
"discriminator@": "Wrapped",
"message@": null,
"wrappingPaperStyle@": "HappyHolidays"
},
"subtrees@": [
{
"leafData@": null,
"nodeData@": {
"discriminator@": "Boxed",
"message@": null,
"wrappingPaperStyle@": null
},
"subtrees@": [
{
"leafData@": {
"bookTitle@": null,
"chocolateType@": "SeventyPercent",
"discriminator@": "Chocolate",
"price@": 5
},
"nodeData@": null,
"subtrees@": []
}
]
}
]
}
The ugly @ signs on the field names are an artifact of serializing the F# record type. This
can be corrected with a bit of effort, but I'm not going to bother right now!
The source code for this example is available at this gist
1572
What if the deserialization fails? For now, we will ignore any error handling and let the
exception propagate.
1573
Here's the complete code -- it's a lot more complicated than going in the other direction!
The code can be grouped as follows:
Helper methods (such as strToChocolateType ) that convert a string into a proper
domain type and throw an exception if the input is invalid.
Case converter methods (such as bookFromDto ) that convert an entire DTO into a case.
And finally, the dtoToGift function itself. It looks at the discriminator field to see
which case converter to call, and throws an exception if the discriminator value is not
recognized.
let strToBookTitle str =
match str with
| null -> failwith "BookTitle must not be null"
| _ -> str
let strToChocolateType str =
match str with
| "Dark" -> Dark
| "Milk" -> Milk
| "SeventyPercent" -> SeventyPercent
| _ -> failwithf "ChocolateType %s not recognized" str
let strToWrappingPaperStyle str =
match str with
| "HappyBirthday" -> HappyBirthday
| "HappyHolidays" -> HappyHolidays
| "SolidColor" -> SolidColor
| _ -> failwithf "WrappingPaperStyle %s not recognized" str
let strToCardMessage str =
match str with
1574
1575
We get lots of exceptions, and as as functional programmers, we should try to remove them
whenever we can.
How we can do that will be discussed in the next section.
The source code for this example is available at this gist.
1576
I'm not going to explain how it works here. If you are not familar with this approach, please
read my post or watch my talk on the topic of functional error handling.
Let's revisit all the steps from the previous section, and use Result rather than throwing
exceptions.
1577
But uh-oh, we now have a Tree where every internal node and leaf is wrapped in a
Result . It's a tree of Results ! The actual ugly signature is this:
Tree<Result<'Leaf>,Result<'Node>> .
But this type is useless as it stands -- what we really want is to merge all the errors together
and return a Result containing a Tree .
How can we transform a Tree of Results into a Result of Tree?
The answer is to use a sequence function which "swaps" the two types. You can read much
more about sequence in my series on elevated worlds.
Note that we could also use the slightly more complicated traverse variant to combine the
map and sequence into one step, but for the purposes of this demonstration, it's easier to
1578
Here is the actual code -- don't worry if you can't understand it immediately. Luckily, we only
need to write it once for each combination of types, so for any kind of Tree/Result
combination in the future, we're set!
/// Convert a tree of Results into a Result of tree
let sequenceTreeOfResult tree =
// from the lower level
let (<*>) = Result.apply
let retn = Result.retn
// from the traversable level
let fLeaf data =
retn LeafNode <*> data
let fNode data subitems =
let makeNode data items = InternalNode(data,items)
let subItems = Result.sequenceSeq subitems
retn makeNode <*> data <*> subItems
// do the traverse
Tree.cata fLeaf fNode tree
// val sequenceTreeOfResult :
// tree:Tree<Result<'a>,Result<'b>> -> Result<Tree<'a,'b>>
Finally, the actual dtoToTree function is simple -- just send the treeDto through
dtoToTreeOfResults and then use sequenceTreeOfResult to convert the final result into a
Result<Tree<..>> , which is just what we need.
in a Tree of Results again, and so we'll have to use sequenceTreeOfResult again to get it
back into the correct Result<Tree<..>> shape.
1579
Let's start with the helper methods (such as strToChocolateType ) that convert a string into a
proper domain type. This time, they return a Result rather than throwing an exception.
let strToBookTitle str =
match str with
| null -> Result.failWithMsg "BookTitle must not be null"
| _ -> Result.retn str
let strToChocolateType str =
match str with
| "Dark" -> Result.retn Dark
| "Milk" -> Result.retn Milk
| "SeventyPercent" -> Result.retn SeventyPercent
| _ -> Result.failWithMsg (sprintf "ChocolateType %s not recognized" str)
let strToWrappingPaperStyle str =
match str with
| "HappyBirthday" -> Result.retn HappyBirthday
| "HappyHolidays" -> Result.retn HappyHolidays
| "SolidColor" -> Result.retn SolidColor
| _ -> Result.failWithMsg (sprintf "WrappingPaperStyle %s not recognized" str)
let strToCardMessage str =
match str with
| null -> Result.failWithMsg "CardMessage must not be null"
| _ -> Result.retn str
The case converter methods have to build a Book or Chocolate from parameters that are
Result s rather than normal values. This is where lifting functions like Result.lift2 can
help. For details on how this works, see this post on lifting and this one on validation with
applicatives.
1580
And finally, the dtoToGift function itself is changed to return a Result if the
discriminator is invalid.
As before, this mapping creates a Tree of Results, so we pipe the output of the Tree.map
through sequenceTreeOfResult ...
`Tree.map fLeaf fNode giftDto |> sequenceTreeOfResult`
1581
open TreeDto_WithErrorHandling
/// Transform a GiftDto to a Result<Gift>
let dtoToGift (giftDto:GiftDto) :Result<Gift>=
let fLeaf (leafDto:GiftContentsDto) =
match leafDto.discriminator with
| "Book" -> bookFromDto leafDto
| "Chocolate" -> chocolateFromDto leafDto
| _ -> Result.failWithMsg (sprintf "Unknown leaf discriminator '%s'" leafDto.d
iscriminator)
let fNode (nodeDto:GiftDecorationDto) =
match nodeDto.discriminator with
| "Wrapped" -> wrappedFromDto nodeDto
| "Boxed" -> boxedFromDto nodeDto
| "WithACard" -> withACardFromDto nodeDto
| _ -> Result.failWithMsg (sprintf "Unknown node discriminator '%s'" nodeDto.d
iscriminator)
// map the tree
Tree.map fLeaf fNode giftDto |> sequenceTreeOfResult
The type signature of dtoToGift has changed -- it now returns a Result<Gift> rather than
just a Gift .
// val dtoToGift : GiftDto -> Result<GiftUsingTree.Gift>
1582
let goodGift = goodJson |> fromJson |> Result.bind dtoToTree |> Result.bind dtoToGift
// check that the description is unchanged
goodGift |> description
// Success "SeventyPercent chocolate in a box wrapped in HappyHolidays paper"
That's fine.
Let's see if the error handling has improved now. We'll corrupt the JSON again:
let badJson1 = goodJson.Replace("leafData","leafDataXX")
let badJson1_result = badJson1 |> fromJson |> Result.bind dtoToTree |> Result.bind dto
ToGift
// Failure ["The data contract type 'TreeDto' cannot be deserialized because the requi
red data member 'leafData@' was not found."]
1583
Summary
We've seen in this series how to define catamorphisms, folds, and in this post in particular,
how to use them to solve real world problems. I hope these posts have been useful, and
have provided you with some tips and insights that you can apply to your own code.
This series turned out to be a lot longer that I intended, so thanks for making it to the end!
Cheers!
1584
In this series of posts, I'll look at how you might handle the common security challenge of
authorization. That is, how can you ensure that clients of your code can only do what you
want them to do?
This series will sketch out two different approaches, first using an approach called capability
based security, and second using statically checked types to emulate access tokens.
Interestingly, both approaches tend to produce a cleaner, more modular design as a side
effect, which is why I like them!
A functional approach to authorization. Capability based security and more.
Constraining capabilities based on identity and role. A functional approach to
authorization, part 2.
Using types as access tokens. A functional approach to authorization, part 3.
1585
1586
interface IConfiguration
{
string GetConfigFilename();
}
Obviously, this is not good! In order for this to work, we have to give the caller the ability to
write to any file on the filesystem, and then a malicious caller could delete or corrupt all sorts
of things.
You could avoid this to some extent by having strict permissions on the file system, but we're
still giving way too much control to the caller.
Attempt 2: Give the caller a TextWriter
Ok, so let's open the file ourselves and just give the caller the opened file stream as a
TextWriter . That way the caller doesn't need permission to access the file system at all.
But of course, a malicious caller could still corrupt the config file by writing garbage to the
file. Again, we're giving way too much control to the caller.
Attempt 3: Give the caller a key/value interface
Let's lock this down by providing the caller an interface that forces them to treat the config
file as a key/value store, like this:
interface IConfiguration
{
void SetConfig(string key, string value);
}
That's much better, but because it is a stringly-typed interface, a malicious caller could still
corrupt the configuration by setting the value to a non-boolean which would not parse. They
could also corrupt all the other configuration keys if they wanted to.
1587
Now the caller can't possibly corrupt the config, because each option is statically typed.
But we still have a problem! What's to stop a malicious caller changing the connection string
when they were only supposed to change the message flag?
Attempt 5: Give the caller only the interface they need
Ok, so let's define a new interface that contains only the methods the caller should have
access to, with all the other methods hidden.
interface IWarningMessageConfiguration
{
void SetMessageFlag(MessageFlag value);
}
That's about as locked down as we can get! The caller can only do the thing we allow them
to do.
In other words, we have just created a design using the Principle Of Least Authority,
normally abbreviated to "POLA".
1588
If we give the caller a filename, we would be limiting ourselves to file-based config files.
By giving the caller a TextWriter, we can make the design more mockable.
But if we give the caller a TextWriter, we are exposing a specific storage format (XML,
JSON, etc) and are also limiting ourselves to text-based storage. By giving the caller a
generic KeyValue store, we hide the format and make the implementation choices more
flexible.
But if we give the caller a generic KeyValue store using strings, we are still exposing
ourselves to bugs where the value is not a boolean, and we'd have to write validation
and tests for that. If we use a statically typed interface instead, we don't have to write
any corruption checking code.
But if we give the caller an interface with too many methods, we are not following the
Interface Segregation Principle. Hence, we should reduce the number of available
methods to the absolute minimum needed by the caller.
Working through a thought process like this, using good design practices only, we end up
with exactly the same result as if we had been worried about security!
That is: designing the most minimal interface that the caller needs will both avoid accidental
complexity (good design) and increase security (POLA).
Of course, we don't normally have to deal with malicious callers, but we should treat
ourselves, as developers, as unintentionally malicious. For example, if there is a extra
method in the interface, it might well be used in a different context, which then increases
coupling between the two contexts and makes refactoring harder.
So, here's a tip: design for malicious callers and you will probably end up with more
modular code!
1589
capability (interface) in the first place. (I'll talk more about authorization in the next post).
Finally, the capabilities can be passed around. For example, I can acquire the capability
at startup and then later pass it to the UI layer which can use it as needed.
In other words, we have a "just-in-time" rather than a "just-in-case" model; we pass in the
minimal amount of authority as and when needed, rather than having excess "ambient"
authority available globally to everyone.
The capability-based model is often focused on operating systems, but it can be mapped to
programming languages very nicely, where it is called the object-capability model.
I hope to demonstrate in this post that by using a capability-based approach in your code,
you can create better designed and more robust code. In addition, potential security errors
will be detectable at compile-time rather than at run-time.
As I mentioned above, if your app is trusted, you can always use .NET reflection to "forge"
capabilities that you are not entitled to. So, again, the approach shown here is not about
preventing truly malicious attacks so much as it about creating a more robust design that
reduces unintentional security vulnerabilities.
1590
Of course, when I act as a proxy in a permission based system, I can stop cooperating with
the third-party if I want to, at which point the third-party loses their access.
The equivalent of that in an authority based system is "revokable authority", which we will
see an example of later. In the car key analogy, this might be like having car keys that selfdestruct on demand!
or in F#:
let messageFlagCapability = // get function;
1591
Object-capability model
Functional programming
Data is immutable.
Getting capabilities
A natural question at this point is: where do these capability functions come from?
The answer is, some sort of service that can authorize you to have that capability. In the
configuration example, we generally don't do serious authorization, so the configuration
service itself will normally provide the capabilities without checking your identity, role or other
claims.
1592
But now I need a capability to access the configuration service. Where does that come
from? The buck has to stop somewhere!
In OO designs, there is typically a bootstrap/startup stage where all the dependencies are
constructed and an IoC container is configured. In a capability based system, a so-called
Powerbox plays a similar role of being the starting point for all authority.
Here's the code for a service that provides configuration capabilities:
interface IConfigurationCapabilities
{
Action<MessageFlag> SetMessageFlag();
Action<ConnectionString> SetConnectionString();
Action<Color> SetBackgroundColor();
}
This code might look very similar to the interface defined earlier, but the difference is that
this one will be initialized at startup to return capabilities that are then passed around.
The actual users of the capabilities will not have access to the configuration system at all,
just the capabilities they have been given. That is, the capability will be injected into the
clients in the same way as a one method interface would be injected in an OO model.
Here's some sample C# pseudocode to demonstrate:
The capability is obtained at startup.
The capability is injected into the main window ( ApplicationWindow ) via the constructor.
The ApplicationWindow creates a checkbox.
The event handler for the checkbox calls the capability.
1593
// at startup
var messageFlagCapability =
configurationCapabilities.SetMessageFlag()
var appWindow = new ApplicationWindow(messageFlagCapability)
// and in the UI class
class ApplicationWindow
{
// pass in capability in the constructor
// just as you would an interface
ApplicationWindow(Action<MessageFlag> messageFlagCapability)
{
// set fields
}
// setup the check box and register the "OnCheckboxChecked" handler
// use the capability when the event happens
void OnCheckboxChecked(CheckBox sender)
{
messageFlagCapability(sender.IsChecked)
}
}
A complete example in F#
Here's the code to a complete example in F# (also available as a gist here).
This example consists of a simple window with a main region and some extra buttons.
If you click in the main area, an annoying dialog pops up with a "don't show this
message again" option.
One of the buttons allows you to change the background color using the system color
picker, and store it in the config.
The other button allows you to reset the "don't show this message again" option back to
false.
It's very crude and very ugly -- no UI designers were hurt in the making of it -- but it should
demonstrate the main points so far.
1594
1595
module Config =
type MessageFlag = ShowThisMessageAgain | DontShowThisMessageAgain
type ConnectionString = ConnectionString of string
type Color = System.Drawing.Color
type ConfigurationCapabilities = {
GetMessageFlag : unit -> MessageFlag
SetMessageFlag : MessageFlag -> unit
GetBackgroundColor : unit -> Color
SetBackgroundColor : Color -> unit
GetConnectionString : unit -> ConnectionString
SetConnectionString : ConnectionString -> unit
}
// a private store for demo purposes
module private ConfigStore =
let mutable MessageFlag = ShowThisMessageAgain
let mutable BackgroundColor = Color.White
let mutable ConnectionString = ConnectionString ""
// public capabilities
let configurationCapabilities = {
GetMessageFlag = fun () -> ConfigStore.MessageFlag
SetMessageFlag = fun flag -> ConfigStore.MessageFlag <- flag
GetBackgroundColor = fun () -> ConfigStore.BackgroundColor
SetBackgroundColor = fun color -> ConfigStore.BackgroundColor <- color
SetConnectionString = fun _ -> () // ignore
GetConnectionString = fun () -> ConfigStore.ConnectionString
SetConnectionString = fun connStr -> ConfigStore.ConnectionString <- connStr
}
1596
module AnnoyingPopupMessage =
open System.Windows.Forms
let createLabel() =
new Label(Text="You clicked the main window", Dock=DockStyle.Top)
let createMessageFlagCheckBox capabilities =
let getFlag,setFlag = capabilities
let ctrl= new CheckBox(Text="Don't show this annoying message again", Dock=Doc
kStyle.Bottom)
ctrl.Checked <- getFlag()
ctrl.CheckedChanged.Add (fun _ -> ctrl.Checked |> setFlag)
ctrl // return new control
let createOkButton (dialog:Form) =
let ctrl= new Button(Text="OK",Dock=DockStyle.Bottom)
ctrl.Click.Add (fun _ -> dialog.Close())
ctrl
let createForm capabilities =
let form = new Form(Text="Annoying Popup Message", Width=300, Height=150)
form.FormBorderStyle <- FormBorderStyle.FixedDialog
form.StartPosition <- FormStartPosition.CenterParent
let label = createLabel()
let messageFlag = createMessageFlagCheckBox capabilities
let okButton = createOkButton form
form.Controls.Add label
form.Controls.Add messageFlag
form.Controls.Add okButton
form
1597
In the main form ( createMainForm ) the complete set of capabilities are passed in, and they
are recombined in various ways as needed for the child controls ( popupMessageCapabilities ,
colorDialogCapabilities ).
1598
1599
module Startup =
// set up capabilities
let configCapabilities = Config.configurationCapabilities
let formCapabilities =
configCapabilities.GetMessageFlag,
configCapabilities.SetMessageFlag,
configCapabilities.GetBackgroundColor,
configCapabilities.SetBackgroundColor
// start
let form = UserInterface.createMainForm formCapabilities
form.ShowDialog() |> ignore
Summary of Part 1
As you can see, this code is very similar to an OO system designed with dependency
injection. There is no global access to capabilities, only those passed in from the parent.
Of course, the use of functions to parameterize behavior like this is nothing special. It's one
of the most fundamental functional programming techniques. So this code is not really
showing any new ideas, rather it is just demonstrating how a standard functional
programming approach can be applied to enforce access paths.
Some common questions at this point:
Question: This seems like extra work. Why do I need to do this at all?
If you have a simple system, you certainly don't need to do this. But here's where it might be
useful:
You have a system which uses fine-grained authorization already, and you want to
make this more explicit and easier to use in practice.
You have a system which runs at a high privilege but has strict requirements about
leaking data or performing actions in an unauthorized context.
1600
In these situations, I believe that is very important to be explicit about what the capabilities
are at all points in the codebase, not just in the UI layer. This not only helps with compliance
and auditing needs, but also has the practical benefit that it makes the code more modular
and easier to maintain.
Question: What's the difference between this approach and dependency injection?
Dependency injection and a capability-based model have different goals. Dependency
injection is all about decoupling, while capabilities are all about controlling access. As we
have seen, both approaches end up promoting similar designs.
Question: What happens if I have hundreds of capabilities that I need to pass around?
It seems like this should be a problem, but in practice it tends not to be. For one thing,
judicious use of partial application means that capabilities can be baked in to a function
before passing it around, so that child objects are not even aware of them.
Secondly, it is very easy -- just a few lines -- to create simple record types that contain a
group of capabilities (as I did with the ConfigurationCapabilities type) and pass those
around if needed.
Question: What's to stop someone accessing global capabilities without following
this approach?
Nothing in C# or F# can stop you accessing global public functions. Just like other best
practices, such as avoiding global variables, we have to rely on self-discipline (and maybe
code reviews) to keep us on the straight and narrow path!
But in the third part of this series, we'll look at a way to prevent access to global functions by
using access tokens.
Question: Aren't these just standard functional programming techniques?
Yes. I'm not claiming to be doing anything clever here!
Question: These capability functions have side-effects. What's up with that?
Yes, these capability functions are not pure. The goal here is not about being pure -- it's
about being explicit about the provision of capabilities.
Even if we used a pure IO context (e.g. in Haskell) it would not help control access to
capabilities. That is, in the context of security, there's a big difference between the capability
to change a password or credit card vs. the capability to change a background color
configuration, even though they are both just "IO" from a computation point of view.
Creating pure capabilities is possible but not very easy to do in F#, so I'm going to keep it
out of scope for this post.
1601
Question: What's your response to what (some person) wrote? And why didn't you
cite (some paper)?
This is a blog post, not an academic paper. I'm not an expert in this area, but just doing
some experiments of my own.
More importantly, as I said earlier, my goal here is very different from security experts -- I'm
not attempting to develop a ideal security model. Rather, I'm just trying to encourage some
good design practices that can help pragmatic developers avoid accidental vulnerabilities in
their code.
I've got more questions...
Some additional questions are answered at the end of part 2, so read those answers first.
Otherwise please add your question in the comments below, and I'll try to address it.
Further reading
The ideas on capability-based security here are mostly derived from the work of Mark Miller
and Marc Stiegler, and the erights.org website, although my version is cruder and simpler.
For a more complete understanding, I suggest you follow up on the links below:
The Wikipedia articles on Capability-based security and Object-capability model are a
good starting point.
What is a Capability, Anyway? by Jonathan Shapiro of the EROS project. He also
discusses ACL-based security vs. a capability-based model.
"The Lazy Programmer's Guide to Secure Computing", a great video on capabilitybased security by Marc Stiegler. Don't miss the last 5 mins (starting around
1h:02m:10s)!
"Object Capabilities for Security", a good talk by David Wagner.
A lot of work has been done on hardening languages for security and safety. For example
the E Language and Mark Miller's thesis on the E Language(PDF); the Joe-E Language built
on top of Java; Google's Caja built over JavaScript; Emily, a capability based language
derived from OCaml; and Safe Haskell(PDF).
My approach is not about strict safeness so much as proactively designing to avoid
unintentional breaches, and the references above do not focus on very much on design
specifically. The most useful thing I have found is a section on capability patterns in E.
Also, if you like this kind of thing, then head over to LtU where there are a number of
discussions, such as this one and this one and this paper.
1602
Coming up next
In the next post, we'll look at how to constrain capabilities based on claims such as the
current user's identity and role.
NOTE: All the code for this post is available as a gist here.
1603
1604
Note that I have deliberately added the IPrincipal to the method signature -- we are not
allowing any "magic" where the principal is fetched from a global context. As with the use of
any global, having implicit access hides the dependencies and makes it hard to test in
isolation.
Here's the F# equivalent, using a Success/Failure return value rather than throwing
exceptions:
let getCustomer id principal =
if customerIdBelongsToPrincipal id principal ||
principal.IsInRole("CustomerAgent")
then
// get customer data
Success "CustomerData"
else
Failure AuthorizationFailed
This "inline" authorization approach is all too common, but unfortunately it has many
problems.
It mixes up security concerns with the database logic. If the authorization logic gets
more complicated, the code will also get more complicated.
It throws an exception (C#) or returns an error (F#) if the authorization fails. It would be
nice if we could tell in advance if we had the authorization rather than waiting until the
last minute.
1605
Let's compare this with a capability-based approach. Instead of directly getting a customer,
we first obtain the capability of doing it.
class CustomerDatabase
{
// "real" code is hidden from public view
private CustomerData GetCustomer(CustomerId id)
{
// get customer data
}
// Get the capability to call GetCustomer
public Func<CustomerId,CustomerData> GetCustomerCapability(CustomerId id, IPrincip
al principal)
{
if ( CustomerIdBelongsToPrincipal(id, principal) ||
principal.IsInRole("CustomerAgent") )
{
// return the capability (the real method)
return GetCustomer;
}
else
{
// throw authorization exception
}
}
}
As you can see, if the authorization succeeds, a reference to the GetCustomer method is
returned to the caller.
It might not be obvious, but the code above has a rather large security hole. I can request
the capability for a particular customer id, but I get back a function that can called for any
customer id! That's not very safe, is it?
What we need to is "bake in" the customer id to the capability, so that it can't be misused.
The return value will now be a Func<CustomerData> , with the customer id not available to be
passed in any more.
1606
class CustomerDatabase
{
// "real" code is hidden from public view
private CustomerData GetCustomer(CustomerId id)
{
// get customer data
}
// Get the capability to call GetCustomer
public Func<CustomerData> GetCustomerCapability(CustomerId id, IPrincipal principal
)
{
if ( CustomerIdBelongsToPrincipal(id, principal) ||
principal.IsInRole("CustomerAgent") )
{
// return the capability (the real method)
return ( () => GetCustomer(id) );
}
else
{
// throw authorization exception
}
}
}
With this separation of concerns in place, we can now handle failure nicely, by returning an
optional value which is present if we get the capability, or absent if not. That is, we know
whether we have the capability at the time of trying to obtain it, not later on when we try to
use it.
1607
class CustomerDatabase
{
// "real" code is hidden from public view
// and doesn't need any checking of identity or role
private CustomerData GetCustomer(CustomerId id)
{
// get customer data
}
// Get the capability to call GetCustomer. If not allowed, return None.
public Option<Func<CustomerData>> GetCustomerCapability(CustomerId id, IPrincipal
principal)
{
if (CustomerIdBelongsToPrincipal(id, principal) ||
principal.IsInRole("CustomerAgent"))
{
// return the capability (the real method)
return Option<Func<CustomerData>>.Some( () => GetCustomer(id) );
}
else
{
return Option<Func<CustomerData>>.None();
}
}
}
This assumes that we're using some sort of Option type in C# rather than just returning
null!
Finally, we can put the authorization logic into its own class (say
CustomerDatabaseCapabilityProvider ), to keep the authorization concerns separate from the
CustomerDatabase .
We'll have to find some way of keeping the "real" database functions private to all other
callers though. For now, I'll just assume the database code is in a different assembly, and
mark the code internal .
1608
1609
Which means, as currently designed, for every function available in CustomerDatabase there
must be a parallel function available in CustomerDatabaseCapabilityProvider as well. We can
see that this approach will not scale well.
It would be nice if we had a way to generally get capabilities for a whole set of database
functions rather than one at a time. Let's see if we can do that!
sense, then, the second capability is a transformed version of the first, with additional
restrictions.
Transforming functions to new functions! This is something we can easily do.
So, let's write a transformer that, given any function of type CustomerId -> 'a , we return a
function with the customer id baked in ( unit -> 'a ), but only if the authorization
requirements are met.
1610
module CustomerCapabilityFilter =
// Get the capability to use any function that has a CustomerId parameter
// but only if the caller has the same customer id or is a member of the
// CustomerAgent role.
let onlyForSameIdOrAgents (id:CustomerId) (principal:IPrincipal) (f:CustomerId ->
'a) =
let principalId = GetIdForPrincipal(principal)
if (principalId = id) || principal.IsInRole("CustomerAgent") then
Some (fun () -> f id)
else
None
The type signature for the onlyForSameIdOrAgents function is (CustomerId -> 'a) -> (unit > 'a) option . It accepts any CustomerId based function and returns, maybe, the same
function with the customer id already applied if the authorization succeeds. If the
authorization does not succeed, None is returned instead.
You can see that this function will work generically with any function that has a CustomerId
as the first parameter. That could be "get", "update", "delete", etc.
So for example, given:
module internal CustomerDatabase =
let getCustomer (id:CustomerId) =
// get customer data
let updateCustomer (id:CustomerId) (data:CustomerData) =
// update customer data
We can create restricted versions now, for example at the top level bootstrapper or
controller:
let principal = // from context
let id = // from context
// attempt to get the capabilities
let getCustomerOnlyForSameIdOrAgents =
onlyForSameIdOrAgents id principal CustomerDatabase.getCustomer
let updateCustomerOnlyForSameIdOrAgents =
onlyForSameIdOrAgents id principal CustomerDatabase.updateCustomer
1611
val getCustomerOnlyForSameIdOrAgents :
(unit -> CustomerData) option
val updateCustomerOnlyForSameIdOrAgents :
(unit -> CustomerData -> unit) option
1612
module CustomerCapabilityFilter =
let onlyForSameId (id:CustomerId) (principal:IPrincipal) (f:CustomerId -> 'a) =
if customerIdBelongsToPrincipal id principal then
Some (fun () -> f id)
else
None
let onlyForAgents (id:CustomerId) (principal:IPrincipal) (f:CustomerId -> 'a) =
if principal.IsInRole("CustomerAgent") then
Some (fun () -> f id)
else
None
For the first business rule, onlyForSameId , we return a capability with the customer id baked
in, as before.
The second business rule, onlyForAgents , doesn't mention customer ids anywhere, so why
do we restrict the function parameter to CustomerId -> 'a ? The reason is that it enforces
that this rule only applies to customer centric capabilities, not ones relating to products or
payments, say.
But now, to make the output of this filter compatible with the first rule ( unit -> 'a ), we need
to pass in a customer id and partially apply it too. It's a bit of a hack but it will do for now.
We can also write a generic combinator that returns the first valid capability from a list.
// given a list of capability options,
// return the first good one, if any
let first capabilityList =
capabilityList |> List.tryPick id
It's a trivial implementation really -- this is the kind of helper function that is just to help the
code be a little more self-documenting.
With this in place, we can apply the rules separately, take the two filters and combine them
into one.
let getCustomerOnlyForSameIdOrAgents =
let f = CustomerDatabase.getCustomer
let cap1 = onlyForSameId id principal f
let cap2 = onlyForAgents id principal f
first [cap1; cap2]
// val getCustomerOnlyForSameIdOrAgents : (CustomerId -> CustomerData) option
1613
Or let's say we have some sort of restriction; the operation can only be performed during
business hours, say.
let onlyIfDuringBusinessHours (time:DateTime) f =
if time.Hour >= 8 && time.Hour <= 17 then
Some f
else
None
We can write another combinator that restricts the original capability. This is just a version of
"bind".
// given a capability option, restrict it
let restrict filter originalCap =
originalCap
|> Option.bind filter
With this in place, we can restrict the "agentsOnly" capability to business hours:
let getCustomerOnlyForAgentsInBusinessHours =
let f = CustomerDatabase.getCustomer
let cap1 = onlyForAgents id principal f
let restriction f = onlyIfDuringBusinessHours (DateTime.Now) f
cap1 |> restrict restriction
So now we have created a new capability, "Customer agents can only access customer data
during business hours", which tightens the data access logic a bit more.
We can combine this with the previous onlyForSameId filter to build a compound capability
which can access customer data:
if you have the same customer id (at any time of day)
if you are a customer agent (only during business hours)
let getCustomerOnlyForSameId =
let f = CustomerDatabase.getCustomer
onlyForSameId id principal f
let getCustomerOnlyForSameId_OrForAgentsInBusinessHours =
let cap1 = getCustomerOnlyForSameId
let cap2 = getCustomerOnlyForAgentsInBusinessHours
first [cap1; cap2]
As you can see, this approach is a useful way to build complex capabilities from simpler
ones.
1614
Additional transforms
It should be obvious that you can easily create additional transforms which can extend
capabilities in other ways. Some examples:
a capability that writes to an audit log on each execution.
a capability that can only be performed once.
a capability that can be revoked when needed.
a capability that is throttled and can only be performed a limited number of times in a
given time period (such as password change attempts).
And so on.
Here are implementations of the first three of them:
/// Uses of the capability will be audited
let auditable capabilityName f =
fun x ->
// simple audit log!
printfn "AUDIT: calling %s with %A" capabilityName x
// use the capability
f x
/// Allow the function to be called once only
let onlyOnce f =
let allow = ref true
fun x ->
if !allow then //! is dereferencing not negation!
allow := false
f x
else
Failure OnlyAllowedOnce
/// Return a pair of functions: the revokable capability,
/// and the revoker function
let revokable f =
let allow = ref true
let capability = fun x ->
if !allow then //! is dereferencing not negation!
f x
else
Failure Revoked
let revoker() =
allow := false
capability, revoker
1615
1616
revokableUpdatePassword (1,"password") |> printfn "Result 1st time before revoking: %A"
revokableUpdatePassword (1,"password") |> printfn "Result 2nd time before revoking: %A"
revoker()
revokableUpdatePassword (1,"password") |> printfn "Result 3nd time after revoking: %A"
A complete example in F#
Here's the code to a complete application in F# (also available as a gist here).
This example consists of a simple console app that allows you to get and update customer
records.
The first step is to login as a user. "Alice" and "Bob" are normal users, while "Zelda" has
a customer agent role.
Once logged in, you can select a customer to edit. Again, you are limited to a choice
between "Alice" and "Bob". (I'm sure you can hardly contain your excitement)
Once a customer is selected, you are presented with some (or none) of the following
options:
Get a customer's data.
Update a customer's data.
Update a customer's password.
Which options are shown depend on which capabilities you have. These in turn are based
on who you are logged in as, and which customer is selected.
1617
module Domain =
open Rop
type CustomerId = CustomerId of int
type CustomerData = CustomerData of string
type Password = Password of string
type FailureCase =
| AuthenticationFailed of string
| AuthorizationFailed
| CustomerNameNotFound of string
| CustomerIdNotFound of CustomerId
| OnlyAllowedOnce
| CapabilityRevoked
The FailureCase type documents all possible things that can go wrong at the top-level of
the application. See the "Railway Oriented Programming" talk for more discussion on this.
1618
module Capabilities =
open Rop
open Domain
// capabilities
type GetCustomerCap = unit -> SuccessFailure<CustomerData,FailureCase>
type UpdateCustomerCap = unit -> CustomerData -> SuccessFailure<unit,FailureCase>
type UpdatePasswordCap = Password -> SuccessFailure<unit,FailureCase>
type CapabilityProvider = {
/// given a customerId and IPrincipal, attempt to get the GetCustomer capabili
ty
getCustomer : CustomerId -> IPrincipal -> GetCustomerCap option
/// given a customerId and IPrincipal, attempt to get the UpdateCustomer capab
ility
updateCustomer : CustomerId -> IPrincipal -> UpdateCustomerCap option
/// given a customerId and IPrincipal, attempt to get the UpdatePassword capab
ility
updatePassword : CustomerId -> IPrincipal -> UpdatePasswordCap option
}
This module references a SuccessFailure result type similar to the one discussed here, but
which I won't show.
Implementing authentication
Next, we'll roll our own little authentication system. Note that when the user "Zelda" is
authenticated, the role is set to "CustomerAgent".
1619
module Authentication =
open Rop
open Domain
let customerRole = "Customer"
let customerAgentRole = "CustomerAgent"
let makePrincipal name role =
let iden = GenericIdentity(name)
let principal = GenericPrincipal(iden,[|role|])
principal :> IPrincipal
let authenticate name =
match name with
| "Alice" | "Bob" ->
makePrincipal name customerRole |> Success
| "Zelda" ->
makePrincipal name customerAgentRole |> Success
| _ ->
AuthenticationFailed name |> Failure
let customerIdForName name =
match name with
| "Alice" -> CustomerId 1 |> Success
| "Bob" -> CustomerId 2 |> Success
| _ -> CustomerNameNotFound name |> Failure
let customerIdOwnedByPrincipal customerId (principle:IPrincipal) =
principle.Identity.Name
|> customerIdForName
|> Rop.map (fun principalId -> principalId = customerId)
|> Rop.orElse false
The customerIdForName function attempts to find the customer id associated with a particular
name, while the customerIdOwnedByPrincipal compares this id with another one.
Implementing authorization
Here are the functions related to authorization, very similar to what was discussed above.
module Authorization =
open Rop
open Domain
let onlyForSameId (id:CustomerId) (principal:IPrincipal) (f:CustomerId -> 'a) =
if Authentication.customerIdOwnedByPrincipal id principal then
Some (fun () -> f id)
else
None
1620
1621
1622
module BusinessServices =
open Rop
open Domain
// use the getCustomer capability
let getCustomer capability =
match capability() with
| Success data -> printfn "%A" data
| Failure err -> printfn ".. %A" err
// use the updateCustomer capability
let updateCustomer capability =
printfn "Enter new data: "
let customerData = Console.ReadLine() |> CustomerData
match capability () customerData with
| Success _ -> printfn "Data updated"
| Failure err -> printfn ".. %A" err
// use the updatePassword capability
let updatePassword capability =
printfn "Enter new password: "
let password = Console.ReadLine() |> Password
match capability password with
| Success _ -> printfn "Password updated"
| Failure err -> printfn ".. %A" err
Note that each of these functions is passed in only the capability needed to do its job. This
code knows nothing about databases, or anything else.
Yes, in this crude example, the code is reading and writing directly to the console. Obviously
in a more complex (and less crude!) design, the inputs to these functions would be passed in
as parameters.
Here's a simple exercise: replace the direct access to the console with a capability such as
getDataWithPrompt ?
1623
I very much like using a "state" design like this, because it ensures that we can't accidentally
access data that we shouldn't. For example, we literally cannot access a customer when
none is selected, because there is no customer id in that state!
For each state, there is a corresponding function.
loggedOutActions is run when we are in the LoggedOut state. It presents the available
actions to you, and changes the state accordingly. You can log in as a user, or exit. If the
login is successful ( authenticate name worked) then the state is changed to LoggedIn .
loggedInActions is run when we are in the LoggedIn state. You can select a customer, or
follows:
First, find out what capabilities we have.
Next convert each capability into a corresponding menu text (using Option.map
because the capability might be missing), then remove the ones that are None.
Next, read a line from input, and depending on what it is, call one of the "business
services" ( getCustomer , updateCustomer , or updatePassword ).
Finally the mainUiLoop function loops around until the state is set to Exit .
module UserInterface =
open Rop
open Domain
open Capabilities
type CurrentState =
| LoggedOut
| LoggedIn of IPrincipal
| CustomerSelected of IPrincipal * CustomerId
| Exit
/// do the actions available while you are logged out. Return the new state
let loggedOutActions originalState =
printfn "[Login] enter Alice, Bob, Zelda, or Exit: "
let action = Console.ReadLine()
match action with
| "Exit" ->
// Change state to Exit
Exit
| name ->
// otherwise try to authenticate the name
match Authentication.authenticate name with
| Success principal ->
LoggedIn principal
1624
1625
1626
The capabilities record is then passed into the user interface when the app is started.
module Application=
open Rop
open Domain
open CustomerDatabase
open Authentication
open Authorization
let capabilities =
let getCustomerOnlyForSameId id principal =
onlyForSameId id principal CustomerDatabase.getCustomer
let getCustomerOnlyForAgentsInBusinessHours id principal =
let f = CustomerDatabase.getCustomer
let cap1 = onlyForAgents id principal f
let restriction f = onlyIfDuringBusinessHours (DateTime.Now) f
cap1 |> restrict restriction
let getCustomerOnlyForSameId_OrForAgentsInBusinessHours id principal =
let cap1 = getCustomerOnlyForSameId id principal
let cap2 = getCustomerOnlyForAgentsInBusinessHours id principal
first [cap1; cap2]
let updateCustomerOnlyForSameId id principal =
onlyForSameId id principal CustomerDatabase.updateCustomer
let updateCustomerOnlyForAgentsInBusinessHours id principal =
let f = CustomerDatabase.updateCustomer
let cap1 = onlyForAgents id principal f
let restriction f = onlyIfDuringBusinessHours (DateTime.Now) f
cap1 |> restrict restriction
let updateCustomerOnlyForSameId_OrForAgentsInBusinessHours id principal =
let cap1 = updateCustomerOnlyForSameId id principal
let cap2 = updateCustomerOnlyForAgentsInBusinessHours id principal
first [cap1; cap2]
let updatePasswordOnlyForSameId id principal =
let cap = passwordUpdate id principal CustomerDatabase.updatePassword
cap
|> Option.map (auditable "UpdatePassword" principal.Identity.Name)
// create the record that contains the capabilities
{
getCustomer = getCustomerOnlyForSameId_OrForAgentsInBusinessHours
updateCustomer = updateCustomerOnlyForSameId_OrForAgentsInBusinessHours
updatePassword = updatePasswordOnlyForSameId
}
1627
let start() =
// pass capabilities to UI
UserInterface.start capabilities
Summary of Part 2
In part 2, we added authorization and other transforms as a separate concern that could be
applied to restrict authority. Again, there is nothing particularly clever about using functions
like this, but I hope that this has given you some ideas that might be useful.
Question: Why go to all this trouble? What's the benefit over just testing an
"IsAuthorized" flag or something?
Here's a typical use of a authorization flag:
if user.CanUpdate() then
doTheAction()
Recall the quote from the previous post: "Capabilities should 'fail safe'. If a capability cannot
be obtained, or doesn't work, we must not allow any progress on paths that assumed that it
was successful."
The problem with testing a flag like this is that it's easy to forget, and the compiler won't
complain if you do. And then you have a possible security breach, as in the following code.
if user.CanUpdate() then
// ignore
// now do the action anyway!
doTheAction()
Not only that, but by "inlining" the test like this, we're mixing security concerns into our main
code, as pointed out earlier.
In contrast, a simple capability approach looks like this:
let updateCapability = // attempt to get the capability
match updateCapability with
| Some update -> update() // call the function
| None -> () // can't call the function
1628
In this example, it is not possible to accidentally use the capability if you are not allowed
to, as you literally don't have a function to call! And this has to be handled at compile-time,
not at runtime.
Furthermore, as we have just seen, capabilities are just functions, so we get all the benefits
of filtering, etc., which are not available with the inlined boolean test version.
Question: In many situations, you don't know whether you can access a resource
until you try. So aren't capabilities just extra work?
This is indeed true. For example, you might want to test whether a file exists first, and only
then try to access it. The IT gods are always ruthless in these cases, and in the time
between checking the file's existence and trying to open it, the file will probably be deleted!
So since we will have to check for exceptions anyway, why do two slow I/O operations when
one would have sufficed?
The answer is that the capability model is not about physical or system-level authority, but
logical authority -- only having the minimum you need to accomplish a task.
For example, a web service process may be operating at a high level of system authority,
and can access any database record. But we don't want to expose that to most of our code.
We want to make sure that any failures in programming logic cannot accidentally expose
unauthorized data.
Yes, of course, the capability functions themselves must do error handling, and as you can
see in the snippets above, I'm using the Success/Failure result type as described here. As
a result, we will need to merge failures from core functions (e.g. database errors) with
capability-specific failures such as Failure OnlyAllowedOnce .
Question: You've created a whole module with types defined for each capability. I
might have hundreds of capabilities. Do you really expect me to do all this extra
work?
There are two points here, so let's address each one in turn:
First, do you have a system that already uses fine-grained authorization, or has businesscritical requirements about not leaking data, or performing actions in an unauthorized
context, or needs a security audit?
If none of these apply, then indeed, this approach is complete overkill!
But if you do have such a system, that raises some new questions:
should the capabilities that are authorized be explicitly described in the code
somewhere?
1629
and if so, should the capabilities be explicit throughout the code, or only at the top-level
(e.g. in the controller) and implicit everywhere else.
The question comes to down to whether you want to be explicit or implicit.
Personally, I prefer things like this to be explicit. It may be a little extra work initially, just a
few lines to define each capability, but I find that it generally stops problems from occurring
further down the line.
And it has the benefit of acting as a single place to document all the security-related
capabilities that you support. Any new requirements would require a new entry here, so can
be sure that no capabilities can sneak in under the radar (assuming developers follow these
practices).
Question: In this code, you've rolled your own authorization. Wouldn't you use a
proper authorization provider instead?
Yes. This code is just an example. The authorization logic is completely separate from the
domain logic, so it should be easy to substitute any authorization provider, such as
ClaimsAuthorizationManager class, or something like XACML.
Coming up
In the next post, we'll look at how to use types to emulate access tokens and prevent
unauthorized access to global functions.
NOTE: All the code for this post is available as a gist here and here.
1630
Real-world authorization
First, let's step back and look at how authorization works in the real world.
Here's a simplified diagram of a basic authorization system (such as OAuth 2.0).
1631
Next, in the database module, we will add an extra parameter to each function, which is the
AccessToken.
Because the AccessToken token is required, we can safely make the database module
public now, as no unauthorized client can call the functions.
let getCustomer (accessToken:AccessToken) (id:CustomerId) =
// get customer data
let updateCustomer (accessToken:AccessToken) (id:CustomerId) (data:CustomerData) =
// update database
Note that the accessToken is not actually used in the implementation. It is just there to force
callers to obtain a token at compile time.
So let's look at how this might be used in practice.
1632
At this point we have an optional access token. Using Option.map , we can apply it to
CustomerDatabase.getCustomer to get an optional capability. And by partially applying the
access token, the user of the capability is isolated from the authentication process.
let getCustomerCapability =
accessToken |> Option.map CustomerDatabase.getCustomer
So now we have a statically typed authorization system that will prevent us from accidentally
getting too much access to the database.
1633
And, if we also store any data (such as the customer id) that was part of the authorization
request, then we don't need to ask for it again in the service.
What's more, we can trust that the information stored in the token is not forged or tampered
with because only the Authorization Service can create the token. In other words, this is the
equivalent of the token being "signed".
Next, the AccessToken type is redefined to be a generic container with a data field. The
constructor is still private, but a public getter is added so clients can access the data field.
type AccessToken<'data> = private {data:'data} with
// but do allow read access to the data
member this.Data = this.data
The authorization implementation is similar to the previous examples, except that this time
the capability type and customer id are stored in the token.
1634
Now what happens if we accidentally get the wrong type of access token? For example, let
us try to access the updatePassword function with an AccessCustomer token.
// attempt to get a capability
let getUpdatePasswordCap =
let accessToken = AuthorizationService.getAccessCustomerToken customerId principal
match accessToken with
| Some token ->
Some (fun password -> CustomerDatabase.updatePassword token password)
| None -> None
match getUpdatePasswordCap with
| Some updatePassword ->
let password = Password "p@ssw0rd"
updatePassword password
| None ->
Failure AuthorizationFailed // error
1636
This code will not even compile! The line CustomerDatabase.updatePassword token password
has an error.
error FS0001: Type mismatch. Expecting a
AccessToken<Capabilities.UpdatePassword>
but given a
AccessToken<Capabilities.AccessCustomer>
The type 'Capabilities.UpdatePassword' does not match the type 'Capabilities.AccessCus
tomer'
We have accidentally fetched the wrong kind of Access Token, but we have been stopped
from accessing the wrong database method at compile time.
Using types in this way is a nice solution to the problem of global access to a potentially
dangerous capability.
A complete example in F#
In the last post, I showed a complete console application in F# that used capabilities to
update a database.
Now let's update it to use access tokens as well. (The code is available as a gist here).
Since this is an update of the example, I'll focus on just the changes.
1637
module Capabilities =
open Rop
open Domain
// each access token gets its own type
type AccessCustomer = AccessCustomer of CustomerId
type UpdatePassword = UpdatePassword of CustomerId
// capabilities
type GetCustomerCap = unit -> SuccessFailure<CustomerData,FailureCase>
type UpdateCustomerCap = CustomerData -> SuccessFailure<unit,FailureCase>
type UpdatePasswordCap = Password -> SuccessFailure<unit,FailureCase>
type CapabilityProvider = {
/// given a customerId and IPrincipal, attempt to get the GetCustomer capabili
ty
getCustomer : CustomerId -> IPrincipal -> GetCustomerCap option
/// given a customerId and IPrincipal, attempt to get the UpdateCustomer capab
ility
updateCustomer : CustomerId -> IPrincipal -> UpdateCustomerCap option
/// given a customerId and IPrincipal, attempt to get the UpdatePassword capab
ility
updatePassword : CustomerId -> IPrincipal -> UpdatePasswordCap option
}
Implementing authorization
The authorization implementation must be changed to return AccessTokens now. The
onlyIfDuringBusinessHours restriction applies to capabilities, not access tokens, so it is
unchanged.
1638
Here's what the database implementation looked like before using access tokens:
let getCustomer id =
// code
let updateCustomer id data =
// code
let updatePassword (id:CustomerId,password:Password) =
// code
And here's what the code looks like after using access tokens:
1639
And here's what the code looks like after using access tokens:
1640
The tokenToCap function is a little utility that applies the (optional) token to a given function
as the first parameter. The output is an (equally optional) capability.
let tokenToCap f token =
token
|> Option.map (fun token ->
fun () -> f token)
And that's it for the changes needed to support access tokens. You can see all the code for
this example here.
Summary of Part 3
In this post, we used types to represent access tokens, as follows:
The AccessToken type is the equivalent of a signed ticket in a distributed authorization
system. It has a private constructor and can only be created by the Authorization
Service (ignoring reflection, of course!).
A specific type of AccessToken is needed to access a specific operation, which ensures
that we can't accidentally do unauthorized activities.
Each specific type of AccessToken can store custom data collected at authorization
time, such as a CustomerId .
Global functions, such as the database, are modified so that they cannot be accessed
without an access token. This means that they can safely be made public.
Question: Why not also store the caller in the access token, so that no other client
can use it?
This is not needed because of the authority-based approach we're using. As discussed in
the first post, once a client has a capability, they can pass it around to other people to use,
so there is no point limiting it to a specific caller.
1641
Question: The authorization module needs to know about the capability and access
token types now. Isn't that adding extra coupling?
If the authorization service is going to do its job, it has to know something about what
capabilities are available, so there is always some coupling, whether it is implicit
("resources" and "actions" in XACML) or explicit via types, as in this model.
So yes, the authorization service and database service both have a dependency on the set
of capabilities, but they are not coupled to each other directly.
Question: How do you use this model in a distributed system?
This model is really only designed to be used in a single codebase, so that type checking
can occur.
You could probably hack it so that types are turned into tickets at the boundary, and
conversely, but I haven't looked at that at all.
Question: Where can I read more on using types as access tokens?
This type-oriented version of an access token is my own design, although I very much doubt
that I'm the first person to think of using types this way. There are some related things for
Haskell (example) but I don't know of any directly analogous work that's accessible to
mainstream developers.
I've got more questions...
Some additional questions are answered at the end of part 1 and part 2, so read those
answers first. Otherwise please add your question in the comments below, and I'll try to
address it.
Conclusion
Thanks for making it all the way to the end!
As I said at the beginning, the goal is not to create an absolutely safe system, but instead
encourage you to think about and integrate authorization constraints into the design of your
system from the beginning, rather than treating it as an afterthought.
What's more, the point of doing this extra work is not just to improve security, but also to
improve the general design of your code. If you follow the principle of least authority, you get
modularity, decoupling, explicit dependencies, etc., for free!
In my opinion, a capability-based system works very well for this:
Functions map well to capabilities, and the need to pass capabilities around fits in very
1642
1643
But seriously, my imaginary co-worker's complaint has some validity: How many tests are
enough?
So now imagine that rather than being a developer, you are a test engineer who is
responsible for testing that the "add" function is implemented correctly.
Unfortunately for you, the implementation is being written by a burned-out, always lazy and
often malicious programmer, who I will call The Enterprise Developer From Hell, or "EDFH".
(The EDFH has a cousin who you might have heard of).
You are practising test-driven-development, enterprise-style, which means that you write a
test, and then the EDFH implements code that passes the test.
So you start with a test like this (using vanilla NUnit style):
1644
[<Test>]
let ``When I add 1 + 2, I expect 3``()=
let result = add 1 2
Assert.AreEqual(3,result)
The EDFH then changes the implementation of the add function to this:
let add x y =
if x=1 && y=2 then
3
else if x=2 && y=2 then
4
else
0
When you again complain to the EDFH, they point out that this approach is actually a best
practice. Apparently it's called "The Transformation Priority Premise".
At this point, you start thinking that the EDFH is being malicious, and that this back-and-forth
could go on forever!
1645
So the question is, what kind of test could you write so that a malicious programmer could
not create an incorrect implementation, even if they wanted to?
Well, you could start with a much larger list of known results, and mix them up a bit.
[<Test>]
let ``When I add two numbers, I expect to get their sum``()=
for (x,y,expected) in [ (1,2,3); (2,2,4); (3,5,8); (27,15,42); ]
let actual = add x y
Assert.AreEqual(expected,actual)
But the EDFH is tireless, and will update the implementation to include all of these cases as
well.
A much better approach is to generate random numbers and use those for inputs, so that a
malicious programmer could not possibly know what to do in advance.
let rand = System.Random()
let randInt() = rand.Next()
[<Test>]
let ``When I add two random numbers, I expect their sum``()=
let x = randInt()
let y = randInt()
let expected = x + y
let actual = add x y
Assert.AreEqual(expected,actual)
If the test looks like this, then the EDFH will be forced to implement the add function
correctly!
One final improvement -- the EDFH might just get lucky and have picked numbers that work
by chance, so let's repeat the random number test a number of times, say 100 times.
[<Test>]
let ``When I add two random numbers (100 times), I expect their sum``()=
for _ in [1..100] do
let x = randInt()
let y = randInt()
let expected = x + y
let actual = add x y
Assert.AreEqual(expected,actual)
1646
That's a good start, but it doesn't stop the EDFH. The EDFH could still implement add
using x * y and this test would pass!
So now what about the difference between add and multiply ? What does addition really
mean?
We could start by testing with something like this, which says that x + x should the same
as x * 2 :
let result1 = add x x
let result2 = x * 2
Assert.AreEqual(result1,result2)
1647
But now we are assuming the existence of multiplication! Can we define a property that only
depends on add itself?
One very useful approach is to see what happens when the function is repeated more than
once. That is, what if you add and then add to the result of that?
That leads to the idea that two add 1 s is the same as one add 2 . Here's the test:
[<Test>]
let ``Adding 1 twice is the same as adding 2``()=
for _ in [1..100] do
let x = randInt()
let y = randInt()
let result1 = x |> add 1 |> add 1
let result2 = x |> add 2
Assert.AreEqual(result1,result2)
That's great! add works perfectly with this test, while multiply doesn't.
However, note that the EDFH could still implement add using y - x and this test would
pass!
Luckily, we have the "parameter order" test above as well. So the combination of both of
these tests should narrow it down so that there is only one correct implementation, surely?
After submitting this test suite we find out the EDFH has written an implementation that
passes both these tests. Let's have a look:
let add x y = 0 // malicious implementation
1648
[<Test>]
let ``Adding zero is the same as doing nothing``()=
for _ in [1..100] do
let x = randInt()
let result1 = x |> add 0
let result2 = x
Assert.AreEqual(result1,result2)
So now we have a set of properties that can be used to test any implementation of add ,
and that force the EDFH to create a correct implementation:
With this in place, we can redefine one of the tests by pulling out the property into a separate
function, like this:
let commutativeProperty x y =
let result1 = add x y
let result2 = add y x // reversed params
result1 = result2
[<Test>]
let ``When I add two numbers, the result should not depend on parameter order``()=
propertyCheck commutativeProperty
We can also do the same thing for the other two properties.
After the refactoring, the complete code looks like this:
1649
1650
What's nice about these properties is that they work with all inputs, not just special magic
numbers. But more importantly, they show us the core essence of addition.
In fact, you can take this approach to the logical conclusion and actually define addition as
anything that has these properties.
This is exactly what mathematicians do. If you look up addition on Wikipedia, you'll see that
it is defined entirely in terms of commutativity, associativity, identity, and so on.
You'll note that in our experiment, we missed defining "associativity", but instead created a
weaker property ( x+1+1 = x+2 ). We'll see later that the EDFH can indeed write a malicious
implementation that satisfies this property, and that associativity is better.
Alas, it's hard to get properties perfect on the first attempt, but even so, by using the three
properties we came up with, we have got a much higher confidence that the implementation
is correct, and in fact, we have learned something too -- we have understood the
requirements in a deeper way.
Specification by properties
A collection of properties like this can be considered a specification.
Historically, unit tests, as well as being functional tests, have been used as a sort of
specification as well. But an approach to specification using properties instead of tests with
"magic" data is an alternative which I think is often shorter and less ambiguous.
You might be thinking that only mathematical kinds of functions can be specified this way,
but in future posts, we'll see how this approach can be used to test web services and
databases too.
Of course, not every business requirement can be expressed as properties like this, and we
must not neglect the social component of software development. Specification by example
and domain driven design can play a valuable role when working with non-technical
customers.
You also might be thinking that designing all these properties is a lot of work -- and you'd be
right! It is the hardest part. In a follow-up post, I'll present some tips for coming up with
properties which might reduce the effort somewhat.
But even with the extra effort involved upfront (the technical term for this activity is called
"thinking about the problem", by the way) the overall time saved by having automated tests
and unambigous specifications will more than pay for the upfront cost later.
1651
In fact, the arguments that are used to promote the benefits of unit testing can equally well
be applied to property-based testing! So if a TDD fan tells you that they don't have the time
to come up with property-based tests, then they might not be looking at the big picture.
could use the same approach for functions with any number of parameters.
When there is a counter-example to the property, we don't know what it is! Not very
helpful when the tests fail!
There's no logging of the random numbers that we generated, and there's no way to set
the seed, which means that we can't debug and reproduce errors easily.
It's not configurable. For example, we can't easily change the number of loops from 100
to something else.
It would be nice if there was a framework that did all that for us!
Thankfully there is! The "QuickCheck" library was originally developed for Haskell by Koen
Claessen and John Hughes, and has been ported to many other languages.
The version of QuickCheck used in F# (and C# too) is the excellent "FsCheck" library
created by Kurt Schelfthout. Although based on the Haskell QuickCheck, it has some nice
additional features, including integration with test frameworks such as NUnit and xUnit.
So let's look at how FsCheck would do the same thing as our homemade property-testing
system.
1652
System.IO.Directory.SetCurrentDirectory (__SOURCE_DIRECTORY__)
#I @"Packages\FsCheck.1.0.3\lib\net45"
//#I @"Packages\FsCheck.0.9.2.0\lib\net40-Client" // use older version for VS2012
#I @"Packages\NUnit.2.6.3\lib"
#r @"FsCheck.dll"
#r @"nunit.framework.dll"
open System
open FsCheck
open NUnit.Framework
Once FsCheck is loaded, you can use Check.Quick and pass in any "property" function. For
now, let's just say that a "property" function is any function (with any parameters) that returns
a boolean.
let add x y = x + y // correct implementation
let commutativeProperty (x,y) =
let result1 = add x y
let result2 = add y x // reversed params
result1 = result2
// check the property interactively
Check.Quick commutativeProperty
let adding1TwiceIsAdding2OnceProperty x =
let result1 = x |> add 1 |> add 1
let result2 = x |> add 2
result1 = result2
// check the property interactively
Check.Quick adding1TwiceIsAdding2OnceProperty
let identityProperty x =
let result1 = x |> add 0
result1 = x
// check the property interactively
Check.Quick identityProperty
If you check one of the properties interactively, say with Check.Quick commutativeProperty ,
you'll see the message:
Ok, passed 100 tests.
Let's see what happens when we have a malicious implementation of add . In the code
below, the EDFH implements add as multiplication!
That implementation will satisfy the commutative property, but what about the
adding1TwiceIsAdding2OnceProperty ?
let add x y =
x * y // malicious implementation
let adding1TwiceIsAdding2OnceProperty x =
let result1 = x |> add 1 |> add 1
let result2 = x |> add 2
result1 = result2
// check the property interactively
Check.Quick adding1TwiceIsAdding2OnceProperty
implementation that exploits this. They'll use a correct implementation for low input values
and an incorrect implementation for high input values:
let add x y =
if (x < 10) || (y < 10) then
x + y // correct for low values
else
x * y // incorrect for high values
1654
What's the alternative? Well, let's steal from the mathematicians and create an associative
property test.
let associativeProperty x y z =
let result1 = add x (add y z) // x + (y + z)
let result2 = add (add x y) z // (x + y) + z
result1 = result2
// check the property interactively
Check.Quick associativeProperty
1655
In this example, the ints are not generated uniformly, but clustered around zero. You can see
this for yourself with a little code:
// see how the values are clustered around the center point
intGenerator
|> Gen.sample 10 1000
|> Seq.groupBy id
|> Seq.map (fun (k,v) -> (k,Seq.length v))
|> Seq.sortBy (fun (k,v) -> k)
|> Seq.toList
You can see that most of the values are in the center (0 is generated 181 times, 1 is
generated 104 times), and the outlying values are rare (10 is generated only 3 times).
You can repeat with larger samples too. This one generates 10000 elements in the range
[-30,30]
intGenerator
|> Gen.sample 30 10000
|> Seq.groupBy id
|> Seq.map (fun (k,v) -> (k,Seq.length v))
|> Seq.sortBy (fun (k,v) -> k)
|> Seq.toList
There are plenty of other generator functions available as well as Gen.sample (more
documentation here).
1656
Once you have a generator for a base type, option and list generators follow. Here is a
generator for int option s:
let intOptionGenerator = Arb.generate<int option>
// generate 10 int options with a maximum size of 5
Gen.sample 5 10 intOptionGenerator
// result: [Some 0; Some -1; Some 2; Some 0; Some 0;
// Some -4; null; Some 2; Some -2; Some 0]
1657
The best thing is that the generator will work with your own user-defined types too!
type Color = Red | Green of int | Blue of bool
let colorGenerator = Arb.generate<Color>
// generate 10 colors with a maximum size of 50
Gen.sample 50 10 colorGenerator
// result: [Green -47; Red; Red; Red; Blue true;
// Green 2; Blue false; Red; Blue true; Green -12]
Here's one that generates a user-defined record type containing another user-defined type.
type Point = {x:int; y:int; color: Color}
let pointGenerator = Arb.generate<Point>
// generate 10 points with a maximum size of 50
Gen.sample 50 10 pointGenerator
(* result
[{x = -8; y = 12; color = Green -4;};
{x = 28; y = -31; color = Green -6;};
{x = 11; y = 27; color = Red;};
{x = -2; y = -13; color = Red;};
{x = 6; y = 12; color = Red;};
// etc
*)
There are ways to have more fine-grained control over how your types are generated, but
that will have to wait for another post!
1658
You have generated random numbers and found that then property fails for 100 , and you
want to try a smaller number. Arb.shrink will generate a sequence of ints, all of which are
smaller than 100. Each one of these is tried with the property in turn until the property fails
again.
isSmallerThan80 100 // false, so start shrinking
Arb.shrink 100 |> Seq.toList
// [0; 50; 75; 88; 94; 97; 99]
For each element in the list, test the property against it until you find another failure:
isSmallerThan80 0 // true
isSmallerThan80 50 // true
isSmallerThan80 75 // true
isSmallerThan80 88 // false, so shrink again
The property failed with 88 , so shrink again using that as a starting point:
Arb.shrink 88 |> Seq.toList
// [0; 44; 66; 77; 83; 86; 87]
isSmallerThan80 0 // true
isSmallerThan80 44 // true
isSmallerThan80 66 // true
isSmallerThan80 77 // true
isSmallerThan80 83 // false, so shrink again
1659
The property failed with 83 now, so shrink again using that as a starting point:
Arb.shrink 83 |> Seq.toList
// [0; 42; 63; 73; 78; 81; 82]
// smallest failure is 81, so shrink again
The property failed with 81 , so shrink again using that as a starting point:
Arb.shrink 81 |> Seq.toList
// [0; 41; 61; 71; 76; 79; 80]
// smallest failure is 80
After this point, shrinking on 80 doesn't work -- no smaller value will be found.
In this case then, FsCheck will report that 80 falsifies the property and that 4 shrinks were
needed.
Just as with generators, FsCheck will generate shrink sequences for almost any type:
Arb.shrink (1,2,3) |> Seq.toList
// [(0, 2, 3); (1, 0, 3); (1, 1, 3); (1, 2, 0); (1, 2, 2)]
Arb.shrink "abcd" |> Seq.toList
// ["bcd"; "acd"; "abd"; "abc"; "abca"; "abcb"; "abcc"; "abad"; "abbd"; "aacd"]
Arb.shrink [1;2;3] |> Seq.toList
// [[2; 3]; [1; 3]; [1; 2]; [1; 2; 0]; [1; 2; 2]; [1; 0; 3]; [1; 1; 3]; [0; 2; 3]]
And, as with generators, there are ways to customize how shrinking works if needed.
We do this by changing the default ("Quick") configuration. There is a field called MaxTest
that we can set. The default is 100, so let's increase it to 1000.
Finally, to use a specific config, you'll need to use Check.One(config,property) rather than
just Check.Quick(property) .
let config = {
Config.Quick with
MaxTest = 1000
}
Check.One(config,isSmallerThan80 )
// result: Ok, passed 1000 tests.
Oops! FsCheck didn't find a counter-example with 1000 tests either! Let's try once more with
10000 tests:
let config = {
Config.Quick with
MaxTest = 10000
}
Check.One(config,isSmallerThan80 )
// result: Falsifiable, after 8660 tests (1 shrink) (StdGen (539845487,295941658)):
// 80
Ok, so we finally got it to work. But why did it take so many tests?
The answer lies in some other configuration settings: StartSize and EndSize .
Remember that the generators start with small numbers and gradually increase them. This is
controlled by the StartSize and EndSize settings. By default, StartSize is 1 and
EndSize is 100. So at the end of the test, the "size" parameter to the generator will be 100.
But, as we saw, even if the size is 100, very few numbers are generated at the extremes. In
this case it means that numbers greater than 80 are unlikely to be generated.
So let's change the EndSize to something larger and see what happens!
let config = {
Config.Quick with
EndSize = 1000
}
Check.One(config,isSmallerThan80 )
// result: Falsifiable, after 21 tests (4 shrinks) (StdGen (1033193705,295941658)):
// 80
That's more like it! Only 21 tests needed now rather than 8660 tests!
1661
Again, FsCheck has found that 25 is the exact boundary point quite quickly. But how did it
do it?
First, the simplest way to see what FsCheck is doing is to use "verbose" mode. That is, use
Check.Verbose rather than Check.Quick :
When do this, you'll see an output like that shown below. I've added all the comments to
explain the various elements.
1662
0: // test 1
-1 // param 1
-1 // param 2
0 // param 3
// associativeProperty -1 -1 0 => true, keep going
1: // test 2
0
0
0 // associativeProperty 0 0 0 => true, keep going
2: // test 3
-2
0
-3 // associativeProperty -2 0 -3 => true, keep going
3: // test 4
1
2
0 // associativeProperty 1 2 0 => true, keep going
// etc
49: // test 50
46
-4
50 // associativeProperty 46 -4 50 => false, start shrinking
// etc
shrink:
35
-4
50 // associativeProperty 35 -4 50 => false, keep shrinking
shrink:
27
-4
50 // associativeProperty 27 -4 50 => false, keep shrinking
// etc
shrink:
25
1
29 // associativeProperty 25 1 29 => false, keep shrinking
shrink:
25
1
26 // associativeProperty 25 1 26 => false, keep shrinking
// next shrink fails
Falsifiable, after 50 tests (10 shrinks) (StdGen (995282583,295941602)):
25
1
26
1663
These functions are generic, and the list of parameters is represented by a list of unknown
length ( obj list ). But since I know I am testing a three parameter property I can hard-code
a three-element list parameter and print them all on one line.
The configuration also has a slot called Replay which is normally None , which means that
each run will be different.
If you set Replay to Some seed , then the test will be replayed exactly the same way. The
seed looks like StdGen (someInt,someInt) and is printed on each run, so if you want to
preserve a run all you need to do is paste that seed into the config.
And again, to use a specific config, you'll need to use Check.One(config,property) rather
than just Check.Quick(property) .
Here's the code with the default tracing functions changed, and the replay seed set explicitly.
// create a function for displaying a test
let printTest testNum [x;y;z] =
sprintf "#%-3i %3O %3O %3O\n" testNum x y z
// create a function for displaying a shrink
let printShrink [x;y;z] =
sprintf "shrink %3O %3O %3O\n" x y z
// create a new FsCheck configuration
let config = {
Config.Quick with
Replay = Random.StdGen (995282583,295941602) |> Some
Every = printTest
EveryShrink = printShrink
}
// check the given property with the new configuration
Check.One(config,associativeProperty)
The output is now much more compact, and looks like this:
1664
#0 -1 -1 0
#1 0 0 0
#2 -2 0 -3
#3 1 2 0
#4 -4 2 -3
#5 3 0 -3
#6 -1 -1 -1
// etc
#46 -21 -25 29
#47 -10 -7 -13
#48 -4 -19 23
#49 46 -4 50
// start shrinking first parameter
shrink 35 -4 50
shrink 27 -4 50
shrink 26 -4 50
shrink 25 -4 50
// start shrinking second parameter
shrink 25 4 50
shrink 25 2 50
shrink 25 1 50
// start shrinking third parameter
shrink 25 1 38
shrink 25 1 29
shrink 25 1 26
Falsifiable, after 50 tests (10 shrinks) (StdGen (995282583,295941602)):
25
1
26
So there you go -- it's quite easy to customize the FsCheck logging if you need to.
Let's look at how the shrinking was done in detail. The last set of inputs (46,-4,50) was false,
so shrinking started.
// The last set of inputs (46,-4,50) was false, so shrinking started
associativeProperty 46 -4 50 // false, so shrink
// list of possible shrinks starting at 46
Arb.shrink 46 |> Seq.toList
// result [0; 23; 35; 41; 44; 45]
We'll loop through the list [0; 23; 35; 41; 44; 45] stopping at the first element that causes
the property to fail:
1665
// find the next test that fails when shrinking the x parameter
let x,y,z = (46,-4,50)
Arb.shrink x
|> Seq.tryPick (fun x -> if associativeProperty x y z then None else Some (x,y,z) )
// answer (35, -4, 50)
The first element that caused a failure was x=35 , as part of the inputs (35, -4, 50) .
So now we start at 35 and shrink that:
// find the next test that fails when shrinking the x parameter
let x,y,z = (35,-4,50)
Arb.shrink x
|> Seq.tryPick (fun x -> if associativeProperty x y z then None else Some (x,y,z) )
// answer (27, -4, 50)
The first element that caused a failure was now x=27 , as part of the inputs (27, -4, 50) .
So now we start at 27 and keep going:
// find the next test that fails when shrinking the x parameter
let x,y,z = (27,-4,50)
Arb.shrink x
|> Seq.tryPick (fun x -> if associativeProperty x y z then None else Some (x,y,z) )
// answer (26, -4, 50)
// find the next test that fails when shrinking the x parameter
let x,y,z = (26,-4,50)
Arb.shrink x
|> Seq.tryPick (fun x -> if associativeProperty x y z then None else Some (x,y,z) )
// answer (25, -4, 50)
// find the next test that fails when shrinking the x parameter
let x,y,z = (25,-4,50)
Arb.shrink x
|> Seq.tryPick (fun x -> if associativeProperty x y z then None else Some (x,y,z) )
// answer None
At this point, x=25 is as low as you can go. None of its shrink sequence caused a failure.
So we're finished with the x parameter!
Now we just repeat this process with the y parameter
1666
// find the next test that fails when shrinking the y parameter
let x,y,z = (25,-4,50)
Arb.shrink y
|> Seq.tryPick (fun y -> if associativeProperty x y z then None else Some (x,y,z) )
// answer (25, 4, 50)
// find the next test that fails when shrinking the y parameter
let x,y,z = (25,4,50)
Arb.shrink y
|> Seq.tryPick (fun y -> if associativeProperty x y z then None else Some (x,y,z) )
// answer (25, 2, 50)
// find the next test that fails when shrinking the y parameter
let x,y,z = (25,2,50)
Arb.shrink y
|> Seq.tryPick (fun y -> if associativeProperty x y z then None else Some (x,y,z) )
// answer (25, 1, 50)
// find the next test that fails when shrinking the y parameter
let x,y,z = (25,1,50)
Arb.shrink y
|> Seq.tryPick (fun y -> if associativeProperty x y z then None else Some (x,y,z) )
// answer None
At this point, y=1 is as low as you can go. None of its shrink sequence caused a failure. So
we're finished with the y parameter!
Finally, we repeat this process with the z parameter
1667
// find the next test that fails when shrinking the z parameter
let x,y,z = (25,1,50)
Arb.shrink z
|> Seq.tryPick (fun z -> if associativeProperty x y z then None else Some (x,y,z) )
// answer (25, 1, 38)
// find the next test that fails when shrinking the z parameter
let x,y,z = (25,1,38)
Arb.shrink z
|> Seq.tryPick (fun z -> if associativeProperty x y z then None else Some (x,y,z) )
// answer (25, 1, 29)
// find the next test that fails when shrinking the z parameter
let x,y,z = (25,1,29)
Arb.shrink z
|> Seq.tryPick (fun z -> if associativeProperty x y z then None else Some (x,y,z) )
// answer (25, 1, 26)
// find the next test that fails when shrinking the z parameter
let x,y,z = (25,1,26)
Arb.shrink z
|> Seq.tryPick (fun z -> if associativeProperty x y z then None else Some (x,y,z) )
// answer None
Adding pre-conditions
Let's say that we have a new idea for a property to check. We'll create a property called
addition is not multiplication which will help to stop any malicious (or even accidental)
1668
Well duh, obviously 0+0 and 0*0 are equal. But how can we tell FsCheck to ignore just
those inputs and leave all the other ones alone?
This is done via a "condition" or filter expression that is prepended to the property function
using ==> (an operator defined by FsCheck).
Here's an example:
let additionIsNotMultiplication x y =
x + y <> x * y
let preCondition x y =
(x,y) <> (0,0)
let additionIsNotMultiplication_withPreCondition x y =
preCondition x y ==> additionIsNotMultiplication x y
Check.Quick additionIsNotMultiplication_withPreCondition
// Falsifiable, after 38 tests (0 shrinks) (StdGen (1870180794,295941700)):
// 2
// 2
This kind of precondition should only be used if you want to filter out a small number of
cases.
If most of the inputs will be invalid, then this filtering will be expensive. In this case there is a
better way to do it, which will be discussed in a future post.
1669
The FsCheck documentation has more on how you can tweak properties here.
1670
type AdditionSpecification =
static member ``Commutative`` x y = commutativeProperty x y
static member ``Associative`` x y z = associativeProperty x y z
static member ``Left Identity`` x = leftIdentityProperty x
static member ``Right Identity`` x = rightIdentityProperty x
Check.QuickAll<AdditionSpecification>()
1671
Rather than marking a test with Test or Fact , you use the Property attribute. And unlike
normal tests, these tests can have parameters!
Here's an example of some tests.
open NUnit.Framework
open FsCheck
open FsCheck.NUnit
[<Property(QuietOnSuccess = true)>]
let ``Commutative`` x y =
commutativeProperty x y
[<Property(Verbose= true)>]
let ``Associative`` x y z =
associativeProperty x y z
[<Property(EndSize=300)>]
let ``Left Identity`` x =
leftIdentityProperty x
As you can see, you can change the configuration for each test (such as Verbose and
EndSize ) via properties of the annotation.
And the QuietOnSuccess flag is available to make FsCheck compatible with standard test
frameworks, which are silent on success and only show messages if something goes wrong.
Summary
In this post I've introduced you to the basics of property-based checking.
There's much more to cover though! In future posts I will cover topics such as:
How to come up with properties that apply to your code. The properties don't have
to be mathematical. We'll look at more general properties such as inverses (for testing
serialization/deserialization), idempotence (for safe handling of multiple updates or
duplicate messages), and also look at test oracles.
How to create your own generators and shrinkers. We've seen that FsCheck can
generate random values nicely. But what about values with constraints such as positive
numbers, or valid email addresses, or phone numbers. FsCheck gives you the tools to
build your own.
How to do model-based testing, and in particular, how to test for concurrency issues.
I've also introduced the notion of an evil malicious programmer. You might think that such a
malicious programmer is unrealistic and over-the-top.
1672
But in many cases you act like an unintentionally malicious programmer. You happily create
a implementation that works for some special cases, but doesn't work more generally, not
out of evil intent, but out of unawareness and blindness.
Like fish unaware of water, we are often unaware of the assumptions we make. Propertybased testing can force us to become aware of them.
Until next time -- happy testing!
The code samples used in this post are available on GitHub.
Want more? I have written a follow up post on choosing properties for property-based
testing
UPDATE: I did a talk on property-based testing based on these posts. Slides and video here.
1673
1674
1675
The commutative property of addition is an obvious example of this pattern. For example,
the result of add 1 then add 2 is the same as the result of add 2 followed by add 1 .
This pattern, generalized, can produce a wide range of useful properties. We'll see some
more uses of this pattern later in this post.
Other pair of functions fit this pattern too, even though they are not strict inverses, pairs such
as insert / contains , create / exists , etc.
1676
Common invariants include size of a collection (for map say), the contents of a collection
(for sort say), the height or depth of something in proportion to size (e.g. balanced trees).
"The more things change, the more they stay the same"
These kinds of properties are based on "idempotence" -- that is, doing an operation twice is
the same as doing it once.
In the diagram below, using distinct to filter the set returns two items, but doing distinct
twice returns the same set again.
Idempotence properties are very useful, and can be extended to things like database
updates and message processing.
Induction properties are often naturally applicable to recursive structures such as lists and
trees.
Often an algorithm to find a result can be complicated, but verifying the answer is easy.
In the diagram below, we can see that finding a route through a maze is hard, but checking
that it works is trivial!
Many famous problems are of this sort, such as prime number factorization. But this
approach can be used for even simple problems.
For example, you might check that a string tokenizer works by just concatenating all the
tokens again. The resulting string should be the same as what you started with.
For example, you might have a high-performance algorithm with optimization tweaks that
you want to test. In this case, you might compare it with a brute force algorithm that is much
slower but is also much easier to write correctly.
Similarly, you might compare the result of a parallel or concurrent algorithm with the result of
a linear, single thread version.
In this section, we'll apply these categories to see if we can come up with properties for
some simple functions such as "sort a list" and "reverse a list".
1679
let ``+1 then sort should be same as sort then +1`` sortFn aList =
let add1 x = x + 1
let result1 = aList |> sortFn |> List.map add1
let result2 = aList |> List.map add1 |> sortFn
result1 = result2
// test
let goodSort = List.sort
Check.Quick (``+1 then sort should be same as sort then +1`` goodSort)
// Ok, passed 100 tests.
Well, that works, but it also would work for a lot of other transformations too. For example, if
we implemented List.sort as just the identity, then this property would be satisfied equally
well! You can test this for yourself:
let badSort aList = aList
Check.Quick (``+1 then sort should be same as sort then +1`` badSort)
// Ok, passed 100 tests.
The problem with this property is that it is not exploiting any of the "sortedness". We know
that a sort will probably reorder a list, and certainly, the smallest element should be first.
How about adding an item that we know will come at the front of the list after sorting?
Path 1: We append Int32.MinValue to the end of the list, then sort.
Path 2: We sort, then prepend Int32.MinValue to the front of the list.
Both lists should be equal.
1680
let ``append minValue then sort should be same as sort then prepend minValue`` sortFn
aList =
let minValue = Int32.MinValue
let appendThenSort = (aList @ [minValue]) |> sortFn
let sortThenPrepend = minValue :: (aList |> sortFn)
appendThenSort = sortThenPrepend
// test
Check.Quick (``append minValue then sort should be same as sort then prepend minValue`
` goodSort)
// Ok, passed 100 tests.
In other words, the bad sort of [0; minValue] is not the same as [minValue; 0] .
So that's good!
But... we've got some hard coded things in there that the Enterprise Developer From Hell
(see previous post) could take advantage of! The EDFH will exploit the fact that we always
use Int32.MinValue and that we always prepend or append it to the test list.
In other words, the EDFH can identify which path we are on and have special cases for each
one:
// The Enterprise Developer From Hell strikes again
let badSort2 aList =
match aList with
| [] -> []
| _ ->
let last::reversedTail = List.rev aList
if (last = Int32.MinValue) then
// if min is last, move to front
let unreversedTail = List.rev reversedTail
last :: unreversedTail
else
aList // leave alone
1681
We could fix this by (a) picking a random number smaller than any number in the list and (b)
inserting it at a random location rather than always appending it. But rather than getting too
complicated, let's stop and reconsider.
An alternative approach which also exploits the "sortedness" is to first negate all the values,
then on the path that negates after the sort, add an extra reverse as well.
let ``negate then sort should be same as sort then negate then reverse`` sortFn aList
=
let negate x = x * -1
let negateThenSort = aList |> List.map negate |> sortFn
let sortThenNegateAndReverse = aList |> sortFn |> List.map negate |> List.rev
negateThenSort = sortThenNegateAndReverse
This property is harder for the EDFH to beat because there are no magic numbers to help
identify which path you are on:
1682
// test
Check.Quick ( ``negate then sort should be same as sort then negate then reverse`` goo
dSort)
// Ok, passed 100 tests.
// test
Check.Quick ( ``negate then sort should be same as sort then negate then reverse`` ba
dSort)
// Falsifiable, after 1 test (1 shrinks)
// [1; 0]
// test
Check.Quick ( ``negate then sort should be same as sort then negate then reverse`` ba
dSort2)
// Falsifiable, after 5 tests (3 shrinks)
// [1; 0]
You might argue that we are only testing sorting for lists of integers. But the List.sort
function is generic and knows nothing about integers per se, so I have high confidence that
this property does test the core sorting logic.
1683
Here are the test results for the correct function and for two incorrect functions:
// test
let goodReverse = List.rev
Check.Quick (``append any value then reverse should be same as reverse then prepend sa
me value`` goodReverse)
// Ok, passed 100 tests.
// bad implementation fails
let badReverse aList = []
Check.Quick (``append any value then reverse should be same as reverse then prepend sa
me value`` badReverse)
// Falsifiable, after 1 test (2 shrinks)
// true, []
// bad implementation fails
let badReverse2 aList = aList
Check.Quick (``append any value then reverse should be same as reverse then prepend sa
me value`` badReverse2)
// Falsifiable, after 1 test (1 shrinks)
// true, [false]
You might notice something interesting here. I never specified the type of the list. The
property works with any list.
In cases like these, FsCheck will generate random lists of bools, strings, ints, etc.
In both failing cases, the anyValue is a bool. So FsCheck is using lists of bools to start with.
Here's an exercise for you: Is this property good enough? Is there some way that the EDFH
can create an implementation that will pass?
1684
And it passes:
let goodReverse = List.rev
Check.Quick (``reverse then reverse should be same as original`` goodReverse)
// Ok, passed 100 tests.
Nevertheless, the use of properties involving inverses can be very useful to verify that your
inverse function (such as deserialization) does indeed "undo" the primary function (such as
serialization).
We'll see some real examples of using this in the next post.
1685
If we are looking for the shortest path, we might not be able to check it, but at least we know
that we have some valid path.
This principle can be applied quite generally.
For example, let's say that we want to check whether a string split function is working.
We don't have to write a tokenizer -- all we have to do is ensure that the tokens, when
concatenated, give us back the original string!
But how can we create an original string? The random strings generated by FsCheck are
unlikely to contain many commas!
There are ways that you can control exactly how FsCheck generates random data, which
we'll look at later.
For now though, we'll use a trick. The trick is to let FsCheck generate a list of random
strings, and then we'll build an originalString from them by concatting them together.
So here's the complete code for the property:
1686
let ``concatting the elements of a string split by commas recreates the original strin
g`` aListOfStrings =
// helper to make a string
let addWithComma s t = s + "," + t
let originalString = aListOfStrings |> List.fold addWithComma ""
// now for the property
let tokens = originalString.Split [| ',' |]
let recombinedString =
// can use reduce safely because there is always at least one token
tokens |> Array.reduce addWithComma
// compare the result with the original
originalString = recombinedString
But something funny happens when we try to check it. We get an error!
1687
What does System.Exception: type not handled System.IComparable mean? It means that
FsCheck is trying to generate a random list, but all it knows is that the elements must be
IComparable . But IComparable is not a type than can be instantiated, so FsCheck throws
an error.
How can we prevent this from happening? The solution is to specify a particular type for the
property, such as int list , like this:
let ``adjacent pairs from a list should be ordered`` sortFn (aList:int list) =
let pairs = aList |> sortFn |> Seq.pairwise
pairs |> Seq.forall (fun (x,y) -> x <= y )
Note that even though the property has been constrained, the property is still a very general
one. We could have used string list instead, for example, and it would work just the
same.
let ``adjacent pairs from a string list should be ordered`` sortFn (aList:string list)
=
let pairs = aList |> sortFn |> Seq.pairwise
pairs |> Seq.forall (fun (x,y) -> x <= y )
Check.Quick (``adjacent pairs from a string list should be ordered`` goodSort)
// Ok, passed 100 tests.
TIP: If FsCheck throws "type not handled", add explicit type constraints to your
property
1688
Are we done now? No! One problem with this property is that it doesn't catch malicious
implementations by the EDFH.
// bad implementation passes
let badSort aList = []
Check.Quick (``adjacent pairs from a list should be ordered`` badSort)
// Ok, passed 100 tests.
1689
Unfortunately, the BDFH is not defeated and can come up with another compliant
implementation! Just repeat the first element N times!
// bad implementation has same length
let badSort2 aList =
match aList with
| [] -> []
| head::_ -> List.replicate (List.length aList) head
// for example
// badSort2 [1;2;3] => [1;1;1]
1690
That leads us to a new property: a sorted list is always a permutation of the original list. Aha!
Let's write the property in terms of permutations now:
let ``a sorted list is always a permutation of the original list`` sortFn (aList:int l
ist) =
let sorted = aList |> sortFn
let permutationsOfOriginalList = permutations aList
// the sorted list must be in the seq of permutations
permutationsOfOriginalList
|> Seq.exists (fun permutation -> permutation = sorted)
1691
1692
Check.Quick (``a sorted list is always a permutation of the original list`` goodSort)
Hmmm. That's funny, nothing seems to be happening. And my CPU is maxing out for some
reason. What's going on?
What's going on is that you are going to be sitting there for a long time! If you are following
along at home, I suggest you right-click and cancel the interactive session now.
The innocent looking permutations is really really slow for any normal sized list. For
example, a list of just 10 items has 3,628,800 permutations. While with 20 items, you are
getting to astronomical numbers.
And of course, FsCheck will be doing hundreds of these tests! So this leads to an important
tip:
TIP: Make sure your property checks are very fast. You will be running them a LOT!
We've already seen that even in the best case, FsCheck will evaluate the property 100
times. And if shrinking is needed, even more. So make sure your tests are fast to run!
But what happens if you are dealing with real systems such as databases, networks, or
other slow dependencies?
In his (highly recommended) video on using QuickCheck, John Hughes tells of when his
team was trying to detect flaws in a distributed data store that could be caused by network
partitions and node failures.
Of course, killing real nodes thousands of times was too slow, so they extracted the core
logic into a virtual model, and tested that instead. As a result, the code was later refactored
to make this kind of testing easier. In other words, property-based testing influenced the
design of the code, just as TDD would.
Here's the code for isPermutationOf and its associated helper functions:
1693
/// Given an element and a list, and other elements previously skipped,
/// return a new list without the specified element.
/// If not found, return None
let rec withoutElementRec anElement aList skipped =
match aList with
| [] -> None
| head::tail when anElement = head ->
// matched, so create a new list from the skipped and the remaining
// and return it
let skipped' = List.rev skipped
Some (skipped' @ tail)
| head::tail ->
// no match, so prepend head to the skipped and recurse
let skipped' = head :: skipped
withoutElementRec anElement tail skipped'
/// Given an element and a list
/// return a new list without the specified element.
/// If not found, return None
let withoutElement x aList =
withoutElementRec x aList []
/// Given two lists, return true if they have the same contents
/// regardless of order
let rec isPermutationOf list1 list2 =
match list1 with
| [] -> List.isEmpty list2 // if both empty, true
| h1::t1 ->
match withoutElement h1 list2 with
| None -> false
| Some t2 ->
isPermutationOf t1 t2
Let's try the test again. And yes, this time it completes before the heat death of the universe.
Check.Quick (``a sorted list has same contents as the original list`` goodSort)
// Ok, passed 100 tests.
What's also great is that the malicious implementation now fails to satisfy this property!
Check.Quick (``a sorted list has same contents as the original list`` badSort2)
// Falsifiable, after 2 tests (5 shrinks)
// [1; 0]
In fact, these two properties, adjacent pairs from a list should be ordered and a sorted
list has same contents as the original list should indeed ensure that any implementation
is correct.
1694
What we want then, is an equivalent to AND and OR that is designed to work with
properties.
FsCheck to the rescue! There are built in operators to combine properties: .&. for AND
and .|. for OR .
Here is an example of them in use:
let ``list is sorted``sortFn (aList:int list) =
let prop1 = ``adjacent pairs from a list should be ordered`` sortFn aList
let prop2 = ``a sorted list has same contents as the original list`` sortFn aList
prop1 .&. prop2
When we test the combined property with a good implementation of sort , everything works
as expected.
let goodSort = List.sort
Check.Quick (``list is sorted`` goodSort )
// Ok, passed 100 tests.
1695
And now, when we test with the bad sort, we get a message Label of failing property: a
sorted list has same contents as the original list :
For more on these operators, see the FsCheck documentation under "And, Or and Labels".
And now, back to the property-divising strategies.
1696
But unfortunately, just like previous examples, the malicious implementations also pass.
let badSort aList = []
Check.Quick (``First element is <= than second, and tail is also sorted`` badSort )
// Ok, passed 100 tests.
let badSort2 aList =
match aList with
| [] -> []
| head::_ -> List.replicate (List.length aList) head
Check.Quick (``First element is <= than second, and tail is also sorted`` badSort2)
// Ok, passed 100 tests.
So as before, we'll need another property (such as the has same contents invariant) to
ensure that the code is correct.
If you do have a recursive data structure, then try looking for recursive properties. They are
pretty obvious and low hanging, when you get the hang of it.
1697
In general, any kind of query should be idempotent, or to put it another way: "asking a
question should not change the answer".
In the real world, this may not be the case. A simple query on a datastore run at different
times may give different results.
Here's a quick demonstration.
First we'll create a NonIdempotentService that gives different results on each query.
1698
type NonIdempotentService() =
let mutable data = 0
member this.Get() =
data
member this.Set value =
data <- value
let ``querying NonIdempotentService after update gives the same result`` value1 value2
=
let service = NonIdempotentService()
service.Set value1
// first GET
let get1 = service.Get()
// another task updates the data store
service.Set value2
// second GET called just like first time
let get2 = service.Get()
get1 = get2
But if we test it now, we find that it does not satisfy the required idempotence property:
Check.Quick ``querying NonIdempotentService after update gives the same result``
// Falsifiable, after 2 tests
1699
type IdempotentService() =
let mutable data = Map.empty
member this.GetAsOf (dt:DateTime) =
data |> Map.find dt
member this.SetAsOf (dt:DateTime) value =
data <- data |> Map.add dt value
let ``querying IdempotentService after update gives the same result`` value1 value2 =
let service = IdempotentService()
let dt1 = DateTime.Now.AddMinutes(-1.0)
service.SetAsOf dt1 value1
// first GET
let get1 = service.GetAsOf dt1
// another task updates the data store
let dt2 = DateTime.Now
service.SetAsOf dt2 value2
// second GET called just like first time
let get2 = service.GetAsOf dt1
get1 = get2
So, if you are building a REST GET handler or a database query service, and you want
idempotence, you should consider using techniques such as etags, "as-of" times, date
ranges, etc.
If you need tips on how to do this, searching for idempotency patterns will turn up some
good results.
1700
So for "list sort", there are many simple but slow implementations around. For example,
here's a quick implementation of insertion sort:
module InsertionSort =
// Insert a new element into a list by looping over the list.
// As soon as you find a larger element, insert in front of it
let rec insert newElem list =
match list with
| head::tail when newElem > head ->
head :: insert newElem tail
| other -> // including empty list
newElem :: other
// Sorts a list by inserting the head into the rest of the list
// after the rest have been sorted
let rec sort list =
match list with
| [] -> []
| head::tail ->
insert head (sort tail)
// test
// insertionSort [5;3;2;1;1]
With this in place, we can write a property that tests the result against insertion sort.
let ``sort should give same result as insertion sort`` sortFn (aList:int list) =
let sorted1 = aList |> sortFn
let sorted2 = aList |> InsertionSort.sort
sorted1 = sorted2
1701
Another way to think about Roman numerals is to imagine an abacus. Each wire has four
"unit" beads and one "five" bead.
This leads to the so-called "bi-quinary" approach:
1702
We now have two completely different algorithms, and we can cross-check them with each
other to see if they give the same result.
let ``biquinary should give same result as tallying`` arabic =
let tallyResult = arabicToRomanUsingTallying arabic
let biquinaryResult = arabicToRomanUsingBiQuinary arabic
tallyResult = biquinaryResult
But if we try running this code, we get a ArgumentException: The input must be non-negative
due to the String.replicate call.
Check.Quick ``biquinary should give same result as tallying``
// ArgumentException: The input must be non-negative.
So we need to only include inputs that are positive. We also need to exclude numbers that
are greater than 4000, say, since the algorithms break down there too.
How can we implement this filter?
We saw in the previous post that we could use preconditions. But for this example, we'll try
something different and change the generator.
1703
First we'll define a new arbitrary integer called arabicNumber which is filtered as we want (an
"arbitrary" is a combination of a generator algorithm and a shrinker algorithm, as described
in the previous post).
let arabicNumber = Arb.Default.Int32() |> Arb.filter (fun i -> i > 0 && i <= 4000)
Next, we create a new property which is constrained to only use "arabicNumber" by using
the Prop.forAll helper.
We'll give the property the rather clever name of "for all values of arabicNumber, biquinary
should give same result as tallying".
let ``for all values of arabicNumber biquinary should give same result as tallying`` =
Prop.forAll arabicNumber ``biquinary should give same result as tallying``
"Model-based" testing
"Model-based" testing, which we will discuss in more detail in a later post, is a variant on
having a test oracle.
The way it works is that, in parallel with your (complex) system under test, you create a
simplified model.
Then, when you do something to the system under test, you do the same (but simplified)
thing to your model.
At the end, you compare your model's state with the state of the system under test. If they
are the same, you're done. If not, either your SUT is buggy or your model is wrong and you
have to start over!
1704
With that, we have come to the end of the various property categories. We'll go over them
one more time in a minute -- but first, an interlude.
If you sometimes feel that trying to find properties is a mental challenge, you're not alone.
Would it help to pretend that it is a game?
As it happens, there is a game based on property-based testing.
It's called Zendo and it involves placing sets of objects (such as plastic pyramids) on a table,
such that each layout conforms to a pattern -- a rule -- or as we would now say, a property!.
The other players then have to guess what the rule (property) is, based on what they can
see.
Here's a picture of a Zendo game in progress:
The white stones mean the property has been satisfied, while black stones mean failure.
Can you guess the rule here? I'm going to guess that it's something like "a set must have a
yellow pyramid that's not touching the ground".
Alright, I suppose Zendo wasn't really inspired by property-based testing, but it is a fun
game, and it has even been known to make an appearance at programming conferences.
If you want to learn more about Zendo, the rules are here.
1705
With all these categories in hand, let's look at one more example problem, and see if we can
find properties for it.
This sample is based on the well-known Dollar example described in Kent Beck's "TDD By
Example" book.
Nat Pryce, of Growing Object-Oriented Software Guided by Tests fame, wrote a blog post
about property-based testing a while ago ("Exploring Test-Driven Development with
QuickCheck").
In it, he expressed some frustration about property-based testing being useful in practice. So
let's revisit the example he referenced and see what we can do with it.
We're not going to attempt to critique the design itself and make it more type-driven -- others
have done that. Instead, we'll take the design as given and see what properties we can
come up with.
So what do we have?
A Dollar class that stores an Amount .
Methods Add and Times that transform the amount in the obvious way.
// OO style class with members
type Dollar(amount:int) =
member val Amount = amount with get, set
member this.Add add =
this.Amount <- this.Amount + add
member this.Times multiplier =
this.Amount <- this.Amount * multiplier
static member Create amount =
Dollar amount
So, first let's try it out interactively to make sure it works as expected:
let d = Dollar.Create 2
d.Amount // 2
d.Times 3
d.Amount // 6
d.Add 1
d.Amount // 7
But that's just playing around, not real testing. So what kind of properties can we think of?
Let's run through them all again:
Different paths to same result
Inverses
1706
Invariants
Idempotence
Structural induction
Easy to verify
Test oracle
Let's skip the "different paths" one for now. What about inverses? Are there any inverses we
can use?
Yes, the setter and getter form an inverse that we can create a property from:
let ``set then get should give same result`` value =
let obj = Dollar.Create 0
obj.Amount <- value
let newValue = obj.Amount
value = newValue
Check.Quick ``set then get should give same result``
// Ok, passed 100 tests.
Idempotence is relevant too. For example, doing two sets in a row should be the same as
doing just one. Here's a property for that:
let ``set amount is idempotent`` value =
let obj = Dollar.Create 0
obj.Amount <- value
let afterFirstSet = obj.Amount
obj.Amount <- value
let afterSecondSet = obj.Amount
afterFirstSet = afterSecondSet
Check.Quick ``set amount is idempotent``
// Ok, passed 100 tests.
1707
But in "TDD by Example" , Kent quickly realizes the problems with that and changes it to an
immutable class, so let me do the same.
Here's the immutable version:
type Dollar(amount:int) =
member val Amount = amount
member this.Add add =
Dollar (amount + add)
member this.Times multiplier =
Dollar (amount * multiplier)
static member Create amount =
Dollar amount
// interactive test
let d1 = Dollar.Create 2
d1.Amount // 2
let d2 = d1.Times 3
d2.Amount // 6
let d3 = d2.Add 1
d3.Amount // 7
What's nice about immutable code is that we can eliminate the need for testing of setters, so
the two properties we just created have now become irrelevant!
To tell the truth they were pretty trivial anyway, so it's no great loss.
So then, what new properties can we devise now?
Let's look at the Times method. How can we test that? Which one of the strategies can we
use?
I think the "different paths to same result" is very applicable. We can do the same thing we
did with "sort" and do a times operation both "inside" and "outside" and see if they give the
same result.
1708
let ``create then times should be same as times then create`` start multiplier =
let d0 = Dollar.Create start
let d1 = d0.Times(multiplier)
let d2 = Dollar.Create (start * multiplier)
d1 = d2
So now we need to fix this by adding support for IEquatable and so on.
You can do that if you like -- I'm going to switch to F# record types and get equality for free!
1709
We can extend this approach for different paths. For example, we can extract the amount
and compare it directly, like this:
1710
let ``create then times then add should be same as times then add then create`` start
multiplier adder =
let d0 = Dollar.Create start
let d1 = d0.Times(multiplier)
let d2 = d1.Add(adder)
let directAmount = (start * multiplier) + adder
let d3 = Dollar.Create directAmount
d2 = d3
Check.Quick ``create then times then add should be same as times then add then create`
`
// Ok, passed 100 tests.
So this "different paths, same result" approach is very fruitful, and we can generate lots of
paths this way.
1711
Let's add a Map method to Dollar . And we can also rewrite Times and Add in terms of
Map :
But how can we test it now? What functions should we pass in?
Don't worry! FsCheck has you covered! In cases like this, FsCheck will actually generate
random functions for you too!
Try it -- it just works!
Check.Quick ``create then map should be same as map then create``
// Ok, passed 100 tests.
Our new "map" property is much more general than the original property using "times", so
we can eliminate the latter safely.
1712
0:
18
<fun:Invoke@3000>
1:
7
<fun:Invoke@3000>
-- etc
98:
47
<fun:Invoke@3000>
99:
36
<fun:Invoke@3000>
Ok, passed 100 tests.
... you get a detailed log of each function that was used:
1713
0:
0
{ 0->1 }
1:
0
{ 0->0 }
2:
2
{ 2->-2 }
-- etc
98:
-5
{ -5->-52 }
99:
10
{ 10->28 }
Ok, passed 100 tests.
Each { 2->-2 } , { 10->28 } , etc., represents the function that was used for that iteration.
1714
These last two points are the most important for me. Programming is not a matter of writing
lines of code, it is about creating a design that meets the requirements.
So, anything that helps you think deeply about the requirements and what can go wrong
should be a key tool in your personal toolbox!
For example, in the Roman Numeral section, we saw that accepting int was a bad idea
(the code broke!). We had a quick fix, but really we should model the concept of a
PositiveInteger in our domain, and then change our code to use that type rather than just
an int . This demonstrates how using PBT can actually improve your domain model, not
just find bugs.
Similarly, introducing a Map method in the Dollar scenario not only made testing easier, but
actually improved the usefulness of the Dollar "api".
Stepping back to look at the big picture, though, TDD and property-based testing are not at
all in conflict. They share the same goal of building correct programs, and both are really
more about design than coding (think "Test-driven design" rather than "Test-driven
development").
1715
1716
1717
First, a little background reading from a reliable source shows that they probably originated
from something similar to tally marks.
This explains the simple strokes for "I" to "IIII" and then the different symbol for "V".
As it evolved, symbols were added for ten and fifty, one hundred and five hundred, and so
on.
This system of counting with ones and fives can be seen in the design of the abacus, old
and new.
In fact, this system even has a name which I'd never heard of -- "bi-quinary coded decimal".
Isn't that fun? I shall now attempt to drop that phrase into casual conversation wherever
possible. (And by the way, the little stones used as counters are called "calculi", whence the
name for the bane of high school students everywhere.)
Much later, in the 13th century, certain abbreviations were added -- substituting "IV" for "IIII"
and "IX" for "VIIII". This subtractive notation means that the order of the symbols becomes
important, something that is not required for a pure tally-based system.
These new requirements show us that nothing has changed in the development biz...
Pope: "We need to add subtractive notation ASAP -- the Arabs are beating us on features."
You: "But it's not backwards compatible, sir. It's a breaking change!"
Pope: "Tough. I need it by next week."
So now that we know all about Roman numerals, do we have enough information to create
the requirements?
Alas, no. As we investigate further, it becomes clear that there is a lot of inconsistency.
There is no ISO or ANSI standard for Roman numerals!
1718
This is not unusual of course. A fuzziness around requirements affects most software
projects. Indeed, part of our job as a developer is to help clarify things and eliminate
ambiguity. So, let's create some requirements based on what we know so far.
1719
In a functional programming language like F#, the most common approach is to return an
Option type, or to return a Success/Failure Choice type. Let's just use an Option , so to
1720
Second, I don't like testing individual cases. I'd prefer that my tests cover all inputs. This is
not always feasible, but when you can do it, as in this case, I think you should.
With this set of inputs, how confident are we that the code meets the requirements?
In a simple case like this, I might be reasonably confident, but this approach to testing
worries me because of the use of "magic" test inputs that are undocumented.
For example, why was 3497 plucked out of nowhere? Because (a) it is bigger than a
thousand and (b) it has some 4's and 9's in it. But the reason it was picked is not
documented in the test code.
Furthermore, if we compare this test suite with the requirements, we can see that the second
and third requirements are not explicitly tested for at all. True, the test with 3497 implicitly
checks the ordering requirement ("M" before "C" before "X"), but that is never made explicit.
Now compare that test with this one:
1721
[<Test>]
let ``For all valid inputs, there must be a max of four "I"s in a row``() =
for i in [1..4000] do
let roman = arabicToRoman i
roman |> assertMaxRepetition "I" 4
This test checks the requirement that you can only have four repetitions of "I".
Unlike the one in the TDD video, this test case covers all possible inputs, not just one. If it
passes, I will have complete confidence that the code meets this particular requirement.
Property-based testing
If you are not familiar with this approach to testing, it is called "property-based testing". You
define a "property" that must be true in general, and then you generate as many inputs as
possible in order to find cases where the property is not true.
In this case, we can test all 4000 inputs. In general though, our problems have a much larger
range of possible inputs, so we generally just test on some representative sample of the
inputs.
Most property-based testing tools are modelled after Haskell's QuickCheck, which is a tool
that automatically generates "interesting" inputs for you, in order to find edge cases as
quickly as possible. These inputs would include things like nulls, negative numbers, empty
lists, strings with non-ascii characters in them, and so on.
An equivalent to QuickCheck is available for most languages now, including FsCheck for F#.
The advantage of property-based testing is that it forces you to think about the requirements
in general terms, rather than as lots of special cases.
That is, rather than a test that says the input "4" maps to "IV" , we have a more general
test that says any input with 4 in the units place has "IV" as the last two characters .
1722
Or for example, let's say that I want to test the substitution rule for 40 => "XL".
// Define a property that should be true for all inputs
let ``if arabic has 4 tens then roman has one XL otherwise none`` arabic =
let roman = arabicToRoman arabic
let has4Tens = (arabic % 100 / 10) = 4
if has4Tens then
assertMaxOccurs "XL" 1 roman
else
assertMaxOccurs "XL" 0 roman
// Explicitly enumerate all inputs...
[<Test>]
let ``For all valid inputs, check the XL substitution``() =
for i in [1..4000] do
``if arabic has 4 tens then roman has one XL otherwise none`` i
// ...Or again use FsCheck to generate inputs for you
let isInRange i = (i >= 1) && (i <= 4000)
let prop i = isInRange i ==> ``if arabic has 4 tens then roman has one XL otherwise no
ne`` i
Check.Quick prop
I'm not going to go into property-based testing any more here, but I think you can see the
benefits over hand-crafted cases with magic inputs.
The code for this post has a full property-based test suite.
Here it is in action:
arabicToRoman 1 // "I"
arabicToRoman 5 // "IIIII"
arabicToRoman 10 // "IIIIIIIIII"
This code actually meets the first and second requirements already, and for all inputs!
Of course, having 4000 tally marks is not very helpful, which is no doubt why the Romans
started abbreviating them.
This is where insight into the domain comes in. If we understand that the tally marks are
being abbreviated, we can emulate that in our code.
So let's convert all runs of five tally marks into a "V".
1724
But now we can have runs of "V"s. Two "V"s need to be collapsed into an "X".
let arabicToRoman arabic =
(String.replicate arabic "I")
.Replace("IIIII","V")
.Replace("VV","X")
// test
arabicToRoman 1 // "I"
arabicToRoman 5 // "V"
arabicToRoman 6 // "VI"
arabicToRoman 10 // "X"
arabicToRoman 12 // "XII"
arabicToRoman 16 // "XVI"
And now we're done. We've met the first three requirements.
1725
If we want to add the optional abbreviations for the fours and nines, we can do that at the
end, after all the tally marks have been accumulated.
let arabicToRoman arabic =
(String.replicate arabic "I")
.Replace("IIIII","V")
.Replace("VV","X")
.Replace("XXXXX","L")
.Replace("LL","C")
.Replace("CCCCC","D")
.Replace("DD","M")
// optional substitutions
.Replace("IIII","IV")
.Replace("VIV","IX")
.Replace("XXXX","XL")
.Replace("LXL","XC")
.Replace("CCCC","CD")
.Replace("DCD","CM")
// test
arabicToRoman 1 // "I"
arabicToRoman 4 // "IV"
arabicToRoman 5 // "V"
arabicToRoman 6 // "VI"
arabicToRoman 10 // "X"
arabicToRoman 12 // "XII"
arabicToRoman 16 // "XVI"
arabicToRoman 40 // "XL"
arabicToRoman 946 // "CMXLVI"
arabicToRoman 3497 // "MMMCDXCVII"
1726
Yes, this might not be the most efficient code, creating strings with 4000 "I"s in them! And of
course, a more efficient approach would subtract the large tallies ("M", then "D", then "C")
straight from the input, leading to the recursive solution demonstrated in the TDD video.
But on the other hand, this implementation might well be efficient enough. The requirements
don't say anything about performance constraints -- YAGNI anyone? -- so I'm tempted to
leave it at this.
1727
Note that the above code does not produce the abbreviations for the four and nine cases.
We can easily modify it to do this though. We just need to pass in the symbol for ten, and
tweak the mapping for the 4 and 9 case, as follows:
let biQuinaryDigits place (unit,five,ten) arabic =
let digit = arabic % (10*place) / place
match digit with
| 0 -> ""
| 1 -> unit
| 2 -> unit + unit
| 3 -> unit + unit + unit
| 4 -> unit + five // changed to be one less than five
| 5 -> five
| 6 -> five + unit
| 7 -> five + unit + unit
| 8 -> five + unit + unit + unit
| 9 -> unit + ten // changed to be one less than ten
| _ -> failwith "Expected 0-9 only"
let arabicToRoman arabic =
let units = biQuinaryDigits 1 ("I","V","X") arabic
let tens = biQuinaryDigits 10 ("X","L","C") arabic
let hundreds = biQuinaryDigits 100 ("C","D","M") arabic
let thousands = biQuinaryDigits 1000 ("M","?","?") arabic
thousands + hundreds + tens + units
1728
Again, both these implementations are very straightforward and easy to verify. There are no
subtle edge cases lurking in the code.
Review
I started off this post being annoyed at a TDD demonstration. Let me review the reasons
why I was annoyed, and how my approach differs.
Requirements
The TDD demonstration video did not make any attempt to document the requirements at
all. I would say that this a dangerous thing to do, especially if you are learning.
I would prefer that before you start coding you always make an effort to be explicit about
what you are trying to do.
With only a tiny bit of effort I came up with some explicit requirements that I could use for
verification later.
I also explicitly documented the range of valid inputs -- something that was unfortunately
lacking in the TDD demonstration.
Understanding the domain
Even if the requirements have been made explicit for you, I think that it is always worthwhile
spending time to really understand the domain you are working in.
In this case, understanding that Roman numerals were a tally-based system helped with the
design later. (Plus I learned what "bi-quinary" means and got to use it in this post!)
Unit tests
The unit tests in the TDD demonstration were built one single case at a time. First zero, then
one, and so on.
As I note above, I feel very uncomfortable with this approach because (a) I don't think it
leads to a good design and (b) the single cases don't cover all possible inputs.
I would strongly recommend that you write tests that map directly to the requirements. If the
requirements are any good, this will mean that the tests cover many inputs at once, so you
can then test as many inputs as you can.
Ideally, you would use a property-based testing tool like QuickCheck. Not only does it make
this approach much easier to implement, but it forces you to identify what the properties of
your design should be, which in turn helps you clarify any fuzzy requirements.
1729
Implementation
Finally, I described two implementations, both completely different from the recursive one
demonstrated in the TDD video.
Both designs were derived directly from an understanding of the domain. The first from using
tally marks, and the second from using an abacus.
To my mind, both of these designs were also easier to understand -- no recursion! -- and
thus easier to have confidence in.
Summary
(Added based on comments I made below.)
Let me be clear that I have absolutely no problem with TDD. And I don't have a problem with
katas either.
But here's my concern about these kinds of "dive-in" demos, namely that novices and
learners might unintentionally learn the following (implicit) lessons:
It is OK to accept requirements as given without asking questions.
It is OK to work without a clear idea of the goal.
It is OK to start coding immediately.
It is OK to create tests that are extremely specific (e.g. with magic numbers).
It is OK to consider only the happy path.
It is OK to do micro refactoring without looking at the bigger picture.
Personally, I think that if you are practicing to be a professional developer, you should:
Practice asking for as much information as possible before you start coding.
Practice writing requirements (from unclear input) in such a way that they can be tested.
Practice thinking (analyzing and designing) rather than immediately coding.
Practice creating general tests rather than specific ones.
Practice thinking about and handling bad inputs, corner cases, and errors.
Practice major refactoring (rather than micro refactoring) so as to develop an intuition
about where the shearing layers should be.
These principles are all completely compatible with TDD (or at least the "London" school of
TDD) and programming katas. There is no conflict, and I cannot see why they would be
controversial.
I'm sure many of you will disagree with this post. I'm up for a (civilized) debate. Please leave
comments below or on Reddit.
If you'd like to see the complete code for this post, it is available as a gist here. The gist also
includes full property-based tests for both implementations.
1731
My development approach
My approach to software development is eclectic and pragmatic -- I like to mix different
techniques and alternate between top-down and bottom-up approaches.
1732
Typically I start with the requirements -- I'm a fan of requirements-driven design! Ideally, I
would aim to become an expert in the domain as well.
Next, I work on modelling the domain, using domain-driven design with a focus on domain
events ("event storming"), not just static data ("aggregates" in DDD terminology).
As part of the modelling process, I sketch a design using type-first development to create
types that represent both the domain data types ("nouns") and the domain activities
("verbs").
After doing a first draft of the domain model, I typically switch to a "bottom up" approach and
code a small prototype that exercises the model that I have defined so far.
Doing some real coding at this point acts as a reality check. It ensures that the domain
model actually makes sense and is not too abstract. And of course, it often drives more
questions about the requirements and domain model, so I go back to step 1, do some
refining and refactoring, and rinse and repeat until I am happy.
(Now if I was working with a team on a large project, at this point we could also start building
a real system incrementally and start on the user interface (e.g. with paper prototypes). Both
of these activities will typically generate yet more questions and changes in requirements
too, so the whole process is cyclical at all levels.)
So this would be my approach in a perfect world. In practice, of course, the world is not
perfect. There is bad management to contend with, a lack of requirements, silly deadlines
and more, all of which mean that I rarely get to use an ideal process.
But in this example, I'm the boss, so if I don't like the result, I've only myself to blame!
Getting started
So, let's get started. What should we do first?
Normally I would start with requirements. But do I really need to spend a lot of time writing
up requirements for a calculator?
I'm going to be lazy and say no. Instead I'm just to dive in -- I'm confident that I know how a
calculator works. (As you'll see later, I was wrong! Trying to write up the requirements would
have been a good exercise, as there are some interesting edge cases.)
So let's start with the type-first design instead.
In my designs, every use-case is a function, with one input and one output.
1733
For this example then, we need to model the public interface to the Calculator as a function.
Here's the signature:
type Calculate = CalculatorInput -> CalculatorOutput
That was easy! The first question then is: are there any other use-cases that we need to
model? I think for now, no. We'll just start with a single case that handles all the inputs.
1734
Ok, let's work on the CalculatorInput first. What would the structure of the input look like?
First, obviously, there will be some keystrokes, or some other way of communicating the
intent of the user. But also, since the calculator is stateless, we need to pass in some state
as well. This state would contain, for example, the digits typed in so far.
As to the output, the function will have to emit a new, updated state, of course.
But do we need anything else, such as a structure containing formatted output for display? I
don't think we do. We want to isolate ourselves from the display logic, so we'll just let the UI
turn the state into something that can be displayed.
What about errors? In other posts, I have spent a lot of time talking about error handling. Is it
needed in this case?
In this case, I think not. In a cheap pocket calculator, any errors are shown right in the
display, so we'll stick with that approach for now.
So here's the new version of the function:
type Calculate = CalculatorInput * CalculatorState -> CalculatorState
1735
CalculatorInput now means the keystrokes or whatever, and CalculatorState is the state.
Notice that I have defined this function using a tuple ( CalculatorInput * CalculatorState ) as
input, rather than as two separate parameters (which would look like CalculatorInput ->
CalculatorState -> CalculatorState ). I did this because both parameters are always needed
and a tuple makes this clear -- I don't want to be partially applying the input, for example.
In fact I do this for all functions when doing type-first design. Every function has one input
and one output. This doesn't mean that there might not be potential for doing partial
application later, just that, at the design stage, I only want one parameter.
Also note that things that are not part of the pure domain (such as configuration and
connection strings) will never be shown at this stage, although, at implementation time, they
will of course be added to the functions that implement the design.
I've defined a type CalculatorDisplay , firstly as documentation to make it clear what the
field value is used for, and secondly, so I can postpone deciding what the display actually is!
So what should the type of the display be? A float? A string? A list of characters? A record
with multiple fields?
Well, I'm going to go for string , because, as I said above, we might need to display errors.
type Calculate = CalculatorInput * CalculatorState -> CalculatorState
and CalculatorState = {
display: CalculatorDisplay
}
and CalculatorDisplay = string
Notice that I am using and to connect the type definitions together. Why?
Well, F# compiles from top to bottom, so you must define a type before it is used. The
following code will not compile:
1736
I could fix this by changing the order of the declarations, but since I am in "sketch" mode,
and I don't want to reorder things all the time, I will just append new declarations to the
bottom and use and to connect them.
In the final production code though, when the design has stabilized, I would reorder these
types to avoid using and . The reason is that and can hide cycles between types and
prevent refactoring.
Some people might say: why not use a char as the input? But as I explained above, in my
domain I only want to deal with ideal data. By using a limited set of choices like this, I never
have to deal with unexpected input.
Also, a side benefit of using abstract types rather than chars is that DecimalSeparator is not
assumed to be ".". The actual separator should be obtained by first getting the current
culture ( System.Globalization.CultureInfo.CurrentCulture ) and then using
CurrentCulture.NumberFormat.CurrencyDecimalSeparator to get the separator. By hiding this
implementation detail from the design, changing the actual separator used will have minimal
effect on the code.
1737
The CalculatorDisplay type is the one we defined earlier, but what is this new
CalculatorDigit type?
Well obviously we need some type to represent all the possible digits that can be used as
input. Other inputs, such as Add and Clear , would not be valid for this function.
type CalculatorDigit =
| Zero | One | Two | Three | Four
| Five | Six | Seven | Eight | Nine
| DecimalSeparator
So the next question is, how do we get a value of this type? Do we need a function that
maps a CalculatorInput to a CalculatorDigit type, like this?
let convertInputToDigit (input:CalculatorInput) =
match input with
| Zero -> CalculatorDigit.Zero
| One -> CalculatorDigit.One
| etc
| Add -> ???
| Clear -> ???
In many situations, this might be necessary, but in this case it seems like overkill. And also,
how would this function deal with non-digits such as Add and Clear ?
So let's just redefine the CalculatorInput type to use the new type directly:
type CalculatorInput =
| Digit of CalculatorDigit
| Add | Subtract | Multiply | Divide
| Equals | Clear
1738
Here's the complete refactored design with new types CalculatorDigit , CalculatorMathOp
and CalculatorAction :
type Calculate = CalculatorInput * CalculatorState -> CalculatorState
and CalculatorState = {
display: CalculatorDisplay
}
and CalculatorDisplay = string
and CalculatorInput =
| Digit of CalculatorDigit
| Op of CalculatorMathOp
| Action of CalculatorAction
and CalculatorDigit =
| Zero | One | Two | Three | Four
| Five | Six | Seven | Eight | Nine
| DecimalSeparator
and CalculatorMathOp =
| Add | Subtract | Multiply | Divide
and CalculatorAction =
| Equals | Clear
type UpdateDisplayFromDigit = CalculatorDigit * CalculatorDisplay -> CalculatorDisplay
This is not the only approach. I could have easily left Equals and Clear as separate
choices.
Now let's revisit UpdateDisplayFromDigit again. Do we need any other parameters? For
example, do we need any other part of the state?
No, I can't think of anything else. When defining these functions, I want to be as minimal as
possible. Why pass in the whole calculator state if you only need the display?
Also, would UpdateDisplayFromDigit ever return an error? For example, surely we can't add
digits indefinitely -- what happens when we are not allowed to? And is there some other
combination of inputs that might cause an error? For example, inputting nothing but decimal
separators! What happens then?
For this little project, I will assume that neither of these will create an explicit error, but
instead, bad input will be rejected silently. In other words, after 10 digits, say, other digits will
be ignored. And after the first decimal separator, subsequent ones will be ignored as well.
Alas, I cannot encode these requirements in the design. But that fact that
UpdateDisplayFromDigit does not return any explicit error type does at least tell me that
If there were unary operations as well, such as 1/x , we would need a different type for
those, but we don't, so we can keep things simple.
Next decision: what numeric type should we use? Should we make it generic?
Again, let's just keep it simple and use float . But we'll keep the Number alias around to
decouple the representation a bit. Here's the updated code:
type DoMathOperation = CalculatorMathOp * Number * Number -> Number
and Number = float
1740
We could have also used the built-in generic Choice type, or even a full "railway oriented
programming" approach, but since this is a sketch of the design, I want the design to stand
alone, without a lot of dependencies, so I'll just define the specific type right here.
Any other errors? NaNs or underflows or overflows? I'm not sure. We have the
MathOperationError type, and it would be easy to extend it as needed.
Thinking about it though, the function could fail, because the display string could be "error"
or something. So let's return an option instead.
type GetDisplayNumber = CalculatorDisplay -> Number option
Similarly, when we do have a successful result, we will want to display it, so we need a
function that works in the other direction:
type SetDisplayNumber = Number -> CalculatorDisplay
This function can never error (I hope), so we don't need the option .
1741
But this is wrong too! Can we have a pendingOp without a pendingNumber , or vice versa?
No. They live and die together.
This implies that the state should contain a pair, and the whole pair is optional, like this:
and CalculatorState = {
display: CalculatorDisplay
pendingOp: (CalculatorMathOp * Number) option
}
But now we are still missing a piece. If the operation is added to the state as pending, when
does the operation actually get run and the result displayed?
Answer: when the Equals button is pushed, or indeed any another math op button. We'll
deal with that later.
Well, it obviously just resets the state so that the display is empty and any pending
operations are removed.
I'm going to call this function InitState rather than "clear", and here is its signature:
type InitState = unit -> CalculatorState
We've created a set of services that can be injected into an implementation of the
Calculate function. With these in place, we can code the Calculate function immediately
1743
Review
So let's review -- with the addition of the services, our initial design is complete. Here is all
the code so far:
type Calculate = CalculatorInput * CalculatorState -> CalculatorState
and CalculatorState = {
display: CalculatorDisplay
pendingOp: (CalculatorMathOp * Number) option
}
and CalculatorDisplay = string
and CalculatorInput =
| Digit of CalculatorDigit
| Op of CalculatorMathOp
| Action of CalculatorAction
and CalculatorDigit =
| Zero | One | Two | Three | Four
| Five | Six | Seven | Eight | Nine
| DecimalSeparator
and CalculatorMathOp =
| Add | Subtract | Multiply | Divide
and CalculatorAction =
| Equals | Clear
and UpdateDisplayFromDigit =
CalculatorDigit * CalculatorDisplay -> CalculatorDisplay
and DoMathOperation =
CalculatorMathOp * Number * Number -> MathOperationResult
and Number = float
and MathOperationResult =
| Success of Number
| Failure of MathOperationError
and MathOperationError =
| DivideByZero
type GetDisplayNumber =
CalculatorDisplay -> Number option
type SetDisplayNumber =
Number -> CalculatorDisplay
type InitState =
unit -> CalculatorState
type CalculatorServices = {
updateDisplayFromDigit: UpdateDisplayFromDigit
doMathOperation: DoMathOperation
getDisplayNumber: GetDisplayNumber
setDisplayNumber: SetDisplayNumber
initState: InitState
}
1744
Summary
I think that this is quite nice. We haven't written any "real" code yet, but with a bit of thought,
we have already built quite a detailed design.
In the next post, I'll put this design to the test by attempting to create an implementation.
The code for this post is available in this gist on GitHub.
1745
In the previous post, we completed a first draft of the design, using only types (no UML
diagrams!).
Now it's time to create a trial implementation that uses the design.
Doing some real coding at this point acts as a reality check. It ensures that the domain
model actually makes sense and is not too abstract. And of course, it often drives more
questions about the requirements and domain model.
First implementation
So let's try implementing the main calculator function, and see how we do.
First, we can immediately create a skeleton that matches each kind of input and processes it
accordingly.
1746
You can see that this skeleton has a case for each type of input to handle it appropriately.
Note that in all cases, a new state is returned.
This style of writing a function might look strange though. Let's look at it a bit more closely.
First, we can see that createCalculate is the not the calculator function itself, but a function
that returns another function. The returned function is a value of type Calculate -- that's
what the :Calculate at the end means.
Here's just the top part:
let createCalculate (services:CalculatorServices) :Calculate =
fun (input,state) ->
match input with
// code
Since it is returning a function, I chose to write it using a lambda. That's what the fun
(input,state) -> is for.
But I could have also written it using an inner function, like this
let createCalculate (services:CalculatorServices) :Calculate =
let innerCalculate (input,state) =
match input with
// code
innerCalculate // return the inner function
1747
Note that I'm creating a newState value from the result of updateDisplayFromDigit and then
returning it as a separate step.
I could have done the same thing in one step, without an explicit newState value, as shown
below:
1748
Neither approach is automatically best. I would pick one or the other depending on the
context.
For simple cases, I would avoid the extra line as being unnecessary, but sometimes having
an explicit return value is more readable. The name of the value tells you an indication of the
return type, and it gives you something to watch in the debugger, if you need to.
Alright, let's implement updateDisplayFromDigit now. It's pretty straightforward.
first use the updateDisplayFromDigit in the services to actually update the display
then create a new state from the new display and return it.
let updateDisplayFromDigit services digit state =
let newDisplay = services.updateDisplayFromDigit (digit,state.display)
let newState = {state with display=newDisplay}
newState //return
For Clear , just init the state, using the provided initState service.
For Equals , we check if there is a pending math op. If there is, run it and update the
display, otherwise do nothing. We'll put that logic in a helper function called
updateDisplayFromPendingOp .
1749
Now to updateDisplayFromPendingOp . I spent a few minutes thinking about, and I've come up
with the following algorithm for updating the display:
First, check if there is any pending op. If not, then do nothing.
Next, try to get the current number from the display. If you can't, then do nothing.
Next, run the op with the pending number and the current number from the display. If
you get an error, then do nothing.
Finally, update the display with the result and return a new state.
The new state also has the pending op set to None , as it has been processed.
And here's what that logic looks like in imperative style code:
// First version of updateDisplayFromPendingOp
// * very imperative and ugly
let updateDisplayFromPendingOp services state =
if state.pendingOp.IsSome then
let op,pendingNumber = state.pendingOp.Value
let currentNumberOpt = services.getDisplayNumber state.display
if currentNumberOpt.IsSome then
let currentNumber = currentNumberOpt.Value
let result = services.doMathOperation (op,pendingNumber,currentNumber)
match result with
| Success resultNumber ->
let newDisplay = services.setDisplayNumber resultNumber
let newState = {display=newDisplay; pendingOp=None}
newState //return
| Failure error ->
state // original state is untouched
else
state // original state is untouched
else
state // original state is untouched
1750
That code does follow the algorithm exactly, but is really ugly and also error prone (using
.Value on an option is a code smell).
On the plus side, we did make extensive use of our "services", which has isolated us from
the actual implementation details.
So, how can we rewrite it to be more functional?
But remember that the function has to return a state. If the overall result of the bind is None ,
then we have not created a new state, and we must return the original state that was passed
in.
This can be done with the built-in defaultArg function which, when applied to an option,
returns the option's value if present, or the second parameter if None .
let updateDisplayFromPendingOp services state =
let result =
state.pendingOp
|> Option.bind ???
defaultArg result state
You can also tidy this up a bit as well by piping the result directly into defaultArg , like this:
let updateDisplayFromPendingOp services state =
state.pendingOp
|> Option.bind ???
|> defaultArg <| state
1751
I admit that the reverse pipe for state looks strange -- it's definitely an acquired taste!
Onwards! Now what about the parameter to bind ? When this is called, we know that
pendingOp is present, so we can write a lambda with those parameters, like this:
let result =
state.pendingOp
|> Option.bind (fun (op,pendingNumber) ->
let currentNumberOpt = services.getDisplayNumber state.display
// code
)
Alternatively, we could create a local helper function instead, and connect it to the bind, like
this:
let executeOp (op,pendingNumber) =
let currentNumberOpt = services.getDisplayNumber state.display
/// etc
let result =
state.pendingOp
|> Option.bind executeOp
I myself generally prefer the second approach when the logic is complicated, as it allows a
chain of binds to be simple. That is, I try to make my code look like:
let doSomething input = return an output option
let doSomethingElse input = return an output option
let doAThirdThing input = return an output option
state.pendingOp
|> Option.bind doSomething
|> Option.bind doSomethingElse
|> Option.bind doAThirdThing
Note that in this approach, each helper function has a non-option for input but always must
output an option.
1752
Rather than having a lot of logic, I'm going keep the helper function ( getCurrentNumber )
simple.
The input is the pair (op,pendingNumber)
The output is the triple (op,pendingNumber,currentNumber) if currentNumber is Some ,
otherwise None .
In other words, the signature of getCurrentNumber will be pair -> triple option , so we can
be sure that is usable with the Option.bind function.
How to convert the pair into the triple? This can be done just by using Option.map to convert
the currentNumber option to a triple option. If the currentNumber is Some , then the output of
the map is Some triple . On the other hand, if the currentNumber is None , then the output
of the map is None also.
let getCurrentNumber (op,pendingNumber) =
let currentNumberOpt = services.getDisplayNumber state.display
currentNumberOpt
|> Option.map (fun currentNumber -> (op,pendingNumber,currentNumber))
let result =
state.pendingOp
|> Option.bind getCurrentNumber
|> Option.bind ???
Now that we have a triple with valid values, we have everything we need to write a helper
function for the math operation.
It takes a triple as input (the output of getCurrentNumber )
It does the math operation
It then pattern matches the Success/Failure result and outputs the new state if
applicable.
1753
Note that, unlike the earlier version with nested ifs, this version returns Some on success
and None on failure.
Displaying errors
Writing the code for the Failure case made me realize something. If there is a failure, we
are not displaying it at all, just leaving the display alone. Shouldn't we show an error or
something?
Hey, we just found a requirement that got overlooked! This is why I like to create an
implementation of the design as soon as possible. Writing real code that deals with all the
cases will invariably trigger a few "what happens in this case?" moments.
So how are we going to implement this new requirement?
In order to do this, we'll need a new "service" that accepts a MathOperationError and
generates a CalculatorDisplay .
type SetDisplayError = MathOperationError -> CalculatorDisplay
doMathOp can now be altered to use the new service. Both Success and Failure cases
1754
I'm going to leave the Some in the result, so we can stay with Option.bind in the result
pipeline*.
* An alternative would be to not return Some , and then use Option.map in the result pipeline
Putting it all together, we have the final version of updateDisplayFromPendingOp . Note that
I've also added a ifNone helper that makes defaultArg better for piping.
1755
But F# allows you to hide the complexity in a different way, by creating computation
expressions.
Since we are dealing with Options, we can create a "maybe" computation expression that
allows clean handling of options. (If we were dealing with other types, we would need to
create a different computation expression for each type).
Here's the definition -- only four lines!
1756
type MaybeBuilder() =
member this.Bind(x, f) = Option.bind f x
member this.Return(x) = Some x
let maybe = new MaybeBuilder()
With this computation expression available, we can use maybe instead of bind, and our
code would look something like this:
let doSomething input = return an output option
let doSomethingElse input = return an output option
let doAThirdThing input = return an output option
let finalResult = maybe {
let! result1 = doSomething
let! result2 = doSomethingElse result1
let! result3 = doAThirdThing result2
return result3
}
In our case, then we can write yet another version of updateDisplayFromPendingOp -- our
fourth!
// Fourth version of updateDisplayFromPendingOp
// * Changed to use "maybe" computation expression
let updateDisplayFromPendingOp services state =
// helper to do the math op
let doMathOp (op,pendingNumber,currentNumber) =
let result = services.doMathOperation (op,pendingNumber,currentNumber)
let newDisplay =
match result with
| Success resultNumber ->
services.setDisplayNumber resultNumber
| Failure error ->
services.setDisplayError error
{display=newDisplay;pendingOp=None}
// fetch the two options and combine them
let newState = maybe {
let! (op,pendingNumber) = state.pendingOp
let! currentNumber = services.getDisplayNumber state.display
return doMathOp (op,pendingNumber,currentNumber)
}
newState |> ifNone state
1757
Note that in this implementation, I don't need the getCurrentNumber helper any more, as I
can just call services.getDisplayNumber directly.
So, which of these variants do I prefer?
It depends.
If there is a very strong "pipeline" feel, as in the ROP approach, then I prefer using an
explicit bind .
On the other hand, if I am pulling options from many different places, and I want to
combine them in various ways, the maybe computation expression makes it easier.
So, in this case, I'll go for the last implementation, using maybe .
well.
For the math operation case, then, there will be two state transformations, and
createCalculate will look like this:
1758
Again, we can make this more functional using exactly the same techniques we used for
updateDisplayFromPendingOp .
So here's the more idiomatic version using Option.map and a newStateWithPending helper
function:
// Second version of addPendingMathOp
// * Uses "map" and helper function
let addPendingMathOp services op state =
let newStateWithPending currentNumber =
let pendingOp = Some (op,currentNumber)
{state with pendingOp=pendingOp}
state.display
|> services.getDisplayNumber
|> Option.map newStateWithPending
|> ifNone state
As before, I'd probably go for the last implementation using maybe . But the Option.map one
is fine too.
1759
Implementation: review
Now we're done with the implementation part. Let's review the code:
1760
1761
Summary
We have proved that our design is reasonable by making an implementation -- plus we
found a missed requirement.
In the next post, we'll implement the services and the user interface to create a complete
application.
The code for this post is available in this gist on GitHub.
1762
1763
module CalculatorServices =
open CalculatorDomain
open CalculatorConfiguration
let updateDisplayFromDigit (config:Configuration) :UpdateDisplayFromDigit =
fun (digit, display) ->
// determine what character should be appended to the display
let appendCh=
match digit with
| Zero ->
// only allow one 0 at start of display
if display="0" then "" else "0"
| One -> "1"
| Two -> "2"
| Three-> "3"
| Four -> "4"
| Five -> "5"
| Six-> "6"
| Seven-> "7"
| Eight-> "8"
| Nine-> "9"
| DecimalSeparator ->
if display="" then
// handle empty display with special case
"0" + config.decimalSeparator
else if display.Contains(config.decimalSeparator) then
// don't allow two decimal separators
""
else
config.decimalSeparator
// ignore new input if there are too many digits
if (display.Length > config.maxDisplayLength) then
display // ignore new input
else
// append the new char
display + appendCh
let getDisplayNumber :GetDisplayNumber = fun display ->
match System.Double.TryParse display with
| true, d -> Some d
| false, _ -> None
let setDisplayNumber :SetDisplayNumber = fun f ->
sprintf "%g" f
let setDisplayError divideByZeroMsg :SetDisplayError = fun f ->
match f with
| DivideByZero -> divideByZeroMsg
let doMathOperation :DoMathOperation = fun (op,f1,f2) ->
match op with
1764
Some comments:
I have created a configuration record that stores properties that are used to
parameterize the services, such as the decimal separator.
The configuration record is passed into the createServices function, which in turn
passes the configuration on those services that need it.
All the functions use the same approach of returning one of the types defined in the
design, such as UpdateDisplayFromDigit or DoMathOperation .
There are only a few tricky edge cases, such as trapping exceptions in division, or
preventing more than one decimal separator being appended.
1765
I won't show all the code, as it is about 200 lines (and you can see it in the gist), but here are
some highlights:
module CalculatorUI =
open CalculatorDomain
type CalculatorForm(initState:InitState, calculate:Calculate) as this =
inherit Form()
// initialization before constructor
let mutable state = initState()
let mutable setDisplayedText =
fun text -> () // do nothing
and then set it to an actual control value when the form has been initialized:
member this.CreateDisplayLabel() =
let display = new Label(Text="",Size=displaySize,Location=getPos(0,0))
display.TextAlign <- ContentAlignment.MiddleRight
display.BackColor <- Color.White
this.Controls.Add(display)
// traditional style - set the field when the form has been initialized
displayControl <- display
1766
But this has the problem that you might accidentally try to access the label control before it is
initialized, causing a NRE. Also, I'd prefer to focus on the desired behavior, rather than
having a "global" field that can be accessed by anyone anywhere.
By using a function, we (a) encapsulate the access to the real control and (b) avoid any
possibility of a null reference.
The mutable function starts off with a safe default implementation ( fun text -> () ), and is
then changed to a new implementation when the label control is created:
member this.CreateDisplayLabel() =
let display = new Label(Text="",Size=displaySize,Location=getPos(0,0))
this.Controls.Add(display)
// update the function that sets the text
setDisplayedText < (fun text -> display.Text <- text)
1767
And since all the digit buttons have the same behavior, as do all the math op buttons, I just
created some helpers that set the event handler in a generic way:
let addDigitButton digit (button:Button) =
button.Click.AddHandler(EventHandler(fun _ _ -> handleDigit digit))
this.Controls.Add(button)
let addOpButton op (button:Button) =
button.Click.AddHandler(EventHandler(fun _ _ -> handleOp op))
this.Controls.Add(button)
Button clicks and keyboard presses are eventually routed into the key function handleInput ,
which does the calculation.
let handleInput input =
let newState = calculate(input,state)
state <- newState
setDisplayedText state.display
let handleDigit digit =
Digit digit |> handleInput
let handleOp op =
Op op |> handleInput
As you can see, the implementation of handleInput is trivial. It calls the calculation function
that was injected, sets the mutable state to the result, and then updates the display.
So there you have it -- a complete calculator!
Let's try it now -- get the code from this gist and try running it as a F# script.
Disaster strikes!
1768
Let's start with a simple test. Try entering 1 Add 2 Equals . What would you expect?
I don't know about you, but what I wouldn't expect is that the calculator display shows 12 !
What's going on? Some quick experimenting shows that I have forgotten something really
important -- when an Add or Equals operation happens, any subsequent digits should not
be added to the current buffer, but instead start a new one. Oh no! We've got a showstopper
bug!
Remind me again, what idiot said "if it compiles, it probably works".*
* Actually, that idiot would be me (among many others).
So what went wrong then?
Well the code did compile, but it didn't work as expected, not because the code was buggy,
but because my design was flawed.
In other words, the use of the types from the type-first design process means that I do have
high confidence that the code I wrote is a correct implementation of the design. But if the
requirements and design are wrong, all the correct code in the world can't fix that.
We'll revisit the requirements in the next post, but meanwhile, is there a patch we can make
that will fix the problem?
(This might seem like a good solution for now, but using flags like this is really a design
smell. In the next post, I'll use a different approach which doesn't involve flags)
1769
1770
You get strange behaviors if you enter operations in unusual orders. For example,
entering 2 + + - shows 8 on the display!
So obviously, this code is not yet fit for purpose.
1771
So the real question is, would test-driven development help us find missing requirements or
subtle edge cases? Not necessarily. Test-driven development will only be effective if we can
think of every possible case that could happen in the first place. In that sense, TDD would
not make up for a lack of imagination!
And if do have good requirements, then hopefully we can design the types to make illegal
states unrepresentable and then we won't need the tests to provide correctness guarantees.
Now I'm not saying that I am against automated testing. In fact, I do use it all the time to
verify certain requirements, and especially for integration and testing in the large.
So, for example, here is how I might test this code:
1772
module CalculatorTests =
open CalculatorDomain
open System
let config = CalculatorConfiguration.loadConfig()
let services = CalculatorServices.createServices config
let calculate = CalculatorImplementation.createCalculate services
let emptyState = services.initState()
/// Given a sequence of inputs, start with the empty state
/// and apply each input in turn. The final state is returned
let processInputs inputs =
// helper for fold
let folder state input =
calculate(input,state)
inputs
|> List.fold folder emptyState
/// Check that the state contains the expected display value
let assertResult testLabel expected state =
let actual = state.display
if (expected <> actual) then
printfn "Test %s failed: expected=%s actual=%s" testLabel expected actual
else
printfn "Test %s passed" testLabel
let ``when I input 1 + 2, I expect 3``() =
[Digit One; Op Add; Digit Two; Action Equals]
|> processInputs
|> assertResult "1+2=3" "3"
let ``when I input 1 + 2 + 3, I expect 6``() =
[Digit One; Op Add; Digit Two; Op Add; Digit Three; Action Equals]
|> processInputs
|> assertResult "1+2+3=6" "6"
// run tests
do
``when I input 1 + 2, I expect 3``()
``when I input 1 + 2 + 3, I expect 6``()
1773
I messed up! As I said earlier, the implementation itself was not the problem. I think the typefirst design process worked. The real problem was that I was too hasty and just dived into
the design without really understanding the requirements.
How can I prevent this from happening again next time?
One obvious solution would be to switch to a proper TDD approach. But I'm going to be a bit
stubborn, and see if I can stay with a type-first design!
In the next post, I will stop being so ad-hoc and over-confident, and instead use a process
that is more thorough and much more likely to prevent these kinds of errors at the design
stage.
The code for this post is available on GitHub in this gist (unpatched) and this gist (patched).
1774
1775
let appendCh=
match digit with
| Zero ->
// only allow one 0 at start of display
if display="0" then "" else "0"
| One -> "1"
| // snip
| DecimalSeparator ->
if display="" then
// handle empty display with special case
"0" + config.decimalSeparator
else if display.Contains(config.decimalSeparator) then
// don't allow two decimal separators
""
else
config.decimalSeparator
This makes me think that these inputs should be treated as different in the design itself and
not hidden in the implementation -- after all we want the design to also act as documentation
as much as possible.
1776
It is a design tool that forces you to think about every possibility that could occur. A
common cause of errors is that certain edge cases are not handled, but a state machine
forces all cases to be thought about.
In this case, in addition to the most obvious bug, there are still some edge cases that are not
dealt with properly, such as immediately following a math operation with another math
operation. What should happen then?
So what is the best way to implement these simple state machines in F#?
Now, designing and implementing FSMs is a complex topic in in own right, with its own
terminology (NFAs and DFAs, Moore vs. Mealy, etc), and whole businesses built around it.
In F#, there are a number of possible approaches, such as table driven, or mutually
recursive functions, or agents, or OO-style subclasses, etc.
But my preferred approach (for an ad-hoc manual implementation) makes extensive use of
union types and pattern matching.
1777
First, create a union type that represents all the states. For example, if there are three states
called "A", "B" and "C", the type would look like this:
type State =
| AState
| BState
| CState
In many cases, each state will need to store some data that is relevant to that state. So we
will need to create types to hold that data as well.
type State =
| AState of AStateData
| BState of BStateData
| CState
and AStateData =
{something:int}
and BStateData =
{somethingElse:int}
Next, all possible events that can happen are defined in another union type. If events have
data associated with them, add that as well.
type InputEvent =
| XEvent
| YEvent of YEventData
| ZEvent
and YEventData =
{eventData:string}
Finally, we can create a "transition" function that, given a current state and input event,
returns a new state.
let transition (currentState,inputEvent) =
match currentState,inputEvent with
| AState, XEvent -> // new state
| AState, YEvent -> // new state
| AState, ZEvent -> // new state
| BState, XEvent -> // new state
| BState, YEvent -> // new state
| CState, XEvent -> // new state
| CState, ZEvent -> // new state
1778
What I like about this approach in a language with pattern matching, like F#, is that if we
forget to handle a particular combination of state and event, we get a compiler
warning. How awesome is that?
It's true that, for systems with many states and input events, it may be unreasonable to
expect every possible combination to be explicitly handled. But in my experience, many
nasty bugs are caused by processing an event when you shouldn't, exactly as we saw with
the original design accumulating digits when it shouldn't have.
Forcing yourself to consider every possible combination is thus a helpful design practice.
Now, even with a small number of states and events, the number of possible combinations
gets large very quickly. To make it more manageable in practice, I typically create a series of
helper functions, one for each state, like this:
let aStateHandler stateData inputEvent =
match inputEvent with
| XEvent -> // new state
| YEvent _ -> // new state
| ZEvent -> // new state
let bStateHandler stateData inputEvent =
match inputEvent with
| XEvent -> // new state
| YEvent _ -> // new state
| ZEvent -> // new state
let cStateHandler inputEvent =
match inputEvent with
| XEvent -> // new state
| YEvent _ -> // new state
| ZEvent -> // new state
let transition (currentState,inputEvent) =
match currentState with
| AState stateData ->
// new state
aStateHandler stateData inputEvent
| BState stateData ->
// new state
bStateHandler stateData inputEvent
| CState ->
// new state
cStateHandler inputEvent
So let's try this approach and attempt to implement the state diagram above:
1779
(near cStateHandler) Incomplete pattern matches on this expression. For example, the
value 'YEvent (_)' may indicate a case not covered by the pattern(s).
This is really helpful. It means we have missed some edge cases and we should change our
code to handle these events.
1780
By the way, please do not fix the code with a wildcard match (underscore)! That defeats the
purpose. If you want to ignore an event, do it explicitly.
Here's the fixed up code, which compiles without warnings:
let bStateHandler stateData inputEvent =
match inputEvent with
| XEvent
| ZEvent ->
// stay in B state
BState stateData
| YEvent _ ->
// transition to C state
CState
let cStateHandler inputEvent =
match inputEvent with
| XEvent
| YEvent _ ->
// stay in C state
CState
| ZEvent ->
// transition to B state
BState {somethingElse=42}
You can see the code for this example in this gist.
1781
Each state is a box, and the events that trigger transitions (such as a digit or math operation
or Equals ) are in red.
If we follow through a sequence of events for something like 1 Add 2 Equals , you can
see that we'll end up at the "Show result" state at the bottom.
But remember that we wanted to raise the handling of zero and decimal separators up to the
design level?
So let's create special events for those inputs, and a new state "accumulate with decimal"
that ignores subsequent decimal separators.
Here's version 2:
1782
1783
I'm only showing the key transitions -- it would be too overwhelming to show all of them. But
it does give us enough information to get started on the detailed requirements.
As we can see, there are five states:
ZeroState
AccumulatorState
AccumulatorDecimalState
ComputedState
ErrorState
And there are six possible inputs:
Zero
NonZeroDigit
DecimalSeparator
MathOp
Equals
Clear
Let's document each state, and what data it needs to store, if any.
1784
State
Special behavior?
ZeroState
(optional) pending op
AccumulatorState
AccumulatorDecimalState
ComputedState
ErrorState
Error message
Action
New State
Zero
(ignore)
ZeroState
NonZeroDigit
AccumulatorState
DecimalSeparator
AccumulatorDecimalState
MathOp
ComputedState
Equals
ComputedState
Clear
(ignore)
ZeroState
We can repeat the process with the AccumulatorState state. Here are the transitions for
each type of input:
1785
Input
Action
New State
Zero
AccumulatorState
NonZeroDigit
AccumulatorState
DecimalSeparator
AccumulatorDecimalState
MathOp
ComputedState
Equals
ComputedState
Clear
ZeroState
The event handling for AccumulatorDecimalState state is the same, except that
DecimalSeparator is ignored.
What about the ComputedState state. Here are the transitions for each type of input:
Input
Action
New State
Zero
ZeroState
NonZeroDigit
AccumulatorState
DecimalSeparator
AccumulatorDecimalState
MathOp
ComputedState
Equals
ComputedState
Clear
ZeroState
1786
Input
Action
New
State
Zero, NonZeroDigit,
DecimalSeparator
MathOp, Equals
(ignore)
ErrorState
Clear
ZeroState
If we compare these types to the first design (below), we have now made it clear that there
is something special about Zero and DecimalSeparator , as they have been promoted to
first class citizens of the input type.
1787
Also, in the old design, we had a single state type (below) that stored data for all contexts,
while in the new design, the state is explicitly different for each context. The types
ZeroStateData , AccumulatorStateData , ComputedStateData , and ErrorStateData make this
obvious.
// from the old design
type CalculatorState = {
display: CalculatorDisplay
pendingOp: (CalculatorMathOp * Number) option
}
// from the new design
type CalculatorState =
| ZeroState of ZeroStateData
| AccumulatorState of AccumulatorStateData
| AccumulatorWithDecimalState of AccumulatorStateData
| ComputedState of ComputedStateData
| ErrorState of ErrorStateData
Now that we have the basics of the new design, we need to define the other types
referenced by it:
1788
Note that because the state is much more complicated, I've added helper function
getDisplayFromState that extracts the display text from the state. This helper function will be
used the UI or other clients (such as tests) that need to get the text to display.
I've also added a getPendingOpFromState , so that we can show the pending state in the UI
as well.
As you can see, it passes the responsibility to a number of handlers, one for each state,
which will be discussed below.
But before we do that, I thought it might be instructive to compare the new state-machine
based design with the (buggy!) one I did previously.
Here is the code from the previous one:
let createCalculate (services:CalculatorServices) :Calculate =
fun (input,state) ->
match input with
| Digit d ->
let newState = updateDisplayFromDigit services d state
newState //return
| Op op ->
let newState1 = updateDisplayFromPendingOp services state
let newState2 = addPendingMathOp services op newState1
newState2 //return
| Action Clear ->
let newState = services.initState()
newState //return
| Action Equals ->
let newState = updateDisplayFromPendingOp services state
newState //return
1790
If we compare the two implementations, we can see that there has been a shift of emphasis
from events to state. You can see this by comparing how main pattern matching is done in
the two implementations:
In the original version, the focus was on the input, and the state was secondary.
In the new version, the focus is on the state, and the input is secondary.
The focus on input over state, ignoring the context, is why the old version was such a bad
design.
To repeat what I said above, many nasty bugs are caused by processing an event when you
shouldn't (as we saw with the original design). I feel much more confident in the new design
because of the explicit emphasis on state and context from the very beginning.
In fact, I'm not alone in noticing these kinds of issues. Many people think that classic "eventdriven programming" is flawed and recommend a more "state driven approach" (e.g. here
and here), just as I have done here.
1791
Again, the real work is done in helper functions such as accumulateNonZeroDigit and
getComputationState . We'll look at those in a minute.
1792
1793
The getComputationState helper is much more complex -- the most complex function in the
entire code base, I should think.
It's very similar to the updateDisplayFromPendingOp that we implemented before, but there
are a couple of changes:
The services.getNumberFromAccumulator code can never fail, because of the statebased approach. That makes life simpler!
The match result with Success/Failure code now returns two possible states:
ComputedState or ErrorState .
If there is no pending op, we still need to return a valid ComputedState , which is what
computeStateWithNoPendingOp does.
1794
Finally, we have a new piece of code that wasn't in the previous implementation at all!
What do you do when you get two math ops in a row? We just replace the old pending op (if
any) with the new one (if any).
let replacePendingOp (computedStateData:ComputedStateData) nextOp =
let newPending = maybe {
let! existing,displayNumber = computedStateData.pendingOp
let! next = nextOp
return next,displayNumber
}
{computedStateData with pendingOp=newPending}
|> ComputedState
1795
To complete the application, we just need to implement the services and the UI, in the same
way as we did before.
As it happens, we can reuse almost all of the previous code. The only thing that has really
changed is the way that the input events are structured, which affects how the button
handlers are created.
You can get the code for the state machine version of the calculator here.
If you try it out the new code, I think that you will find that it works first time, and feels much
more robust. Another win for state-machine driven design!
Exercises
If you liked this design, and want to work on something similar, here are some exercises that
you could do:
First, you could add some other operations. What would you have to change to
implement unary ops such as 1/x and sqrt ?
Some calculators have a back button. What would you have to do to implement this?
Luckily all the data structures are immutable, so it should be easy!
Most calculators have a one-slot memory with store and recall. What would you have to
change to implement this?
The logic that says that there are only 10 chars allowed on the display is still hidden
from the design. How would you make this visible?
Summary
I hope you found this little experiment useful. I certainly learned something, namely: don't
shortcut requirements gathering, and consider using a state based approach from the
beginning -- it might save you time in the long run!
1796
Enterprise Tic-Tac-Toe
Enterprise Tic-Tac-Toe
UPDATE: Slides and video from my talk on this topic
This post is one of series in I which I hope to close the gap between theory and practice in
functional programming. I pick a small project and show you my thought processes as I go
about designing and implementing it from beginning to end.
For the next project in this series of posts, I'm going to do a walkthrough of a Tic-Tac-Toe
(aka Noughts and Crosses) implementation, written in a functional style.
Now, to be clear, I'm not a games developer in any shape or form, so I won't be focused on
performance or UX at all, just on the design process -- taking some requirements that we all
know (I hope) and translating them to functional code.
In fact, to be very clear, I'll deliberately be going a bit overboard on the design just to
demonstrate what you can do. There will be no objects. Everything will be immutable,
Everything will be typed. There will be capability based security, and more. Performance will
definitely be taking a back seat. Luckily, Tic-Tac-Toe does not need to support a high frame
rate!
In fact, I'm going to call this version "Enterprise Tic-Tac-Toe"!
Why? Well let's look at what you need for "Enterprise":
We need separation of concerns so that specialist teams can work on different parts
of the code at the same time.
We need a documented API so that the different teams can work effectively in parallel.
We need a security model to prevent unauthorized actions from occurring.
We need well-documented code so that the architect can ensure that the
implementation matches the UML diagrams.
We need auditing and logging to ensure that the system is SOX compliant.
We need scalability to ensure that the system is ready for the challenges of rapid
customer acquisition.
1797
Enterprise Tic-Tac-Toe
Actually, those are the stated reasons, but we all know that this is not the whole story. The
real reasons for an "enterprise design" become apparent when you talk to the people
involved:
Development Manager: "We need separation of concerns because the front-end team
and back-end team hate each other and refuse to work in the same room."
Front-end team: "We need a documented API so that those dummies building the backend won't keep breaking our code on every commit."
Back-end team: "We need a security model because those idiots building the front-end
will always find a way to do something stupid unless we constrain them."
Maintenance team: "We need well-documented code because we're fed up of having to
reverse engineer the hacked-up spaghetti being thrown at us."
Testers and Operations: "We need auditing and logging so that we can see what the
effing system is doing inside."
Everyone: "We don't really need scalability at all, but the CTO wants to us to be
buzzword compliant."
It's true that there are already some wonderful "enterprise" projects out there, such as Easy
Plus in PHP and Fizz Buzz Enterprise Edition in Java, but I hope that my own small
contribution to this genre will be considered worthy.
Seriously, I hope that the code won't be quite as bad amusing as those other enterprise
projects. In fact, I hope to demonstrate that you can have "enterprise" ready functional code
which is still readable!
1798
Enterprise Tic-Tac-Toe
As I have said before, I like to drive the design by working from the events that can happen,
rather than the objects involved. I'm old school, so I call them use-cases, but I also like the
event-storming approach.
Either way, for the Tic-Tac-Toe "domain", we have three different "event-driven use-cases"
(in this case, just various mouse clicks!) to think about:
Initialize a game
Player X moves
Player O moves
Let's start with the first: initialization. This is equivalent to a new -style constructor in an OO
program.
For Tic-Tac-Toe, there are no configuration parameters needed, so the input would be "null"
(aka unit ) and the output would be a game ready to play, like this:
type InitGame = unit -> Game
Now, what is this Game ? Since everything is immutable, the other scenarios are going to
have to take an existing game as input, and return a slightly changed version of the game.
So Game is not quite appropriate. How about GameState instead? A "player X moves"
function will thus look something like this:
type PlayerXMoves = GameState * SomeOtherStuff -> GameState
You'll see that I added SomeOtherStuff to the input parameters because there's always
some other stuff! We'll worry about what the "other stuff" is later.
Ok, What should we do next? Should we look more deeply into the internals of GameState ?
No. Let's stay high-level and do more "outside-in" style design. I like this approach in general
because it allows me to focus on what's important and not get side-tracked by
implementation details.
1799
Enterprise Tic-Tac-Toe
For each player's move, we start with the current game state, plus some other input created
by the player, and end up with a new game state.
The problem is that both functions look exactly the same and could be easily substituted for
each other. To be honest, I don't trust the user interface to always call the right one -- or at
least, it could be a potential issue.
One approach is to have only one function, rather than two. That way there's nothing to go
wrong.
But now we need to handle the two different input cases. How to do that? Easy! A
discriminated union type:
type UserAction =
| PlayerXMoves of SomeStuff
| PlayerOMoves of SomeStuff
And now, to process a move, we just pass the user action along with the state, like this:
type Move = UserAction * GameState -> GameState
So now there is only one function for the UI to call rather than two, and less to get wrong.
This approach is great where there is one user, because it documents all the things that they
can do. For example, in other games, you might have a type like this:
type UserAction =
| MoveLeft
| MoveRight
| Jump
| Fire
However in this situation, this way doesn't feel quite right. Since there are two players, what I
want to do is give each player their own distinct function to call and not allow them to use the
other player's function. This not only stops the user interface component from messing up,
but also gives me my capability-based security!
But now we are back to the original problem: how can we tell the two functions apart?
What I'll do is to use types to distinguish them. We'll make the SomeOtherStuff be owned by
each player, like this:
type PlayerXMoves = GameState * PlayerX's Stuff -> GameState
type PlayerOMoves = GameState * PlayerO's Stuff -> GameState
1800
Enterprise Tic-Tac-Toe
This way the two functions are distinct, and also PlayerO cannot call PlayerX's function
without having some of PlayerX's Stuff as well. If this sound's complicated, stay tuned -it's easier than it looks!
What is SomeOtherStuff?
What is this mysterious SomeOtherStuff ? In other words, what information do we need to
make a move?
For most domains, there might quite a lot of stuff that needs to be passed in, and the stuff
might vary based on the context and the state of the system.
But for Tic-Tac-Toe, it's easy, it's just the location on the grid where the player makes their
mark. "Top Left", "Bottom Center", and so on.
How should we define this position using a type?
The most obvious approach would be to use a 2-dimensional grid indexed by integers:
(1,1) (1,2) (1,3) , etc. But I have to admit that I'm too lazy to write unit tests that deal with
bounds-checking, nor can I ever remember which integer in the pair is the row and which the
column. I want to write code that I don't have to test!
Instead, let's define a type explicitly listing each position of horizontally and vertically:
type HorizPosition = Left | HCenter | Right
type VertPosition = Top | VCenter | Bottom
And then the position of a square in the grid (which I'm going to call a "cell") is just a pair of
these:
type CellPosition = HorizPosition * VertPosition
which means: "to play a move, the input is a game state and a selected cell position, and the
output is an updated game state".
Both player X and player O can play the same cell position, so, as we said earlier, we need
to make them distinct.
1801
Enterprise Tic-Tac-Toe
And with that, our move functions now have different types and can't be mixed up:
type PlayerXMoves = GameState * PlayerXPos -> GameState
type PlayerOMoves = GameState * PlayerOPos -> GameState
1802
Enterprise Tic-Tac-Toe
One approach is just to pass the entire game state to the UI and let the UI redisplay the
whole thing from scratch. Or perhaps, to be more efficient, the UI could cache the previous
state and do a diff to decide what needs to be updated.
In more complicated applications, with thousands of cells, we can be more efficient and
make the UI's life easier by explicitly returning the cells that changed with each move, like
this:
// added "ChangedCells"
type PlayerXMoves = GameState * PlayerXPos -> GameState * ChangedCells
type PlayerOMoves = GameState * PlayerOPos -> GameState * ChangedCells
Since Tic-Tac-Toe is a tiny game, I'm going to keep it simple and just return the game state
and not anything like ChangedCells as well.
But as I said at the beginning, I want the UI to be as dumb as possible! The UI should not
have to "think" -- it should be given everything it needs to know by the backend, and to just
follow instructions.
As it stands, the cells can be fetched directly from the GameState , but I'd rather that the UI
did not know how GameState is defined. So let's give the UI a function ( GetCells , say) that
can extract the cells from the GameState :
type GetCells = GameState -> Cell list
Another approach would be for GetCells to return all the cells pre-organized into a 2D grid - that would make life even easier for the UI.
type GetCells = GameState -> Cell[,]
But now the game engine is assuming the UI is using a indexed grid. Just as the UI
shouldn't know about the internals of the backend, the backend shouldn't make assumptions
about how the UI works.
It's fair enough to allow the UI to share the same definition of Cell as the backend, so we
can just give the UI a list of Cell s and let it display them in its own way.
Ok, the UI should have everything it needs to display the game now.
1803
Enterprise Tic-Tac-Toe
module TicTacToeDomain =
type HorizPosition = Left | HCenter | Right
type VertPosition = Top | VCenter | Bottom
type CellPosition = HorizPosition * VertPosition
type CellState =
| X
| O
| Empty
type Cell = {
pos : CellPosition
state : CellState
}
type PlayerXPos = PlayerXPos of CellPosition
type PlayerOPos = PlayerOPos of CellPosition
// the private game state
type GameState = exn // use a placeholder
// the "use-cases"
type InitGame = unit -> GameState
type PlayerXMoves = GameState * PlayerXPos -> GameState
type PlayerOMoves = GameState * PlayerOPos -> GameState
// helper function
type GetCells = GameState -> Cell list
Note that in order to make this code compile while hiding the implementation of GameState ,
I've used a generic exception class ( exn ) as a placeholder for the actual implementation of
GameState . I could also have used unit or string instead, but exn is not likely to get
mixed up with anything else, and will prevent it being accidentally overlooked later!
A note on tuples
Just a reminder that in this design phase, I'm going to combine all the input parameters into
a single tuple rather than treat them as separate parameters.
This means that I'll write:
InputParam1 * InputParam2 * InputParam3 -> Result
1804
Enterprise Tic-Tac-Toe
I'm doing this just to make the input and output obvious. When it comes to the
implementation, it's more than likely that we'll switch to the standard way, so that we can
take advantage of the techniques in our functional toolbox such as partial application.
Ok, so now we have a GameState and we are ready to display the initial grid.
At this point, the UI would create, say, a grid of empty buttons, associate a cell to each
button, and then draw the cell in the "empty" state.
This is fine, because the UI doesn't have to think. We are explicitly giving the UI a list of all
cells, and also making the initial cell state Empty , so the UI doesn't have to know which is
the default state -- it just displays what it is given.
One thing though. Since there is no input needed to set up the game, and the game state is
immutable, we will have exactly the same initial state for every game.
Therefore we don't need a function to create the initial game state, just a "constant" that gets
reused for each game.
type InitialGameState = GameState
1805
Enterprise Tic-Tac-Toe
We then pass that and the GameState into the appropriate Move function
type PlayerXMoves =
GameState * PlayerXPos -> GameState
The output is a new GameState . The UI then calls GetCells to get the new cells. We loop
through this list, update the display, and now we're ready to try again.
Excellent!
Umm... except for the bit about knowing when to stop.
As designed, This game will go on forever. We need to include something in the output of
the move to let us know whether the game is over!
So let's create a GameStatus type to keep track of that.
type GameStatus =
| InProcess
| PlayerXWon
| PlayerOWon
| Tie
And we need to add it to the output of the move as well, so now we have:
type PlayerXMoves =
GameState * PlayerXPos -> GameState * GameStatus
So now we can keep playing moves repeatedly while GameStatus is InProcess and then
stop.
The pseudocode for the UI would look like
1806
Enterprise Tic-Tac-Toe
I think we've got everything we need to play a game now, so let's move on to error handling.
1807
Enterprise Tic-Tac-Toe
The big question is: can we fix these three issues in our design without having to rely on
special validation code in the implementation? That is, can we encode these rules into types.
At this point you might be thinking "why bother with all these types?"
The advantage of using types over validation code is that the types are part of the design,
which means that business rules like these are self-documenting. On the other hand,
validation code tends to be scattered around and buried in obscure classes, so it is hard to
get a big picture of all the constraints.
In general then, I prefer to use types rather than code if I can.
And we can extend this approach to stop player X playing twice in a row too. Simply make
the ValidPositionsForNextMove be a list of PlayerOPos rather than generic positions. Player
X will not be able to play them!
type ValidMovesForPlayerX = PlayerXPos list
type ValidMovesForPlayerO = PlayerOPos list
type PlayerXMoves =
GameState * PlayerXPos -> // input
GameState * GameStatus * ValidMovesForPlayerO // output
type PlayerOMoves =
GameState * PlayerOPos -> // input
GameState * GameStatus * ValidMovesForPlayerX // output
1808
Enterprise Tic-Tac-Toe
This approach also means that when the game is over, there are no valid moves available.
So the UI cannot just loop forever, it will be forced to stop and deal with the situation.
So now we have encoded all three rules into the type system -- no manual validation
needed.
Some refactoring
Let's do some refactoring now.
First we have a couple of choice types with a case for Player X and another similar case for
Player O.
type CellState =
| X
| O
| Empty
type GameStatus =
| InProcess
| PlayerXWon
| PlayerOWon
| Tie
Let's extract the players into their own type, and then we can parameterize the cases to
make them look nicer:
type Player = PlayerO | PlayerX
type CellState =
| Played of Player
| Empty
type GameStatus =
| InProcess
| Won of Player
| Tie
The second thing we can do is to note that we only need the valid moves in the InProcess
case, not the Won or Tie cases, so let's merge GameStatus and ValidMovesForPlayer into
a single type called MoveResult , say:
1809
Enterprise Tic-Tac-Toe
We've replaced the InProcess case with two new cases PlayerXToMove and
PlayerOToMove , which I think is actually clearer.
I could have had the new GameState returned as part of MoveResult as well, but I left it
"outside" to make it clear that is not to be used by the UI.
Also, leaving it outside will give us the option of writing helper code that will thread a game
state through a series of calls for us. This is a more advanced technique, so I'm not going to
discuss it in this post.
Finally, the InitialGameState should also take advantage of the MoveResult to return the
available moves for the first player. Since it has both a game state and a initial set of moves,
let's just call it NewGame instead.
type NewGame = GameState * MoveResult
If the initial MoveResult is the PlayerXToMove case, then we have also constrained the UI so
that only player X can move first. Again, this allows the UI to be ignorant of the rules.
Second recap
So now here's the tweaked design we've got after doing the walkthrough.
1810
Enterprise Tic-Tac-Toe
module TicTacToeDomain =
type HorizPosition = Left | HCenter | Right
type VertPosition = Top | VCenter | Bottom
type CellPosition = HorizPosition * VertPosition
type Player = PlayerO | PlayerX
type CellState =
| Played of Player
| Empty
type Cell = {
pos : CellPosition
state : CellState
}
type PlayerXPos = PlayerXPos of CellPosition
type PlayerOPos = PlayerOPos of CellPosition
// the private game state
type GameState = exn // use a placeholder
type ValidMovesForPlayerX = PlayerXPos list
type ValidMovesForPlayerO = PlayerOPos list
// the move result
type MoveResult =
| PlayerXToMove of ValidMovesForPlayerX
| PlayerOToMove of ValidMovesForPlayerO
| GameWon of Player
| GameTied
// the "use-cases"
type NewGame =
GameState * MoveResult
type PlayerXMoves =
GameState * PlayerXPos -> GameState * MoveResult
type PlayerOMoves =
GameState * PlayerOPos -> GameState * MoveResult
// helper function
type GetCells = GameState -> Cell list
We're not quite done with the outside-in design yet. One question is yet to be resolved: how
can we hide the implementation of GameState from the UI?
Enterprise Tic-Tac-Toe
In any design, we want to decouple the "interface" from the "implementation". In this case,
we have:
A set of shared data structures and functions that are used by both the UI and the game
engine. ( CellState , MoveResult , PlayerXPos , etc.)
A set of private data structures and functions that should only be accessed by the game
logic. (just GameState so far)
It's obviously a good idea to keep these types separate. How should we do this?
In F#, the easiest way is to put them into separate modules, like this:
/// Types shared by the UI and the game logic
module TicTacToeDomain =
type HorizPosition = Left | HCenter | Right
type VertPosition = Top | VCenter | Bottom
type CellPosition = HorizPosition * VertPosition
type Player = PlayerO | PlayerX
type CellState =
| Played of Player
| Empty
type PlayerXMoves =
GameState * PlayerXPos -> GameState * MoveResult
// etc
/// Private types used by the internal game logic
module TicTacToeImplementation =
open TicTacToeDomain
// private implementation detail
type GameState = {
cells : Cell list
}
// etc
But if we want to keep the internals of the game logic private, what do we do with
GameState ? It's used by public functions such as PlayerXMoves , but we want to keep its
1812
Enterprise Tic-Tac-Toe
The first choice might be to put the public and private types in the same module, and have
this module be the "core" domain module that all other modules depend on.
Here's some code that demonstrates what this approach would look like:
module TicTacToeImplementation =
// public types
type HorizPosition = Left | HCenter | Right
type VertPosition = Top | VCenter | Bottom
type CellPosition = HorizPosition * VertPosition
type CellState =
| Played of Player
| Empty
type PlayerXMoves =
GameState * PlayerXPos -> GameState * MoveResult
// etc
// --------------------
// private types
type private InternalType = // to do
// ------------------- // public types with private constructor
type GameState = private {
cells : Cell list
}
// etc
1813
Enterprise Tic-Tac-Toe
The type 'XXX' is less accessible than the value, member or type 'YYY' it is used in
And even if this weren't a problem, putting the "interface" and the "implementation" in the
same file will generally end up creating extra complexity as the implementation gets larger.
implementations that inherited GameState , what's to stop me passing a game state from
implementation B into a function that is expecting a game state from implementation A?
Nothing! Chaos would ensue!
1814
Enterprise Tic-Tac-Toe
Note that in a pure OO model this situation could not happen because the GameState itself
would have stateful methods instead of the pure functional API that we have here.
1815
Enterprise Tic-Tac-Toe
The types that don't use the game state are unchanged, but you can see that
PlayerXMoves<'T> has been parameterized with the game state.
Adding generics like this can often cause cascading changes to many types, forcing them all
to be parameterized. Dealing with all these generics is one reason why type inference is so
helpful in practice!
Now for the types internal to the game logic. They can all be public now, because the UI
won't be able to know about them.
module TicTacToeImplementation =
open TicTacToeDomain
// can be public
type GameState = {
cells : Cell list
}
Finally, here's what the implementation of a playerXMoves function might look like:
let playerXMoves : PlayerXMoves<GameState> =
fun (gameState,move) ->
// logic
This function references a particular implementation, but can be passed into the UI code
because it conforms to the PlayerXMoves<'T> type.
Furthermore, by using generic parameters, we naturally enforce that the same
implementation, say "GameStateA", is used throughout.
In other words, the game state created by InitGame<GameStateA> can only be passed to a
PlayerXMoves<GameStateA> function which is parameterized on the same implementation
type.
1816
Enterprise Tic-Tac-Toe
1817
Enterprise Tic-Tac-Toe
module TicTacToeImplementation =
open TicTacToeDomain
/// create the state of a new game
let newGame : NewGame<GameState> =
// return new game and current available moves
let validMoves = // to do
gameState, PlayerXToMove validMoves
let playerXMoves : PlayerXMoves<GameState> =
fun (gameState,move) ->
// implementation
module WinFormUI =
open TicTacToeDomain
open System.Windows.Forms
type TicTacToeForm<'T>
(
// pass in the required functions
// as parameters to the constructor
newGame:NewGame<'T>,
playerXMoves:PlayerXMoves<'T>,
playerOMoves:PlayerOMoves<'T>,
getCells:GetCells<'T>
) =
inherit Form()
// implementation to do
module WinFormApplication =
open WinFormUI
// get functions from implementation
let newGame = TicTacToeImplementation.newGame
let playerXMoves = TicTacToeImplementation.playerXMoves
let playerOMoves = TicTacToeImplementation.playerOMoves
let getCells = TicTacToeImplementation.getCells
// create form and start game
let form =
new TicTacToeForm<_>(newGame,playerXMoves,playerOMoves,getCells)
form.Show()
1818
Enterprise Tic-Tac-Toe
Next, you can see that I've explicitly added the type parameters to TicTacToeForm<'T> like
this.
TicTacToeForm<'T>(newGame:NewGame<'T>, playerXMoves:PlayerXMoves<'T>, etc)
I could have eliminated the type parameter for the form by doing something like this instead:
TicTacToeForm(newGame:NewGame<_>, playerXMoves:PlayerXMoves<_>, etc)
or even:
TicTacToeForm(newGame, playerXMoves, etc)
and let the compiler infer the types, but this often causes a "less generic" warning like this:
warning FS0064: This construct causes code to be less generic than indicated by the ty
pe annotations.
The type variable 'T has been constrained to be type 'XXX'.
By explicitly writing TicTacToeForm<'T> , this can be avoided, although it is ugly for sure.
This acts both as a container to pass around functions, and as nice documentation of what
functions are available in the API.
The implementation now has to create an "api" object:
1819
Enterprise Tic-Tac-Toe
module TicTacToeImplementation =
open TicTacToeDomain
/// create the functions to export
let newGame : NewGame<GameState> = // etc
let playerXMoves : PlayerXMoves<GameState> = // etc
// etc
// export the functions
let api = {
newGame = newGame
playerOMoves = playerOMoves
playerXMoves = playerXMoves
getCells = getCells
}
Enterprise Tic-Tac-Toe
Now let's create a minimal implementation of the UI. We won't draw anything or respond to
clicks, just mock up some functions so that we can test the logic.
Here's my first attempt:
type TicTacToeForm<'GameState>(api:TicTacToeAPI<'GameState>) =
inherit Form()
let mutable gameState : 'GameState = ???
let mutable lastMoveResult : MoveResult = ???
let displayCells gameState =
let cells = api.getCells gameState
for cell in cells do
// update display
let startGame()=
let initialGameState,initialResult = api.newGame
gameState <- initialGameState
lastMoveResult <- initialResult
// create cell grid from gameState
let handleMoveResult moveResult =
match moveResult with
| PlayerXToMove availableMoves ->
// show available moves
1821
Enterprise Tic-Tac-Toe
As you can see, I'm planning to use the standard Form event handling approach -- each cell
will have a "clicked" event handler associated with it. How the control or pixel location is
converted to a CellPosition is something I'm not going to worry about right now, so I've just
hard-coded some dummy data.
I'm not going to be pure here and have a recursive loop. Instead, I'll keep the current
gameState as a mutable which gets updated after each move.
But now we have got a tricky situation... What is the gameState when the game hasn't
started yet? What should we initialize it to? Similarly, when the game is over, what should it
be set to?
let mutable gameState : 'GameState = ???
One choice might be to use a GameState option but that seems like a hack, and it makes
me think that we are failing to think of something.
1822
Enterprise Tic-Tac-Toe
Similarly, we have a field to hold the result of the last move ( lastMoveResult ) so we can
keep track of whose turn it is, or whether the game is over.
But again, what should it be set to when the game hasn't started?
Let's take a step back and look at all the states the user interface can be in -- not the state of
the game itself, but the state of the user interface.
We start off in an "idle" state, with no game being played.
Then the user starts the game, and we are "playing".
While each move is played, we stay in the "playing" state.
When the game is over, we show the win or lose message.
We wait for the user to acknowledge the end-of-game message, then go back to idle
again.
Again, this is for the UI only, it has nothing to do with the internal game state.
So -- our solution to all problems! -- let's create a type to represent these states.
type UiState =
| Idle
| Playing
| Won
| Lost
But do we really need the Won and Lost states? Why don't we just go straight back to
Idle when the game is over?
The nice thing about using a type like this is that we can easily store the data that we need
for each state.
What data do we need to store in the Idle state? Nothing that I can think of.
What data do we need to store in the Playing state? Well, this would be a perfect
place to keep track of the gameState and lastMoveResult that we were having
problems with earlier. They're only needed when the game is being played, but not
otherwise.
So our final version looks like this. We've had to add the <'GameState> to UiState because
we don't know what the actual game state is.
1823
Enterprise Tic-Tac-Toe
type UiState<'GameState> =
| Idle
| Playing of 'GameState * MoveResult
With this type now available, we no longer need to store the game state directly as a field in
the class. Instead we store a mutable UiState , which is initialized to Idle .
type TicTacToeForm<'GameState>(api:TicTacToeAPI<'GameState>) =
inherit Form()
let mutable uiState = Idle
And when we handle a click, we only do something if the uiState is in Playing mode, and
then we have no trouble getting the gameState and lastMoveResult that we need, because
it is stored as part of the data for that case.
1824
Enterprise Tic-Tac-Toe
let handleClick() =
match uiState with
| Idle -> ()
// do nothing
| Playing (gameState,lastMoveResult) ->
let gridIndex = 0,0 // dummy for now
let cellPos = createCellPosition gridIndex
match lastMoveResult with
| PlayerXToMove availableMoves ->
let playerXmove = PlayerXPos cellPos
// if move is in available moves then send it
// to the api
let newGameState,newResult =
api.playerXMoves gameState playerXmove
// handle the result
// e.g. if the game is over
handleMoveResult newResult
// update the uiState with newGameState
uiState <- Playing (newGameState,newResult)
| PlayerOToMove availableMoves ->
// etc
| _ ->
// ignore other states
If you look at the last line of the PlayerXToMove case, you can see the global uiState field
being updated with the new game state:
| PlayerXToMove availableMoves ->
// snipped
let newGameState,newResult = // get new state
// update the uiState with newGameState
uiState <- Playing (newGameState,newResult)
Enterprise Tic-Tac-Toe
1826
Enterprise Tic-Tac-Toe
module TicTacToeDomain =
type HorizPosition = Left | HCenter | Right
type VertPosition = Top | VCenter | Bottom
type CellPosition = HorizPosition * VertPosition
type Player = PlayerO | PlayerX
type CellState =
| Played of Player
| Empty
type Cell = {
pos : CellPosition
state : CellState
}
type PlayerXPos = PlayerXPos of CellPosition
type PlayerOPos = PlayerOPos of CellPosition
type ValidMovesForPlayerX = PlayerXPos list
type ValidMovesForPlayerO = PlayerOPos list
type MoveResult =
| PlayerXToMove of ValidMovesForPlayerX
| PlayerOToMove of ValidMovesForPlayerO
| GameWon of Player
| GameTied
// the "use-cases"
type NewGame<'GameState> =
'GameState * MoveResult
type PlayerXMoves<'GameState> =
'GameState -> PlayerXPos -> 'GameState * MoveResult
type PlayerOMoves<'GameState> =
'GameState -> PlayerOPos -> 'GameState * MoveResult
// helper function
type GetCells<'GameState> =
'GameState -> Cell list
// the functions exported from the implementation
// for the UI to use.
type TicTacToeAPI<'GameState> =
{
newGame : NewGame<'GameState>
playerXMoves : PlayerXMoves<'GameState>
playerOMoves : PlayerOMoves<'GameState>
getCells : GetCells<'GameState>
}
1827
Enterprise Tic-Tac-Toe
1828
Enterprise Tic-Tac-Toe
1829
Enterprise Tic-Tac-Toe
1830
Enterprise Tic-Tac-Toe
1831
Enterprise Tic-Tac-Toe
moves
|> List.iteri (fun i move ->
printfn "%i) %A" i move )
/// Get the move corresponding to the
/// index selected by the user
let getMove moveIndex moves =
if moveIndex < List.length moves then
let move = List.nth moves moveIndex
Some move
else
None
/// Given that the user has not quit, attempt to parse
/// the input text into a index and then find the move
/// corresponding to that index
let processMoveIndex inputStr gameState availableMoves makeMove processInputAgain
=
match Int32.TryParse inputStr with
// TryParse will output a tuple (parsed?,int)
| true,inputIndex ->
// parsed ok, now try to find the corresponding move
match getMove inputIndex availableMoves with
| Some move ->
// corresponding move found, so make a move
let moveResult = makeMove gameState move
ContinuePlay moveResult // return it
| None ->
// no corresponding move found
printfn "...No move found for inputIndex %i. Try again" inputIndex
// try again
processInputAgain()
| false, _ ->
// int was not parsed
printfn "...Please enter an int corresponding to a displayed move."
// try again
processInputAgain()
/// Ask the user for input. Process the string entered as
/// a move index or a "quit" command
let rec processInput gameState availableMoves makeMove =
// helper that calls this function again with exactly
// the same parameters
let processInputAgain() =
processInput gameState availableMoves makeMove
printfn "Enter an int corresponding to a displayed move or q to quit:"
let inputStr = Console.ReadLine()
if inputStr = "q" then
ExitGame
else
1832
Enterprise Tic-Tac-Toe
1833
Enterprise Tic-Tac-Toe
And finally, the application code that connects all the components together and launches the
UI:
module ConsoleApplication =
let startGame() =
let api = TicTacToeImplementation.api
ConsoleUi.startGame api
Example game
Here's what the output of this game looks like:
|-|X|-|
|X|-|-|
|O|-|-|
Player O to move
0) PlayerOPos (Left, Top)
1834
Enterprise Tic-Tac-Toe
Logging
Oops! We promised we would add logging to make it enterprise-ready!
1835
Enterprise Tic-Tac-Toe
That's easy -- all we have to do is replace the api functions with equivalent functions that log
the data we're interested in
module Logger =
open TicTacToeDomain
let logXMove (PlayerXPos cellPos)=
printfn "X played %A" cellPos
let logOMove (PlayerOPos cellPos)=
printfn "O played %A" cellPos
/// inject logging into the API
let injectLogging api =
// make a logged version of the game function
let playerXMoves state move =
logXMove move
api.playerXMoves state move
// make a logged version of the game function
let playerOMoves state move =
logOMove move
api.playerOMoves state move
// create a new API with
// the move functions replaced
// with logged versions
{ api with
playerXMoves = playerXMoves
playerOMoves = playerOMoves
}
Obviously, in a real system you'd replace it with a proper logging tool such as log4net and
generate better output, but I think this demonstrates the idea.
Now to use this, all we have to do is change the top level application to transform the original
api to a logged version of the api:
module ConsoleApplication =
let startGame() =
let api = TicTacToeImplementation.api
let loggedApi = Logger.injectLogging api
ConsoleUi.startGame loggedApi
1836
Enterprise Tic-Tac-Toe
Oh, and remember that I originally had the initial state created as a function rather than as a
constant?
type InitGame = unit -> GameState
I changed to a constant early on in the design. But I'm regretting that now, because it means
that I can't hook into the "init game" event and log it. If I do want to log the start of each
game, I should really change it back to a function again.
Questions
Question: You went to the trouble of hiding the internal structure of GameState , yet
the PlayerXPos and PlayerOPos types are public. Why?
I forgot! And then laziness kept me from updating the code, since this is really just an
exercise in design.
It's true that in the current design, a malicious user interface could construct a PlayerXPos
and then play X when it is not player X's turn, or to play a position that has already been
played.
You could prevent this by hiding the implementation of PlayerXPos in the same way as we
did for game state, using a type parameter. And of course you'd have to tweak all the related
classes too.
Here's a snippet of what that would look like:
type MoveResult<'PlayerXPos,'PlayerOPos> =
| PlayerXToMove of 'PlayerXPos list
| PlayerOToMove of 'PlayerOPos list
| GameWon of Player
| GameTied
type NewGame<'GameState,'PlayerXPos,'PlayerOPos> =
'GameState * MoveResult<'PlayerXPos,'PlayerOPos>
type PlayerXMoves<'GameState,'PlayerXPos,'PlayerOPos> =
'GameState -> 'PlayerXPos ->
'GameState * MoveResult<'PlayerXPos,'PlayerOPos>
type PlayerOMoves<'GameState,'PlayerXPos,'PlayerOPos> =
'GameState -> 'PlayerOPos ->
'GameState * MoveResult<'PlayerXPos,'PlayerOPos>
1837
Enterprise Tic-Tac-Toe
We'd also need a way for the UI to see if the CellPosition a user selected was valid. That
is, given a MoveResult and the desired CellPosition , if the position is valid, return Some
move, otherwise return None .
type GetValidXPos<'PlayerXPos,'PlayerOPos> =
CellPosition * MoveResult<'PlayerXPos,'PlayerOPos> -> 'PlayerXPos option
It's getting kind of ugly now, though. That's one problem with type-first design: the type
parameters can get complicated!
So it's a trade-off. How much do you use types to prevent accidental bugs without
overwhelming the design?
In this case, I do think the GameState should be secret, as it is likely to change in the future
and we want to ensure that the UI is not accidentally coupled to implementation details.
For the move types though, (a) I don't see the implementation changing and (b) the
consequence of a malicious UI action is not very high, so overall I don't mind having the
implementation be public.
UPDATE 2015-02-16: In the next post I solve this problem in a more elegant way, and get rid
of GameState as well!
Question: Why are you using that strange syntax for defining the initGame and move
functions?
You mean, why I am defining the functions like this:
/// create the state of a new game
let newGame : NewGame<GameState> =
// implementation
let playerXMoves : PlayerXMoves<GameState> =
fun (gameState,move) ->
// implementation
1838
Enterprise Tic-Tac-Toe
I'm doing this when I want to treat functions as values. Just as we might say "x is a value of
type int" like this x :int = ... , I'm saying that "playerXMoves is a value of type
PlayerXMoves" like this: playerXMoves : PlayerXMoves = ... . It's just that in this case, the
value is a function rather than a simple value.
Doing it this way follows from the type-first approach: create a type, then implement things
that conform to that type.
Would I recommend doing this for normal code? Probably not!
I'm only doing this as part of an exploratory design process. Once the design stabilizes, I
would tend to switch back to the normal way of doing things.
Question: This seems like a lot of work. Isn't this just BDUF under another guise?
This might seem like quite a long winded way of doing design, but in practice, it would
probably not take very long. Certainly no longer than mocking up an exploratory prototype in
another language.
We've gone through a number of quick iterations, using types to document the design, and
using the REPL as a "executable spec checker" to make sure that it all works together
properly.
And at the end of all this, we now have a decent design with some nice properties:
There is a "API" that separates the UI from the core logic, so that work on each part can
proceed in parallel if needed.
The types act as documentation and will constrain the implementation in a way that
UML diagrams could never do!
The design is encoded in types, so that any the inevitable changes that occur during
development can be made with confidence.
I think this whole process is actually pretty agile, once you get used to working this way.
Question: Come on, would you really write Tic-Tac-Toe this way?
It depends. If it was just me, maybe not. :-)
But if it was a more complex system with different teams for the front-end and back-end,
then I would certainly use a design-first approach like this. In cases like that, things like datahiding and abstract interfaces are critical, and I think this approach delivers that.
Question: Why is the design so specific? It seems like none of it will be reusable at
all. Why not?
Yes, this code is full of very specific types: Cell , GameState , etc. And it's true that none of
it will be reusable.
1839
Enterprise Tic-Tac-Toe
There is always a tension between a very domain-specific and non-reusable design, like this
one, and an abstract and reusable library of things like lists and trees.
Ideally, you would start with low-level, reusable components and then compose them into
larger more-specific ones (e.g. a DSL), from which you can build a application. (Tomas has a
good post on exactly this).
The reasons why I did not do that here is that, first, I always like to start with very concrete
designs. You can't even know what a good abstraction looks like until you have built
something a few times.
We have separated the UI from the core logic, but going any further than that does not make
sense to me right now. If I was going to build lots of other kinds of games that were similar to
Tic-Tac-Toe, then some useful abstractions might become apparent.
Second, designs with concrete types are easier for non-experts to understand. I'd like to
think that I could show these domain types to a non-programmer (e.g. a domain expert) and
have them understand and comment sensibly on them. If they were more abstract, that
would not be possible.
Exercises
If you want a challenge, here are some exercises for you:
The playerXMoves and playerOMoves functions have very similar code. How would you
refactor them to reduce that?
Do a security audit and think of all the ways that a malicious user or UI could corrupt the
game using the current design. Then fix them!
Summary
In this post, we've seen how to design a system using mostly types, with the occasional
code fragments to help us clarify issues.
It was definitely an exercise in design overkill but I hope that there are some ideas in there
that might be applicable to real non-toy projects.
At the start, I claimed that this design would be "enterprise" ready. Is it?
We do have separation of concerns via the functions that are exported to the UI.
We do have a well documented API. There are no magic numbers, the names of the
types are self-documenting, and the list of functions exported is in one place.
We do have a security model to prevent unauthorized actions from occurring. As it
1840
Enterprise Tic-Tac-Toe
stands, it would be hard to accidentally mess up. And if we go the extra distance by
parameterizing the move types as well, then it becomes really quite hard for the game
to be corrupted.
We do have well-documented code, I think. Even though this is "enterprise", the code is
quite explicit in what it does. There are no wasted abstractions -- no
AbstractSingletonProxyFactoryBean to make fun of.
We did add auditing and logging easily, and in an elegant way after the fact, without
interfering with the core design.
We get scalability for free because there is no global session data. All we have to do is
persist the game state in the browser (Or we could use MongoDb and be web scale).
This is not a perfect design -- I can think of a number of ways to improve it -- but overall I'm
quite happy with it, considering it was a straight brain-to-code dump.
What do you think? Let me know in the comments.
UPDATE 2015-02-16: I ended up being unhappy with this design after all. In the next
post I tell you why, and present a better design.
NOTE: The code for this post is available on GitHub in this gist.
1841
The UI (or other client) passes the game state into each move, and gets a updated
game state back.
Each move also returns a MoveResult which contains the game status (in process,
won, tied), and if the game is still in process, whose turn it is, and what the available
moves are.
Here's the code:
1842
module TicTacToeDomain =
type HorizPosition = Left | HCenter | Right
type VertPosition = Top | VCenter | Bottom
type CellPosition = HorizPosition * VertPosition
type Player = PlayerO | PlayerX
type CellState =
| Played of Player
| Empty
type Cell = {
pos : CellPosition
state : CellState
}
type PlayerXPos = PlayerXPos of CellPosition
type PlayerOPos = PlayerOPos of CellPosition
type ValidMovesForPlayerX = PlayerXPos list
type ValidMovesForPlayerO = PlayerOPos list
type MoveResult =
| PlayerXToMove of ValidMovesForPlayerX
| PlayerOToMove of ValidMovesForPlayerO
| GameWon of Player
| GameTied
// the "use-cases"
type NewGame<'GameState> =
'GameState * MoveResult
type PlayerXMoves<'GameState> =
'GameState -> PlayerXPos -> 'GameState * MoveResult
type PlayerOMoves<'GameState> =
'GameState -> PlayerOPos -> 'GameState * MoveResult
1843
The problem was that the PlayerXPos and PlayerOPos types are public, so that a malicious
user could have forged one and played twice anyway!
Yes, these types could have been made private by parameterizing them like the game state,
but the design would have become very ugly very quickly.
Second, even if the moves were made unforgeable, there's that game state floating about.
It's true that the game state internals are private, but a malicious user could have still caused
problems by reusing a game state. For example, they could attempt to play one of the valid
moves with a game state from a previous turn, or vice versa.
In this particular case, it would not be dangerous, but in general it might be a problem.
So, as you can see, this design was becoming a bit smelly to me, which was why I was
becoming unhappy.
type PlayerXMoves =
GameState * PlayerXPos -> // input
GameState * MoveResult // output
The user is passing in the location ( PlayerXPos ) that they want to play.
But let's now take away the user's ability to choose the position. Why don't I give the user a
function, a MoveCapability say, that has the position baked in?
type MoveCapability =
GameState -> // input
GameState * MoveResult // output
In fact, why not bake the game state into the function too? That way a malicious user can't
pass the wrong game state to me.
This means that there is no "input" at all now -- everything is baked in!
type MoveCapability =
unit -> // no input
GameState * MoveResult // output
But now we have to give the user a whole set of capabilities, one for each possible move
they can make. Where do these capabilities come from?
Answer, the MoveResult of course! We'll change the MoveResult to return a list of
capabilities rather than a list of positions.
type MoveResult =
| PlayerXToMove of MoveCapability list
| PlayerOToMove of MoveCapability list
| GameWon of Player
| GameTied
1845
Let's deal with the first issue: how does the user know which capability is associated with
which square?
The answer is just to create a new structure that "labels" the capability. In this case, with the
cell position.
1846
type NextMoveInfo = {
posToPlay : CellPosition
capability : MoveCapability }
And now we must change the MoveResult to return a list of these labelled capabilities,
rather than the unlabelled ones:
type MoveResult =
| PlayerXToMove of NextMoveInfo list
| PlayerOToMove of NextMoveInfo list
| GameWon of Player
| GameTied
Note that the cell position is for the user's information only -- the actual position is still baked
into the capability and cannot be forged.
Now for the second issue: how does the UI know what to display as a result of the move?
Let's just return that information to it directly in a new structure:
/// Everything the UI needs to know to display the board
type DisplayInfo = {
cells : Cell list
}
And once again, the MoveResult must be changed, this time to return the DisplayInfo for
each case:
type MoveResult =
| PlayerXToMove of DisplayInfo * NextMoveInfo list
| PlayerOToMove of DisplayInfo * NextMoveInfo list
| GameWon of DisplayInfo * Player
| GameTied of DisplayInfo
1847
depends on MoveCapability again. But the F# compiler does not allow forward references
in general.
Circular dependencies like this are generally frowned upon (I even have a post called "cyclic
dependencies are evil"!) and there are normally work-arounds which you can use to remove
them.
In this case though, I will link them together using the and keyword, which replaces the
type keyword and is useful for just these kinds of cases.
type MoveCapability =
// etc
and NextMoveInfo = {
// etc
and MoveResult =
// etc
1848
Originally, we had an API with slots for the three use-cases and also a helper function
getCells :
type TicTacToeAPI<'GameState> =
{
newGame : NewGame<'GameState>
playerXMoves : PlayerXMoves<'GameState>
playerOMoves : PlayerOMoves<'GameState>
getCells : GetCells<'GameState>
}
But now, we don't need the playerXMoves or playerOMoves , because they are returned to us
in the MoveResult of a previous move.
And getCells is no longer needed either, because we are returning the DisplayInfo
directly now.
So after all these changes, the new API just has a single slot in it and looks like this:
type NewGame = unit -> MoveResult
type TicTacToeAPI =
{
newGame : NewGame
}
I've changed NewGame from a constant to a parameterless function, which is in fact, just a
MoveCapability in disguise.
1849
module TicTacToeDomain =
type HorizPosition = Left | HCenter | Right
type VertPosition = Top | VCenter | Bottom
type CellPosition = HorizPosition * VertPosition
type Player = PlayerO | PlayerX
type CellState =
| Played of Player
| Empty
type Cell = {
pos : CellPosition
state : CellState
}
/// Everything the UI needs to know to display the board
type DisplayInfo = {
cells : Cell list
}
/// The capability to make a move at a particular location.
/// The gamestate, player and position are already "baked" into the function.
type MoveCapability =
unit -> MoveResult
/// A capability along with the position the capability is associated with.
/// This allows the UI to show information so that the user
/// can pick a particular capability to exercise.
and NextMoveInfo = {
// the pos is for UI information only
// the actual pos is baked into the cap.
posToPlay : CellPosition
capability : MoveCapability }
/// The result of a move. It includes:
/// * The information on the current board state.
/// * The capabilities for the next move, if any.
and MoveResult =
| PlayerXToMove of DisplayInfo * NextMoveInfo list
| PlayerOToMove of DisplayInfo * NextMoveInfo list
| GameWon of DisplayInfo * Player
| GameTied of DisplayInfo
// Only the newGame function is exported from the implementation
// all other functions come from the results of the previous move
type TicTacToeAPI =
{
newGame : MoveCapability
}
1850
I'm much happier with this design than with the previous one:
There is no game state for the UI to worry about.
There are no type parameters to make it look ugly.
The api is even more encapsulated -- a malicious UI can do very little now.
It's shorter -- always a good sign!
Logging revisited
In the previous post, I demonstrated how logging could be injected into the API.
But in this design, the capabilities are opaque and have no parameters, so how are we
supposed to log that a particular player chose a particular location?
Well, we can't log the capabilities, but we can log their context, which we have via the
NextMoveInfo . Let's see how this works in practice.
First, given a MoveCapability , we want to transform it into another MoveCapability that also
logs the player and cell position used.
Here's the code for that:
/// Transform a MoveCapability into a logged version
let transformCapability transformMR player cellPos (cap:MoveCapability) :MoveCapabilit
y =
// create a new capability that logs the player & cellPos when run
let newCap() =
printfn "LOGINFO: %A played %A" player cellPos
let moveResult = cap()
transformMR moveResult
newCap
1851
When it is called, log the player and cell position. These are not available from the
MoveCapability that was passed in, so we have to pass them in explicitly.
1852
For the PlayerOToMove case, do the same as the PlayerXToMove case, except change
the player to PlayerO .
Finally, we can inject logging into the API as a whole by transforming the MoveResult
returned by newGame :
/// inject logging into the API
let injectLogging api =
// create a new API with the functions
// replaced with logged versions
{ api with
newGame = fun () -> api.newGame() |> transformMoveResult
}
So there you go. Logging is a bit trickier than before, but still possible.
A warning on recursion
In this code, I've been passing around functions that call each other recursively. When you
do this, you have to be careful that you don't unwittingly cause a stack overflow.
In a game like this, when the number of nested calls is guaranteed to be small, then there is
no issue. But if you are doing tens of thousands of nested calls, then you should worry about
potential problems.
In some cases, the F# compiler will do tail-call optimization, but I suggest that you stress test
your code to be sure!
1853
The original design was data-centric. Yes, we gave each player a function to use, but it was
the same function used over and over, with different data passed in each time.
The new design is function-centric (or as I prefer, capability-centric). There is very little data
now. Instead, the result of each function call is another set of functions than can be used for
the next step, and so on, ad infinitum.
In fact, it reminds me somewhat of a continuation-based approach, except that rather than
passing in a continuation, the function itself returns a list of continuations, and then you pick
one to use.
1854
In the data-centric version, all the data needed for a move was passed in each time,
which means that scaling the backend services would be trivial.
In capability-centric approach though, the state has to be stored somewhere. If the
complete game state can be encoded into the URI, then this approach will allow
stateless servers as well, but otherwise, some sort of state-storage will be needed.
Some web frameworks have made this function-centred approach a key part of their design,
most notably Seaside.
The excellent WebSharper framework for F# also uses something similar, I think (I don't
know WebSharper as well as I want to, alas, so correct me if I'm wrong).
Summary
In this post, I tore up my original design and replaced it with an even more function-centric
one, which I like better.
But of course it still has all those qualities we love: separation of concerns, an API, a
watertight security model, self-documenting code, and logging.
I'm going to stop with Tic-Tac-Toe now -- I think I've wrung it dry! I hope you found these two
walkthoughs interesting; I learned a lot myself.
NOTE: The code for this post is available on GitHub in this gist.
1855
1856
That's 17 lines vs. only 2 lines. Imagine that difference multiplied over a whole project!
If I did use this approach, my productivity would drop drastically. I'm sorry -- I just can't afford
it.
1857
Look at the difference! I don't know about you, but I find the second example a bit disturbing,
as if something important is missing.
To be honest, I feel a bit lost without the guidance that curly braces give me.
1858
And here's the function signature for similar code in C#, with explicit type declarations.
public IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(
IEnumerable<TSource> source,
Func<TSource, TKey> keySelector
)
...
I may be in the minority here, but I like the second version much better. It's important to me
to know that the return is of type IEnumerable<IGrouping<TKey, TSource>> .
Sure, the compiler will type check this for you and warn you if there is a type mismatch. But
why let the compiler do the work when your brain can do it instead?
Ok, I admit that if you do use generics, and lambdas, and functions that return functions, and
all the other newfangled stuff, then yes, your type declarations can get really hairy and
complex. And it gets really hard to type them properly.
But I have an easy fix for that -- don't use generics and don't pass around functions. Your
signatures will be much simpler.
1859
Haha! Just kidding! Of course I can't be bothered to put null-checking code everywhere. I'd
never get any real work done.
But I've only ever had to deal with one bad crash caused by a NPE. And the business didn't
lose too much money during the few weeks I spent looking for the problem. So I'm not sure
why this is such a big deal.
1860
But I don't see any mention of patterns in functional design. How can you get useful stuff
done without Strategy, AbstractFactory, Decorator, Proxy, and so on?
Perhaps the functional programmers are not aware of them?
1861
1862
Daddy: That's right. Can you count? There are two doggies.
Alice: Look at those horsies.
Daddy: Yes darling. Do you know what the kitties and doggies and horsies all have in
common?
Alice: No. Nothing in common!
Daddy: Well, actually they do have something in common. Can you see what it is?
1863
1864
1865
UPDATE: Many people have seriously misread this post, it seems. So let me be clear:
1866
I am not saying that statically typed languages are "better" than dynamic languages.
I am not saying that FP languages are "better" than OO languages.
I am not saying that being able to reason about code is the most important aspect of a
language.
What I am saying is:
Not being able to reason about code has costs that many developers might not be
aware of.
Therefore, being "reasonable" should be one of the (many) factors under consideration
when choosing a programming language, not just ignored due to lack of awareness.
IF you want to be able to reason about your code, THEN it will be much easier if your
language supports the features that I mention.
The fundamental paradigm of OO (object-identity, behavior-based) is not compatible
with "reasonability", and so it will be hard to retrofit existing OO languages to add this
quality.
That's it. Thank you!
1867
Of course, there is a huge amount of advice out there on how to do just this: naming
guidelines, formatting rules, design patterns, etc., etc.
But can your programming language by itself help your code to be more reasonable, more
predictable? I think the answer is yes, but I'll let you judge for yourself.
Below, I'll present a series of code fragments. After each snippet, I'm going to ask you what
you think the code does. I've deliberately not shown my own comments so that you can think
about it and do your own reasoning. After you have thought about it, scroll down to read my
opinion.
Example 1
Let's start off by looking at the following code.
We start with a variable x that is assigned the integer 2 .
Then DoSomething is called with x as a parameter.
Then y is assigned to x - 1 .
The question I would ask you is simple: What is the value of y ?
var x = 2;
DoSomething(x);
// What value is y?
var y = x - 1;
1868
The answer is -1 . Did you get that answer? No? If you can't figure it out, scroll down again.
1869
1870
Yes, it's horrible! DoSomething accesses x directly rather than through the parameter, and
then turns it into a boolean of all things! Then, subtracting 1 from x casts it from false to
0 , so that y is -1 .
Don't you totally hate this? Sorry to mislead you about the language, but I just wanted to
demonstrate how annoying it is when the language behaves in unpredictable ways.
JavaScript is a very useful and important language. But no one would claim that
reasonableness was one of its strengths. In fact, most dynamically-typed languages have
quirks that make them hard to reason about in this way.
Thanks to static typing and sensible scoping rules, this kind of thing could never happen in
C# (unless you tried really hard!) In C#, if you don't match up the types properly, you get a
compile-time error rather than a run-time error.
In other words, C# is much more predictable than JavaScript. Score one for static typing!
So now we have our first requirement for making a language predictable:
How to make your language predictable:
1. Variables should not be allowed to change their type.
C# is looking good compared to JavaScript. But we're not done yet...
1871
UPDATE: This is an admittedly silly example. In retrospect, I could have picked a better one.
Yes, I know that no one sensible would ever do this. The point still stands: the JavaScript
language does not prevent you from doing stupid things with implicit typecasts.
Example 2
In this next example, we're going to create two instances of the same Customer class, with
exactly the same data in them.
The question is: Are they equal?
// create two customers
var cust1 = new Customer(99, "J Smith");
var cust2 = new Customer(99, "J Smith");
// true or false?
cust1.Equals(cust2);
1872
// true or false?
cust1.Equals(cust2);
Who knows? It depends on how the Customer class has been implemented. This code is
not predictable.
1873
You'll have to look at whether the class implements IEquatable at least, and you'll probably
have to look at the internals of the class as well to see exactly what is going on.
But why is this even an issue?
Let me ask you this:
How often would you NOT want the instances to be equal?
How often have you had to override the Equals method?
How often have you had a bug caused by forgetting to override the Equals method?
How often have you had a bug caused by mis-implementing GetHashCode (such as
forgetting to change it when the fields that you compare on change)?
Why not make the objects equal by default, and make reference equality testing the special
case?
So let's add another item to our list.
How to make your language predictable:
1. Variables should not be allowed to change their type.
2. Objects containing the same values should be equal by default.
Example 3
In this next example, I've got two objects containing exactly the same data, but which are
instances of different classes.
The question again is: Are they equal?
// create a customer and an order
var cust = new Customer(99, "J Smith");
var order = new Order(99, "J Smith");
// true or false?
cust.Equals(order);
1874
1875
// true or false?
cust.Equals(order);
Who cares! This is almost certainly a bug! Why are you even comparing two different
classes like this in the first place?
Compare their names or ids, certainly, but not the objects themselves. This should be a
compiler error.
If it isn't, why not? You probably just used the wrong variable name by mistake but now you
have a subtle bug in your code. Why does your language let you do this?
So let's add another item to our list.
How to make your language predictable:
1. Variables should not be allowed to change their type.
2. Objects containing the same values should be equal by default.
3. Comparing objects of different types is a compile-time error.
UPDATE: Many people have pointed out that you need this when comparing classes related
by inheritance. This is true, of course. But what is the cost of this feature? You get the ability
to compare subclasses, but you lose the ability to detect accidental errors.
Which is more important in practice? That's for you to decide, I just wanted to make it clear
that there are costs associated with the status quo, not just benefits.
Example 4
In this snippet, we're just going to create a Customer instance. That's all. Can't get much
more basic than that.
1876
// create a customer
var cust = new Customer();
// what is the expected output?
Console.WriteLine(cust.Address.Country);
1877
Who knows?
It depends on whether the Address property is null or not. And that is something you can't
tell without looking at the internals of the Customer class again.
Yes, we know that it is a best practice that constructors should initialize all fields at
construction time, but why doesn't the language enforce it?
If the address is required, then make it be required in the constructor. And if the address is
not always required, then make it clear that the Address property is optional and might be
missing.
So let's add another item to our list of improvements.
How to make your language predictable:
1. Variables should not be allowed to change their type.
2. Objects containing the same values should be equal by default.
3. Comparing objects of different types is a compile-time error.
1878
Example 5
In this next example, we're going to:
Create a customer.
Add it to a set that uses hashing.
Do something with the customer object.
See if the customer is still in the set.
What could possibly go wrong?
// create a customer
var cust = new Customer(99, "J Smith");
// add it to a set
var processedCustomers = new HashSet<Customer>();
processedCustomers.Add(cust);
// process it
ProcessCustomer(cust);
// Does the set contain the customer? true or false?
processedCustomers.Contains(cust);
So, does the set still contain the customer at the end of this code?
(scroll down for answer)
1879
1880
It's clear that ProcessCustomer has changed something just by looking at this code. If
ProcessCustomer hadn't changed anything, it wouldn't have needed to return an object at all.
Going back to the question, it's clear that in this implementation the original version of the
customer is guaranteed to still be in the set, no matter what ProcessCustomer does.
1881
Of course, that doesn't solve the issue of whether the new one or the old one (or both)
should be in the set. But unlike the implementation using the mutable customer, this issue is
now staring you in the face and won't go unnoticed accidentally.
So immutability FTW!
So that's another item for our list.
How to make your language predictable:
1. Variables should not be allowed to change their type.
2. Objects containing the same values should be equal by default.
3. Comparing objects of different types is a compile-time error.
4. Objects must always be initialized to a valid state. Not doing so is a compile-time error.
5. Once created, objects and collections must be immutable.
Time for a quick joke about immutability:
"How many Haskell programmers does it take to change a lightbulb?"
"Haskell programmers don't "change" lightbulbs, they "replace" them. And you must
also replace the whole house at the same time."
Almost done now -- just one more!
Example 6
In this final example, we'll try to fetch a customer from a CustomerRepository .
// create a repository
var repo = new CustomerRepository();
// find a customer by id
var customer = repo.GetById(42);
// what is the expected output?
Console.WriteLine(customer.Id);
1882
1883
1884
// create a repository
var repo = new CustomerRepository();
// find a customer by id and
// return a CustomerOrError result
var customerOrError = repo.GetById(42);
The code that processed this "customerOrError" result would then have to test what kind of
result it was, and handle each case separately, like this:
// handle both cases
if (customerOrError.IsCustomer)
Console.WriteLine(customerOrError.Customer.Id);
if (customerOrError.IsError)
Console.WriteLine(customerOrError.ErrorMessage);
This is exactly the approach taken by most functional languages. It does help if the language
provides conveniences to make this technique easier, such as sum types, but even without
that, this approach is still the only way to go if you want to make it obvious what your code is
doing. (You can read more about this technique here.)
So that's the last two items to add to our list, at least for now.
How to make your language predictable:
1. Variables should not be allowed to change their type.
2. Objects containing the same values should be equal by default.
3. Comparing objects of different types is a compile-time error.
4. Objects must always be initialized to a valid state. Not doing so is a compile-time error.
5. Once created, objects and collections must be immutable.
6. No nulls allowed.
7. Missing data or errors must be made explicit in the function signature.
I could go on, with snippets demonstrating the misuse of globals, side-effects, casting, and
so on. But I think I'll stop here -- you've probably got the idea by now!
1885
1886
In fact, no language can be reasoned about perfectly and still be practical. But still, some
languages are certainly more reasonable than others.
I think that one of the reasons why many people have become so enthusiastic about
functional-style code (and call it "simple" even though it's full of strange symbols!) is exactly
this: immutability, and lack of side effects, and all the other functional principles, act together
to enforce this reasonability and predictability, which in turn helps to reduce your cognitive
burden so that you need only focus on the code in front of you.
Questions
Let me see if I can prempt some questions...
Question: These examples are very contrived! If you code carefully and follow good
practices, you can write safe code without these features!
Yes, you can. I'm not claiming you can't. But this post is not about writing safe code, it's
about reasoning about the code. There is a difference.
And it's not about what you can do if you are careful. It's about what can happen if you are
not careful!
That is, does your programming language (not your coding guidelines, or tests, or IDE, or
development practices) give you support for reasoning about your code?
Question: You're telling me that a language should have these features. Isn't that very
arrogant of you?
Please read carefully. I am not saying that at all. What I am saying is that:
IF you want to be able to reason about your code, THEN it will be much easier if your
1887
1888
Summary
I said just above that this post is not trying to persuade you to pick a language based on
"reasonability" alone. But that's not quite true.
If you have already picked a statically typed, high-level language such as C# or Java, then
it's clear that reasonability or something like it was an important criterion in your language
decision.
In that case, I hope that the examples in this post might have made you more willing to
consider using an even more "reasonable" language on your platform of choice (.NET or
JVM).
The argument for staying put -- that your current language will eventually "catch up" -- may
be true purely in terms of features, but no amount of future enhancements can really change
the core design decisions in an OO language. You'll never get rid of nulls, or mutability, or
having to override equality all the time.
What's nice about F#, or Scala/Clojure, is that these functional alternatives don't require you
to change your ecosystem, but they do immediately improve your code quality.
In my opinion, it's quite a low risk compared with the cost of business as usual.
(I'll leave the issue of finding skilled people, training, support, etc, for another post. But see
this, this, this, and this if you're worried about hiring)
1889
Which is of course is a misquote of this famous scene. Oops, I mean this one.
Ok, I might be exaggerating a bit. Some UML diagrams are useful (I like sequence diagrams
for example) and in general, I do think a good picture or diagram can be worth 1000 words.
But I believe that, in many cases, using UML for class diagrams is not necessary.
Instead, a concise language like F# (or OCaml or Haskell) can convey the same meaning in
a way that is easier to read, easier to write, and most important, easier to turn into working
code!
With UML diagrams, you need to translate them to code, with the possibility of losing
something in translation. But if the design is documented in your programming language
itself, there is no translation phase, and so the design must always be in sync with the
implementation.
To demonstrate this in practice, I decided to scour the internet for some good (and not-sogood) UML class diagrams, and convert them into F# code. You can compare them for
yourselves.
Regular expressions
1890
Student enrollment
Here's another classic one: enrollment (source).
Here's the UML diagram:
1891
1892
type Student = {
Name: string
Address: string
PhoneNumber: string
EmailAddress: string
AverageMark: float
}
type Professor= {
Name: string
Address: string
PhoneNumber: string
EmailAddress: string
Salary: int
}
type Seminar = {
Name: string
Number: string
Fees: float
TaughtBy: Professor option
WaitingList: Student list
}
type Enrollment = {
Student : Student
Seminar : Seminar
Marks: float list
}
type EnrollmentRepository = Enrollment list
// ==================================
// activities / use-cases / scenarios
// ==================================
type IsElegibleToEnroll = Student -> Seminar -> bool
type GetSeminarsTaken = Student -> EnrollmentRepository -> Seminar list
type AddStudentToWaitingList = Student -> Seminar -> Seminar
The F# mirrors the UML diagram, but I find that by writing functions for all the activities
rather than drawing pictures, holes in the original requirements are revealed.
For example, in the GetSeminarsTaken method in the UML diagram, where is the list of
seminars stored? If it is in the Student class (as implied by the diagram) then we have a
mutual recursion between Student and Seminar and the whole tree of every student and
seminar is interconnected and must be loaded at the same time unless hacks are used.
Instead, for the functional version, I created an EnrollmentRepository to decouple the two
classes.
1893
Similarly, it's not clear how enrollment actually works, so I created an EnrollStudent
function to make it clear what inputs are needed.
type EnrollStudent = Student -> Seminar -> Enrollment option
Because the function returns an option , it is immediately clear that enrollment might fail
(e.g student is not eligible to enroll, or is enrolling twice by mistake).
1894
I'm just copying the UML diagram, but I have to say that I hate this design. It's crying out to
have more fine grained states.
In particular, the Confirm and Dispatch functions are horrible -- they give no idea of what
else is needed as input or what the effects will be. This is where writing real code can force
you to think a bit more deeply about the requirements.
1895
// == Customer related ==
type Customer = {
name:string
address:string
}
// == Item related ==
type [<Measure>] grams
type Item = {
shippingWeight: int<grams>
description: string
}
type Qty = int
type Price = decimal
// == Payment related ==
type PaymentMethod =
| Cash
| Credit of number:string * cardType:string * expDate:Date
| Check of name:string * bankID: string
type Payment = {
amount: decimal
paymentMethod : PaymentMethod
}
// == Order related ==
type TaxStatus = Taxable | NonTaxable
type Tax = decimal
type OrderDetail = {
item: Item
qty: int
taxStatus : TaxStatus
}
type OrderStatus = Open | Completed
type Order = {
date: DateTime;
customer: Customer
status: OrderStatus
lines: OrderDetail list
payments: Payment list
}
1896
// ==================================
// activities / use-cases / scenarios
// ==================================
type GetPriceForQuantity = Item -> Qty -> Price
type CalcTax = Order -> Tax
type CalcTotal = Order -> Price
type CalcTotalWeight = Order -> int<grams>
I've done some minor tweaking, adding units of measure for the weight, creating types to
represent Qty and Price .
Again, this design might be improved with more fine grained states, such as creating a
separate AuthorizedPayment type (to ensure that an order can only be paid with authorized
payments) and a separate PaidOrder type (e.g. to stop you paying for the same order
twice).
Here's the kind of thing I mean:
// Try to authorize a payment. Note that it might fail
type Authorize = UnauthorizedPayment -> AuthorizedPayment option
// Pay an unpaid order with an authorized payment.
type PayOrder = UnpaidOrder -> AuthorizedPayment -> PaidOrder
Hotel Booking
Here's one from the JetBrains IntelliJ documentation (source).
1897
1898
price: decimal
}
type CreditCardInfo = {
card: string
name: string
expiryMonth: int
expiryYear: int
}
type Booking = {
id: int
user: User
hotel: Hotel
checkinDate: Date
checkoutDate: Date
creditCardInfo: CreditCardInfo
smoking: bool
beds: int
}
// What are these?!? And why are they in the domain?
type EntityManager = unit
type FacesMessages = unit
type Events = unit
type Log = unit
type BookingAction = {
em: EntityManager
user: User
hotel: Booking
booking: Booking
facesMessages : FacesMessages
events: Events
log: Log
bookingValid: bool
}
type ChangePasswordAction = {
user: User
em: EntityManager
verify: string
booking: Booking
changed: bool
facesMessages : FacesMessages
}
type RegisterAction = {
user: User
em: EntityManager
facesMessages : FacesMessages
verify: string
registered: bool
1899
I have to stop there, sorry. The design is driving me crazy. I can't even.
What are these EntityManager and FacesMessages fields? And logging is important of
course, but why is Log a field in the domain object?
By the way, in case you think that I am deliberately picking bad examples of UML design, all
these diagrams come from the top results in an image search for "uml class diagram".
Library
This one is better, a library domain (source).
Here's the F# equivalent. Note that because it is code, I can add comments to specific types
and fields, which is doable but awkward with UML.
1900
Note also that I can say ISBN: string option to indicate an optional ISBN rather that the
awkward [0..1] syntax.
type Author = {
name: string
biography: string
}
type Book = {
ISBN: string option
title: string
author: Author
summary: string
publisher: string
publicationDate: Date
numberOfPages: int
language: string
}
type Library = {
name: string
address: string
}
// Each physical library item - book, tape cassette, CD, DVD, etc. could have its own
item number.
// To support it, the items may be barcoded. The purpose of barcoding is
// to provide a unique and scannable identifier that links the barcoded physical item
// to the electronic record in the catalog.
// Barcode must be physically attached to the item, and barcode number is entered into
// the corresponding field in the electronic item record.
// Barcodes on library items could be replaced by RFID tags.
// The RFID tag can contain item's identifier, title, material type, etc.
// It is read by an RFID reader, without the need to open a book cover or CD/DVD case
// to scan it with barcode reader.
type BookItem = {
barcode: string option
RFID: string option
book: Book
/// Library has some rules on what could be borrowed and what is for reference onl
y.
isReferenceOnly: bool
belongsTo: Library
}
type Catalogue = {
belongsTo: Library
records : BookItem list
}
type Patron = {
1901
name: string
address: string
}
type AccountState = Active | Frozen | Closed
type Account = {
patron: Patron
library: Library
number: int
opened: Date
/// Rules are also defined on how many books could be borrowed
/// by patrons and how many could be reserved.
history: History list
state: AccountState
}
and History = {
book : BookItem
account: Account
borrowedOn: Date
returnedOn: Date option
}
Since the Search and Manage interfaces are undefined, we can just use placeholders
( unit ) for the inputs and outputs.
type Librarian = {
name: string
address: string
position: string
}
/// Either a patron or a librarian can do a search
type SearchInterfaceOperator =
| Patron of Patron
| Librarian of Librarian
type SearchRequest = unit // to do
type SearchResult = unit // to do
type SearchInterface = SearchInterfaceOperator -> Catalogue -> SearchRequest -> Search
Result
type ManageRequest = unit // to do
type ManageResult = unit // to do
/// Only librarians can do management
type ManageInterface = Librarian -> Catalogue -> ManageRequest -> ManageResult
1902
Again, this might not be the perfect design. For example, it's not clear that only Active
accounts could borrow a book, which I might represent in F# as:
type Account =
| Active of ActiveAccount
| Closed of ClosedAccount
/// Only ActiveAccounts can borrow
type Borrow = ActiveAccount -> BookItem -> History
If you want to see a more modern approach to modelling this domain using CQRS and event
sourcing, see this post.
Software licensing
The final example is from a software licensing domain (source).
1903
1904
// ==========================
// Product-related
// ==========================
/// Flags can be ORed together
[<Flags>]
type LockingType =
| HL
| SL_AdminMode
| SL_UserMode
type Rehost =
| Enable
| Disable
| LeaveAsIs
| SpecifyAtEntitlementTime
type BatchCode = {
id : String5
}
type Feature = {
id : int
name : String50
description : string option
}
type ProductInfo = {
id : int
name : String50
lockingType : LockingType
rehost : Rehost
description : string option
features: Feature list
bactchCode: BatchCode
}
type Product =
| BaseProduct of ProductInfo
| ProvisionalProduct of ProductInfo * baseProduct:Product
// ==========================
// Entitlement-related
// ==========================
type EntitlementType =
| HardwareKey
| ProductKey
| ProtectionKeyUpdate
type Entitlement = {
EID : string
entitlementType : EntitlementType
1905
startDate : Date
endDate : Date option
neverExpires: bool
comments: string option
customer: Customer
products: Product list
}
This diagram is just pure data and no methods, so there are no function types. I have a
feeling that there are some important business rules that have not been captured.
For example, if you read the comments in the source, you'll see that there are some
interesting constraints around EntitlementType and LockingType . Only certain locking
types can be used with certain entitlement types.
That might be something that we could consider modelling in the type system, but I haven't
bothered. I've just tried to reproduct the UML as is.
Summary
I think that's enough to get the idea.
My general feeling about UML class diagrams is that they are OK for a sketch, if a bit
heavyweight compared to a few lines of code.
For detailed designs, though, they are not nearly detailed enough. Critical things like context
and dependencies are not at all obvious. In my opinion, none of the UML diagrams I've
shown have been good enough to write code from, even as a basic design.
Even more seriously, a UML diagram can be very misleading to non-developers. It looks
"official" and can give the impression that the design has been thought about deeply, when
in fact the design is actually shallow and unusable in practice.
Disagree? Let me know in the comments!
1906
And indeed, C is a very extrovert language. Coding examples are littered with files and
console IO. Similarly, you can tell that PHP, Python and Perl are equally extrovert with just
one glance at their manuals.
In fact, I would say that all of the most popular languages are extrovert, and the reasons are
obvious. They ooze confidence, they make friends easily, and they get things done.
On the other hand, I would say that Haskell is a great example of an introvert language.
For example, in the book "Learn You A Haskell" the "hello world" example doesn't appear
until chapter 9! And in "Real World Haskell", IO is not invited to dinner until chapter 7.
If you don't have the full manual handy, another telling clue that a language is introverted is if
it early on introduces you to its close friend, the Fibonacci function. Introvert languages love
recursion!
1907
Now, just as in the real world, introvert languages are misunderstood by extroverts. They are
accused of being too arrogant, too serious, too risk-averse.
But that's not fair -- introvert languages are really just more reflective and thoughtful, and
thus more likely to have deep insights than the shallow, yapping extroverts.
But...
"All generalisations are false including this one"
You might think that imperative and OO languages would be extrovert, while languages with
more declarative paradigms (functional, logic) would be introvert, but that is not always the
case.
For example, SQL is a declarative language, but its whole purpose in life is data-processing,
which makes it extrovert in my book.
And nearer to home, F# is a functional-first language, but is very happy to do IO, and in fact
has excellent support for real-world data processing via type providers and Deedle.
Just as people are not all one or the other, so with programming languages. There is a
range. Some languages are extremely extrovert, some extremely introvert, and some inbetween.
1908
In their defence, many important programming concepts were first developed in solipsistic
languages. But alas, despite their ardent followers, they never gain the widespread
recognition they deserve.
1909
and by doing this, I guarantee that I can't use an OrderId where I need an CustomerId .
The problem is that adding a layer of indirection like this can affect performance:
the extra indirection can cause data access to be much slower.
the wrapper class needs extra memory, creating memory pressure.
this in turn triggers the garbage collector more often, which can often be the cause of
performance problems in managed code.
In general, I don't generally worry about micro-performance like this at design-time. Many
many things will have a much bigger impact on performance, including any kind of I/O, and
the algorithms you choose.
As a result, I am very much against doing micro-benchmarks out of context. You should
always profile a real app in a real context, rather than worrying too much over things that
might not be important.
Having said that, I am now going to do some micro-benchmarks!
1910
Let's see how a wrapper type fares when used in large numbers. Let's say we want to:
create ten million customer ids
then, map over them twice
then, filter them
Admittedly, it's a bit silly adding 1 to a customer id -- we'll look at a better example later.
Anyway, here's the code:
// type is an wrapper for a primitive type
type CustomerId = CustomerId of int
// create two silly functions for mapping and filtering
let add1ToCustomerId (CustomerId i) =
CustomerId (i+1)
let isCustomerIdSmall (CustomerId i) =
i < 100000
// --------------------------------// timed with a 1 million element array
// --------------------------------#time
Array.init 1000000 CustomerId
// map it
|> Array.map add1ToCustomerId
// map it again
|> Array.map add1ToCustomerId
// filter it
|> Array.filter isCustomerIdSmall
|> ignore
#time
That is, it takes about 0.3 seconds to do those steps, and it creates quite a bit of garbage,
triggering four gen1 collections. If you are not sure what "gen0", "gen1", and "gen2" mean,
then this is a good place to start.
1911
That is, it takes about 3.5 seconds to do those steps, and it creates a lot of garbage,
including a few gen2 GC's, which are really bad. In fact, you might even get an "out of
memory" exception, in which case, you'll have to restart F# Interactive!
So what are the alternatives to using a wrapper? There are two common approaches:
Using type aliases
Using units of measure
Let's start with type aliases.
If I want to use the type as documentation, I must then annotate the functions appropriately.
For example, in the add1ToCustomerId below both the parameter and the return value have
been annotated so that it has the type CustomerId -> CustomerId rather than int -> int .
let add1ToCustomerId (id:CustomerId) :CustomerId =
id+1
1912
It takes about 17 milliseconds to do those steps, and more importantly, very little garbage
was generated.
If we increase the array size to 10 million, we get a 10x slower result, but still no garbage:
Real: 00:00:00.166, CPU: 00:00:00.156, GC gen0: 0, gen1: 0, gen2: 0
Compared with the earlier version at over three seconds, that's excellent.
1913
And sadly, the two ids compare equal, and we can pass an OrderId to function expecting a
CustomerId without any complaint from the compiler.
CustomerId and OrderId are still two different types, but the unit of measure is erased, so
by the time the JIT sees it the type looks like an primitive int.
We can see that this is true when we time the same steps as before:
1914
Again, the code is very fast (22 milliseconds), and just as importantly, very little garbage was
generated again.
If we increase the array size to 10 million, we maintain the high performance (just as with the
type alias approach) and still no garbage:
Real: 00:00:00.157, CPU: 00:00:00.156, GC gen0: 0, gen1: 0, gen2: 0
1915
Three what though? Three customer ids per order id? What does that even mean?
Yes, surely this will never happen in practice, but still it bothers me!
1916
You can see that for both versions I've created a constructor createCustomerId and a getter
customerIdValue and, for the type alias version, an active pattern that looks just like
CustomerId .
With this code in place, we can use CustomerId without caring about the implementation:
1917
And now we can run the same micro-benchmark with both implementations:
// create two silly functions for mapping and filtering
let add1ToCustomerId (CustomerId i) =
createCustomerId (i+1)
let isCustomerIdSmall (CustomerId i) =
i < 100000
// --------------------------------// timed with a 1 million element array
// --------------------------------#time
Array.init 1000000 createCustomerId
// map it
|> Array.map add1ToCustomerId
// map it again
|> Array.map add1ToCustomerId
// filter it
|> Array.filter isCustomerIdSmall
|> Array.length
#time
1918
1919
module EmailAddress =
// type with private constructor
type EmailAddress = private EmailAddress of string
// safe constructor
let create s =
if System.String.IsNullOrWhiteSpace(s) then
None
else if s.Contains("@") then
Some (EmailAddress s)
else
None
// get data
let value (EmailAddress s) = s
module ActivityHistory =
open EmailAddress
// type with private constructor
type ActivityHistory = private {
emailAddress : EmailAddress
visits : int
}
// safe constructor
let create email visits =
{emailAddress = email; visits = visits }
// get data
let email {emailAddress=e} = e
let visits {visits=a} = a
As before, for each type there is a constructor and a getter for each field.
NOTE: Normally I would define a type outside a module, but because the real constructor
needs to be private, I've put the type inside the module and given the module and the type
the same name. If this is too awkward, you can rename the module to be different from the
type, or use the OCaml convention of calling the main type in a module just "T", so you get
EmailAddress.T as the type name.
To make a more performant version, we replace EmailAddress with a type alias, and
Activity with a struct, like this:
1920
module EmailAddress =
// aliased type
type EmailAddress = string
// safe constructor
let inline create s :EmailAddress option =
if System.String.IsNullOrWhiteSpace(s) then
None
else if s.Contains("@") then
Some s
else
None
// get data
let inline value (e:EmailAddress) :string = e
module ActivityHistory =
open EmailAddress
[<Struct>]
type ActivityHistory(emailAddress : EmailAddress, visits : int) =
member this.EmailAddress = emailAddress
member this.Visits = visits
// safe constructor
let create email visits =
ActivityHistory(email,visits)
// get data
let email (act:ActivityHistory) = act.EmailAddress
let visits (act:ActivityHistory) = act.Visits
This version reimplements the constructor and a getter for each field. I could have made the
field names for ActivityHistory be the same in both cases too, but. in the struct case, type
inference would not work. By making them different, the user is forced to use the getter
functions rather than dotting in.
Both implementations have the same "API", so we can create code that works with both:
1921
Rather than using #time , this time I wrote a custom timer that runs a function 10 times and
prints out the GC and memory used on each run.
/// Do countN repetitions of the function f and print the
/// time elapsed, number of GCs and change in total memory
let time countN label f =
let stopwatch = System.Diagnostics.Stopwatch()
// do a full GC at the start but NOT thereafter
// allow garbage to collect for each iteration
System.GC.Collect()
printfn "Started"
let getGcStats() =
let gen0 = System.GC.CollectionCount(0)
let gen1 = System.GC.CollectionCount(1)
let gen2 = System.GC.CollectionCount(2)
let mem = System.GC.GetTotalMemory(false)
gen0,gen1,gen2,mem
printfn "======================="
printfn "%s (%s)" label WrappedOrAliased
printfn "======================="
for iteration in [1..countN] do
let gen0,gen1,gen2,mem = getGcStats()
stopwatch.Restart()
f()
stopwatch.Stop()
let gen0',gen1',gen2',mem' = getGcStats()
// convert memory used to K
let changeInMem = (mem'-mem) / 1000L
printfn "#%2i elapsed:%6ims gen0:%3i gen1:%3i gen2:%3i mem:%6iK" iteration sto
pwatch.ElapsedMilliseconds (gen0'-gen0) (gen1'-gen1) (gen2'-gen2) changeInMem
1923
=======================
mapAndFilter: 1000000 records (Wrapped)
=======================
# 1 elapsed: 820ms gen0: 13 gen1: 8 gen2: 1 mem: 72159K
# 2 elapsed: 878ms gen0: 12 gen1: 7 gen2: 0 mem: 71997K
# 3 elapsed: 850ms gen0: 12 gen1: 6 gen2: 0 mem: 72005K
# 4 elapsed: 885ms gen0: 12 gen1: 7 gen2: 0 mem: 72000K
# 5 elapsed: 6690ms gen0: 16 gen1: 10 gen2: 1 mem:-216005K
# 6 elapsed: 714ms gen0: 12 gen1: 7 gen2: 0 mem: 72003K
# 7 elapsed: 668ms gen0: 12 gen1: 7 gen2: 0 mem: 71995K
# 8 elapsed: 670ms gen0: 12 gen1: 7 gen2: 0 mem: 72001K
# 9 elapsed: 6676ms gen0: 16 gen1: 11 gen2: 2 mem:-215998K
#10 elapsed: 712ms gen0: 13 gen1: 7 gen2: 0 mem: 71998K
=======================
mapAndFilter: 1000000 records (Aliased)
=======================
# 1 elapsed: 193ms gen0: 7 gen1: 0 gen2: 0 mem: 25325K
# 2 elapsed: 142ms gen0: 8 gen1: 0 gen2: 0 mem: 23779K
# 3 elapsed: 143ms gen0: 8 gen1: 0 gen2: 0 mem: 23761K
# 4 elapsed: 138ms gen0: 8 gen1: 0 gen2: 0 mem: 23745K
# 5 elapsed: 135ms gen0: 7 gen1: 0 gen2: 0 mem: 25327K
# 6 elapsed: 135ms gen0: 8 gen1: 0 gen2: 0 mem: 23762K
# 7 elapsed: 137ms gen0: 8 gen1: 0 gen2: 0 mem: 23755K
# 8 elapsed: 140ms gen0: 8 gen1: 0 gen2: 0 mem: 23777K
# 9 elapsed: 174ms gen0: 7 gen1: 0 gen2: 0 mem: 25327K
#10 elapsed: 180ms gen0: 8 gen1: 0 gen2: 0 mem: 23762K
Now this code no longer consists of only value types, so the profiling is getting muddier now!
The mapAndFilter function uses createCustomerWithRandomActivity which in turn uses
Option , a reference type, so there will be a large number of reference types being
1924
My suggestion is to turn the DU into a struct with a tag for each case, and fields for all
possible data.
For example, let's say that we have DU that classifies an Activity into Active and
Inactive , and for the Active case we store the email and visits and for the inactive case
1925
Note that Visits is not used in the Inactive case, so is set to a default value.
Now let's create a function that classifies the activity history, creates a Classification and
then filters and extracts the email only for active customers.
open Classification
let createClassifiedCustomer activity =
let email = ActivityHistory.email activity
let visits = ActivityHistory.visits activity
if isCustomerInactive activity then
Classification.createInactive email
else
Classification.createActive email visits
// execute creation and iteration for a large number of records
let extractActiveEmails noOfRecords =
Array.init noOfRecords (fun _ -> createCustomerWithRandomActivityHistory() )
// map to a classification
|> Array.map createClassifiedCustomer
// extract emails for active customers
|> Array.choose (function
| Active (email,visits) -> email |> Some
| Inactive _ -> None )
|> ignore
1926
=======================
extractActiveEmails: 1000000 records (Wrapped)
=======================
# 1 elapsed: 664ms gen0: 12 gen1: 6 gen2: 0 mem: 64542K
# 2 elapsed: 584ms gen0: 14 gen1: 7 gen2: 0 mem: 64590K
# 3 elapsed: 589ms gen0: 13 gen1: 7 gen2: 0 mem: 63616K
# 4 elapsed: 573ms gen0: 11 gen1: 5 gen2: 0 mem: 69438K
# 5 elapsed: 640ms gen0: 15 gen1: 7 gen2: 0 mem: 58464K
# 6 elapsed: 4297ms gen0: 13 gen1: 7 gen2: 1 mem:-256192K
# 7 elapsed: 593ms gen0: 14 gen1: 7 gen2: 0 mem: 64623K
# 8 elapsed: 621ms gen0: 13 gen1: 7 gen2: 0 mem: 63689K
# 9 elapsed: 577ms gen0: 11 gen1: 5 gen2: 0 mem: 69415K
#10 elapsed: 609ms gen0: 15 gen1: 7 gen2: 0 mem: 58480K
=======================
extractActiveEmails: 1000000 records (Aliased)
=======================
# 1 elapsed: 254ms gen0: 32 gen1: 1 gen2: 0 mem: 33162K
# 2 elapsed: 221ms gen0: 33 gen1: 0 gen2: 0 mem: 31532K
# 3 elapsed: 196ms gen0: 32 gen1: 0 gen2: 0 mem: 33113K
# 4 elapsed: 185ms gen0: 33 gen1: 0 gen2: 0 mem: 31523K
# 5 elapsed: 187ms gen0: 33 gen1: 0 gen2: 0 mem: 31532K
# 6 elapsed: 186ms gen0: 32 gen1: 0 gen2: 0 mem: 33095K
# 7 elapsed: 191ms gen0: 33 gen1: 0 gen2: 0 mem: 31514K
# 8 elapsed: 200ms gen0: 32 gen1: 0 gen2: 0 mem: 33096K
# 9 elapsed: 189ms gen0: 33 gen1: 0 gen2: 0 mem: 31531K
#10 elapsed: 3732ms gen0: 33 gen1: 1 gen2: 1 mem:-256432K
As before, the aliased/struct version is more performant, being faster and generating less
garbage (although there was a GC pause at the end, oh dear).
Questions
Isn't this a lot of work, creating two implementations?
Yes! I don't think you should do this in general. This is just an experiment on my part.
I suggest that turning records and DUs into structs is a last resort, only done after you have
eliminated all other bottlenecks first.
However, there may be a few special cases where speed and memory are critical, and then,
perhaps, it might be worth doing something like this.
1927
Well, because the types are essentially private, we do lose some of the nice syntax available
when you have access to the internals of the type, such as {rec with ...} , but as I said,
you should really only be using this technique with small records anyway.
More importantly, value types like structs are not a silver bullet. They have their own
problems.
For example, they can be slower when passed as arguments (because of copy-by-value)
and you must be careful not to box them implicitly, otherwise you end up doing allocations
and creating garbage. Microsoft has guidelines on using classes vs structs, but see also this
commentary on breaking these guidelines and these rules.
1928
Make all I/O async. Use streaming IO over random access IO if possible. Batch up your
requests.
Check your algorithms. Anything worse than O(n log(n)) should be looked at.
Don't do things twice. Cache as needed.
Keep things in the CPU cache by keeping objects in contiguous memory and avoiding
too many deep reference (pointer) chains. Things that help with this are using arrays
instead of lists, value types instead of reference types, etc.
Avoid pressure on the garbage collector by minimizing allocations. Avoid creating longlived objects that survive gen0 collections.
To be clear, I don't claim to be an expert on .NET performance and garbage collection. In
fact, if you see something wrong with this analysis, please let me know!
Here are some sources that helped me:
The book Writing High-Performance .NET Code by Ben Watson.
Martin Thompson has a great blog on performance and some excellent videos, such as
Top 10 Performance Folklore. (Good summary here.)
Understanding Latency, a video by Gil Tene.
Essential Truths Everyone Should Know about Performance in a Large Managed
Codebase, a video by Dustin Cambell at Microsoft.
For F# in particular:
Yan Cui has some blog posts on records vs structs and memory layout.
Jon Harrop has a number of good articles such as this one but some of it is behind
a paywall.
Video: High Performance F# in .NET and on the GPU with Jack Pappas. The
sound is bad, but the slides and discussion are good!
Resources for Math and Statistics on fsharp.org
Summary
"Keep it clean; keep it simple; aim to be elegant." -- Martin Thompson
This was a little experiment to see if I could have my cake and eat it too. Domain modelling
using lots of types, but with the ability to get performance when needed in an elegant way.
I think that this is quite a nice solution, but as I said earlier, this optimization (and uglification)
should only ever be needed for a small number of heavily used core types that are allocated
many millions of times.
Finally, I have not used this approach myself in a large production system (I've never needed
to), so I would be interested in getting feedback from people in the trenches on what they do.
1929
1930