Programming F# (Chris Smith)
Programming F# (Chris Smith)
Programming F#
Chris Smith
foreword by Don Syme
Published by O’Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472.
O’Reilly books may be purchased for educational, business, or sales promotional use. Online editions
are also available for most titles (http://my.safaribooksonline.com). For more information, contact our
corporate/institutional sales department: (800) 998-9938 or [email protected].
Printing History:
October 2009: First Edition.
Nutshell Handbook, the Nutshell Handbook logo, and the O’Reilly logo are registered trademarks of
O’Reilly Media, Inc. Programming F#, the image of a bullfinch, and related trade dress are trademarks
of O’Reilly Media, Inc.
Many of the designations used by manufacturers and sellers to distinguish their products are claimed as
trademarks. Where those designations appear in this book, and O’Reilly Media, Inc., was aware of a
trademark claim, the designations have been printed in caps or initial caps.
While every precaution has been taken in the preparation of this book, the publisher and author assume
no responsibility for errors or omissions, or for damages resulting from the use of the information con-
tained herein.
TM
ISBN: 978-0-596-15364-9
[M]
1254512912
Table of Contents
Foreword . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii
Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xv
1. Introduction to F# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Getting to Know F# 3
Visual Studio 2010 4
Your Second F# Program 5
Values 6
Whitespace Matters 6
.NET Interop 8
Comments 8
F# Interactive 8
Managing F# Source Files 11
2. Fundamentals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Primitive Types 13
Numeric Primitives 14
Arithmetic 15
Conversion Routines 17
BigInt 17
Bitwise Operations 18
Characters 18
Strings 19
Boolean Values 20
Comparison and Equality 21
Functions 22
Type Inference 23
v
Generic Functions 24
Scope 25
Control Flow 27
Core Types 29
Unit 29
Tuple 30
Lists 32
Aggregate Operators 36
Option 39
Printfn 41
Anatomy of an F# Program 42
Modules 43
Namespaces 43
Program Startup 44
3. Functional Programming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
Programming with Functions 48
Immutability 49
Function Values 50
Recursive Functions 53
Symbolic Operators 55
Function Composition 57
Pattern Matching 62
Match Failure 64
Named Patterns 64
Matching Literals 65
when Guards 66
Grouping Patterns 67
Matching the Structure of Data 68
Outside of Match Expressions 69
Alternate Lambda Syntax 70
Discriminated Unions 70
Using Discriminated Unions for Tree Structures 72
Pattern Matching 73
Methods and Properties 75
Records 75
Cloning Records 76
Pattern Matching 77
Type Inference 77
Methods and Properties 78
Lazy Evaluation 79
Lazy Types 79
Sequences 80
vi | Table of Contents
Sequence Expressions 81
Seq Module Functions 82
Aggregate Operators 83
4. Imperative Programming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
Understanding Memory in .NET 86
Value Types Versus Reference Types 87
Default Values 87
Reference Type Aliasing 89
Changing Values 89
Reference Cells 91
Mutable Records 92
Arrays 92
Indexing an Array 93
Array Slices 95
Creating Arrays 96
Pattern Matching 97
Array Equality 97
Array Module Functions 98
Multidimensional Arrays 99
Mutable Collection Types 101
List<'T> 101
Dictionary<'K,'V> 102
HashSet<'T> 104
Looping Constructs 105
While Loops 105
For Loops 106
Exceptions 108
Handling Exceptions 109
Reraising Exceptions 111
Defining Exceptions 111
9. Scripting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
F# Script Files 230
Table of Contents | ix
Directives 230
General Directives 231
F# Script-Specific Directives 231
F# Script Recipes 234
Colorful Output 234
Producing Sound 235
Walking a Directory Structure 235
Starting Processes Easily 236
Automating Microsoft Office 237
x | Table of Contents
Dynamic Instantiation 298
Instantiating Types 298
Instantiating F# Types 299
Dynamic Invocation 299
The Question Mark Operators 300
Using Reflection 301
Declarative Programming 302
Plug-in Architecture 305
B. F# Interop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371
Table of Contents | xi
Foreword
This book marks a transition point in the history of the F# language. From its origins
as a research project at Microsoft Research, Cambridge, and its long heritage from
languages such as OCaml and Haskell, F# has now emerged as a stable, efficient, and
enjoyable productivity tool for compositional and succinct programming on the .NET
platform. With the release of Visual Studio 2010, a whole new generation of program-
mers will have the language available to them as they build the future through the
software they design, whether it be through beautiful code, groundbreaking software
frameworks, high-performance websites, new software methodologies, better numer-
ical algorithms, great testing, intelligent parallelization, better modelling, or any num-
ber of the other manifestations of “good programming” with F#. As the designer of
F#, I am thrilled to see Chris Smith, a major contributor on the F# team, present F#
in a way that is accessible to a wide audience.
F# combines the simplicity and elegance of typed functional programming with the
strengths of the .NET platform. Although typed functional programming is relatively
new to many programmers and thus requires some learning, it makes programming
simpler in many ways. F# programs tend to be built from compositional, correct foun-
dational elements, and type inference makes programs shorter and clearer. In this book,
Chris first introduces the foundational paradigms of F#: functional programming, im-
perative programming, and object-oriented programming, and emphasizes how these
can be used together. His focus is on simple, clear explanations of the foundational
elements of the language, with an eye on the enjoyment that comes from programming
in general, and programming with F# in particular.
When used at its best, F# makes things simple. For example, F# units of measure are
covered in this book and are a landmark feature in the history of programming lan-
guages, taming much of the complexity of working with floating-point numbers in
common application areas. Furthermore, the huge, generation-defining shift toward
asynchronous and parallel processing forms part of the background to the development
of F#. In this book, Chris tackles the foundations F# and .NET provide to help you
work in this world. Likewise, F# has great strengths in language-oriented program-
ming, a topic dear to Chris’s heart, and again well covered here.
xiii
Above all, F# is a practical language, and Chris has ensured that the reader is well
equipped with the information needed to use the current generation of F# tools well,
with a notable emphasis on F# scripting. Chris’s book describes F# “1.0” as it appears
in Visual Studio 2010, the first officially supported release of the language. The F# team
and I are very grateful to Chris for his contributions to the development of the language
and its tools. I hope you enjoy your programming with F# as much as Chris and I have
enjoyed working as part of the F# team.
—Don Syme
Principal Researcher and F# Designer, Microsoft Research
xiv | Foreword
Preface
There is a new language in Visual Studio called F#. It will help you write more expres-
sive, easier-to-maintain code while enabling you to take advantage of the breadth of
the .NET platform. Learning F# will not only make you more productive, it will also
make you a better programmer. Once you have mastered concepts such as functional
programming introduced in F#, you can apply them to programs written in other lan-
guages, as well as have a new perspective on programming.
Introducing F#
So what actually is F#? In a nutshell, F# is a multiparadigm programming language
built on .NET, meaning that it supports several different styles of programming natively.
I’ll spare you the history of the language and instead just go over the big bullets:
• F# supports functional programming, which is a style of programming that em-
phasizes what a program should do, not explicitly how the program should work.
• F# supports object-oriented programming. In F#, you can abstract code into
classes and objects, enabling you to simplify your code.
• F# supports imperative programming. In F# you can modify the contents of mem-
ory, read and write files, send data over the network, and so on.
• F# is statically typed. Being statically typed means that type information is known
at compile time, leading to type-safe code. F# won’t allow you to put a square peg
into a round hole.
• F# is a .NET language. It runs on the Common Language Infrastructure (CLI) and
so it gets things like garbage collection (memory management) and powerful class
libraries for free. F# also supports all .NET concepts natively, such as delegates,
enumerations, structures, P/Invoke, and so on.
Even without all the jargon, it is clear that F# is a broad and powerful language. But
don’t worry, we’ll cover it all step by step.
xv
Who This Book Is For
This book isn’t intended to be an introductory text on programming and assumes fa-
miliarity with basic concepts like looping, functions, and recursion. However, no pre-
vious experience with functional programming or .NET is required.
If you come from a C# or VB.NET background, you should feel right at home. While
F# approaches programming from a different viewpoint, you can apply all of your
existing .NET know-how to programming in F#.
If you come from an OCaml or Haskell background, the syntax of F# should look very
familiar. F# has most of the features of those languages, and adds many more to inte-
grate well with .NET.
xvi | Preface
Part I, Multiparadigm Programming
Chapter 1, Introduction to F#, presents the F# language and the Visual Studio 2010
integrated development environment (IDE). Even if you are familiar with Visual Studio,
I recommend you read this chapter, as F# has some unique characteristics when it
comes to building and running projects.
Chapter 2, Fundamentals, introduces the core types and concepts that will be the foun-
dation for all other chapters.
Chapter 3, Functional Programming, introduces functional programming and how to
write F# code using this style.
Chapter 4, Imperative Programming, describes how to mutate values and change pro-
gram state in an imperative manner.
Chapter 5, Object-Oriented Programming, covers object-oriented programming from
creating simple types to inheritance and polymorphism.
Chapter 6, .NET Programming, goes over some style-independent concepts exposed
by the .NET Framework and CLI.
Chapter 7, Applied Functional Programming, covers more advanced topics in functional
programming such as tail recursion and functional design patterns.
Chapter 8, Applied Object-Oriented Programming, describes how to develop and take
advantage of a rich type system. Special attention will be paid to how to leverage the
functional aspects of F# to make object-oriented code better.
Preface | xvii
Appendixes
This book also features a couple of appendixes to flesh out any extra concepts you
might be interested in.
Appendix A does a quick sweep through the existing technologies available on the .NET
platform and how to use them from F#.
Appendix B covers how to write F# to interoperate with existing libraries as well as
unmanaged code using P/Invoke and COM-interop.
Notes like this are used to add more detail for the curious reader.
Warnings are indicated in this style to help you avoid common mistakes.
How to Contact Us
Please address comments and questions concerning this book to the publisher:
O’Reilly Media, Inc.
1005 Gravenstein Highway North
Sebastopol, CA 95472
800-998-9938 (in the U.S. or Canada)
707-829-0515 (international/local)
707-829-0104 (fax)
xviii | Preface
Although we’ve tested and verified the information in this book, you may find that
some aspects of the F# language have changed since this writing (or perhaps even a
bug in an example!). We have a web page for the book, where we list examples and any
plans for future editions. You can access this page at:
http://oreilly.com/catalog/9780596153649
You can also send messages electronically. To be put on the mailing list or request a
catalog, send email to:
[email protected]
To comment on the book, send email to:
[email protected]
For information about this book and others, as well as additional technical articles and
discussion on F#, see the O’Reilly website:
http://www.oreilly.com
or the O’Reilly .NET DevCenter:
http://www.oreillynet.com/dotnet
or the Microsoft Developer Network portal for F#:
http://www.msdn.com/fsharp
Preface | xix
Safari® Books Online
Safari Books Online is an on-demand digital library that lets you easily
search over 7,500 technology and creative reference books and videos to
find the answers you need quickly.
With a subscription, you can read any page and watch any video from our library online.
Read books on your cell phone and mobile devices. Access new titles before they are
available for print, and get exclusive access to manuscripts in development and post
feedback for the authors. Copy and paste code samples, organize your favorites, down-
load chapters, bookmark key sections, create notes, print out pages, and benefit from
tons of other time-saving features.
O’Reilly Media has uploaded this book to the Safari Books Online service. To have full
digital access to this book and others on similar topics from O’Reilly and other pub-
lishers, sign up for free at http://my.safaribooksonline.com.
Acknowledgments
I get all the credit for writing this book, but the real effort for putting together the F#
language comes from F# teams at Microsoft in Redmond and Microsoft Research in
Cambridge, England. I’ve had immense pleasure working with a ridiculously talented
group of people and without their effort F# would just be a TODO sticky note on Don
Syme’s monitor.
Here are the people at Microsoft who made F# happen:
Anar Alimov Joe Pamer
Andrew Kennedy Jomo Fisher
Baofa Feng Kyle Ross
Brian McNamara Laurant Le Blun
Daniel Quirk Luke Hoban
Dmitry Lomov Matteo Taveggia
Dominic Cooney Ralf Herbrich
Don Syme Santosh Zachariah
Gordon Hogenson Sean Wang
Greg Neverov Timothy Ng
James Margetson Tomas Petricek
Also, from the amazing editors at O’Reilly to the outstanding people in the F# com-
munity, this book could not have been possible without the help of many others: John
Osborn, Laurel Ruma, Brian MacDonald, Matt Ellis, André van Meulebrouck, Stephen
Toub, Jared Parsons, Dustin “honorary member” Campbell, Michael de la Maza, Alex
Peake, Ryan Cavanaugh, Brian Peek, Matthew Podwysocki, Richard Minerich, Kate
Moore, and finally Stuart Bowers—you’re easily a level-9 F# ninja by now.
xx | Preface
PART I
Multiparadigm Programming
CHAPTER 1
Introduction to F#
Getting to Know F#
As with all programming books, it is customary to write a Hello, World application,
and I don’t want to deviate from tradition. Open up Notepad or your favorite text editor
and create a new file named HelloWorld.fs with the following text:
// HelloWorld.fs
Success! You’ve just written your first F# application. To compile this application, use
the F# compiler, fsc.exe, located in the Program Files\Microsoft F#\v4.0 folder. (Or,
if you are using the Mono, wherever you chose to install F#.) The following snippet
shows calling the F# compiler on the command line to build and run your application:
3
C:\Program Files\Microsoft F#\v4.0>fsc HelloWorld.fs
Microsoft F# Compiler, (c) Microsoft Corporation, All Rights Reserved
F# Version 1.9.8.0, compiling for .NET Framework Version v4.0.21017
After you click OK in the New Project dialog, you’ll see an empty code editor, a blank
canvas ready for you to create your F# masterpiece.
4 | Chapter 1: Introduction to F#
To start with, let’s revisit our Hello, World application. Type the following code into
the F# editor:
printfn "Hello, World"
Now press Control + F5 to run your application. When your application starts, a con-
sole window will appear and display the entirely unsurprising result shown in Fig-
ure 1-2.
open System
[<EntryPoint>]
let main (args : string[]) =
Now that you have actual F# code, hopefully you are curious about what is going on.
Let’s look at this program line by line to see how it works.
Values
Example 1-1 introduces three values named greeting, thing, and timeOfDay:
let greeting, thing = args.[0], args.[1]
let timeOfDay = DateTime.Now.ToString("hh:mm tt")
The key thing here is that the let keyword binds a name to a value. It is worth pointing
out that unlike most other programming languages, in F# values are immutable by
default, meaning they cannot be changed once initialized. We will cover why values
are immutable in Chapter 3, but for now it is sufficient to say it has to do with functional
programming.
F# is also case-sensitive, so any two values with names that only differ by case are
considered different:
let number = 1
let Number = 2
let NUMBER = 3
You can enclose the value’s name with a pair of tickmarks, in which case
the name can contain any character except for tabs and newlines. This
allows you to refer to values and functions exposed from other .NET
languages that may conflict with F# keywords:
let ``this.Isn't %A% good value Name$!@#`` = 5
Whitespace Matters
Other languages, like C#, use semicolons and curly braces to indicate when statements
and blocks of code are complete. However, programmers typically indent their code to
make it more readable anyway, so these extra symbols often just add syntactic clutter
to the code.
In F#, whitespace—spaces and newlines—is significant. The F# compiler allows you
to use whitespace to delimit code blocks. For example, anything indented more than
6 | Chapter 1: Introduction to F#
the if keyword is considered to be in the body of the if statement. Because tab
characters can indicate an unknown number of space characters, they are prohibited
in F# code.
You can configure the Visual Studio editor to automatically convert tab
characters into spaces by changing the relevant setting under
Tools→Options→Text Editor→F#.
Reviewing Example 1-1, notice that the body of the main method was indented by four
spaces, and the body of the if statement was indented by another four spaces:
let main (args : string[]) =
If the body of the if statement, the failwith, was dedented four spaces and therefore
lined up with the if keyword, the F# compiler would yield a warning. This is because
the compiler wouldn’t be able to determine if the failwith was meant for the body of
the if statement:
[<EntryPoint>]
let main (args : string[]) =
The general rule is that anything belonging to a method or statement must be indented
further than the keyword that began the method or statement. So in Example 1-1,
everything in the main method was indented past the first let and everything in the
if statement was indented past the if keyword. As you see and write more F# code,
you will quickly find that omitting semicolons and curly braces makes the code easier
to write and also much easier to read.
// ...
The .NET Framework contains a broad array of libraries for everything from graphics
to databases to web services. F# can take advantage of any .NET library natively by
calling directly into it. In Example 1-1, the DateTime.Now property was used in the
System namespace in the mscorlib.dll assembly. Conversely, any code written in F#
can be consumed by other .NET languages.
For more information on .NET libraries, you can skip ahead to Appendix A for a quick
tour of what’s available.
Comments
Like any language, F# allows you to comment your code. To declare a single-line com-
ment, use two slashes, //; everything after them until the end of the line will be ignored
by the compiler:
// Program exit code
For larger comments that span multiple lines, you can use multiline comments, which
indicate to the compiler to ignore everything between the (* and *) characters:
(*
Mega Hello World:
Take two command line parameters and then print
them along with the current time to the console.
*)
For F# applications written in Visual Studio, there is a third type of comment: an XML
documentation comment. If a comment starting with three slashes, ///, is placed above
an identifier, Visual Studio will display the comment’s text when you hover over it.
Figure 1-3 shows applying an XML documentation comment and its associated tooltip.
F# Interactive
So far you have written some F# code and executed it, and the rest of the book will
have many more examples. While you could leave a wake of new projects while working
through this book and trying out the samples, Visual Studio comes with a tool called
F# Interactive or FSI. Using the FSI window, you will not only find it much easier to
work through the examples in this book, but it will also help you write applications.
8 | Chapter 1: Introduction to F#
Figure 1-3. XML documentation comments
F# Interactive is a tool known as a REPL, which stands for read-evaluate-print loop.
It accepts F# code, compiles and executes it, then prints the results. This allows you
to quickly and easily experiment with F# code without needing to create new projects
or build a full application to see the result of a five-line snippet.
In C# and VB.NET, you must compile and then run your application in order to see
its results, which makes it cumbersome to try out and experiment with small code
fragments. Even if you use the Visual Studio Immediate Window while debugging, you
are limited to just evaluating expressions and cannot actually write code, such as de-
fining new functions and classes.
In Visual Studio, most profiles launch the F# Interactive window by using the Control
+Alt+F keyboard combination. Once the FSI window is available, it accepts F# code
until you terminate the input with ;; and a newline. The code entered will be compiled
and executed just as shown in Figure 1-4.
After each code snippet is sent to FSI, for every name introduced, you will see val <name>.
If an expression was left unnamed, by default, it will be called it. After the name of the
identifier will be a colon and the type of the code evaluated, followed by its value. For
example, in Figure 1-4, the value x was introduced, of type int, with value 42.
If you are running F# without Visual Studio, you can find a console
version of F# Interactive named fsi.exe in the same directory you found
fsc.exe.
Try running these other snippets in FSI. Note that every code snippet is terminated
with ;;:
> 2 + 2;;
val it : int = 4
> // Introduce two values
let x = 1
F# Interactive | 9
let y = 2.3
val x : int
val y : float
FSI dramatically simplifies testing and debugging your applications because you can
send F# code from your current project to the FSI window by highlighting the code in
Visual Studio and pressing Alt+Enter.
After selecting all the code in Example 1-1 within the code editor and pressing
Alt+Enter, you will see the following in the FSI window:
>
10 | Chapter 1: Introduction to F#
This allows you to write code in the Visual Studio editor—which offers syntax high-
lighting and IntelliSense—but test your code using the FSI window. You can test the
main method you sent to FSI simply by calling it:
> main [| "Hello"; "World" |];;
Hello, World at 10:52 AM
val it : int = 0
The majority of the samples in this book are taken directly from FSI
sessions. I encourage you to use FSI as well to follow along and experi-
ment with the F# language’s syntax.
F# source files are compiled in the order they are displayed in Visual Studio’s Solution
Explorer, from top to bottom. Whenever you add a new code file, it is added to the
bottom of the list, but if you want to rearrange the source files, you can right-click a
code file and select Move Up or Move Down, as shown in Figure 1-5. The keyboard
shortcut for reordering project files is Alt+Up and Alt+Down.
Now that you are armed with the logistical know-how for compiling F# applications,
the rest of this book will focus exclusively on the syntax and semantics of the F# pro-
gramming language.
12 | Chapter 1: Introduction to F#
CHAPTER 2
Fundamentals
In Chapter 1, you wrote your first F# program. I broke it down to give you a feel for
what you were doing, but much of the code is still a mystery. In this chapter, I’ll provide
the necessary foundation for you to understand that code fully, but more importantly,
I’ll present several more examples that you can use to grasp the basics of F# before you
move on to the more complex features.
The first section of this chapter covers primitive types, like int and string, which are
the building blocks for all F# programs. I’ll then cover functions so that you can ma-
nipulate data.
The third section details foundational types such as list, option, and unit. Mastering
how to use these types will enable you to expand into the object-oriented and functional
styles of F# code covered in later chapters.
By the end of this chapter, you will be able to write simple F# programs for processing
data. In future chapters, you will learn how to add power and expressiveness to your
code, but for now let’s master the basics.
Primitive Types
A type is a concept or abstraction and is primarily about enforcing safety. Types rep-
resent a proof of sorts if a conversion will work. Some types are straightforward—
representing an integer—while others are far more abstract—like a function. F# is
statically typed, meaning that type checking is done at compile time. For example, if a
function accepts an integer as a parameter, you will get a compiler error if you try to
pass in a string.
Like C# and VB.NET, F# supports the full cast and crew of primitive .NET types (which
are the same for most programming languages). They are built into the F# language
and separate from user-defined types, which you define yourself.
13
To create a value, simply use a let binding.
You can do much more with let bindings, but we’ll save that for Chapter 3. For now,
just know that you can use a let binding (via the let keyword) to introduce a new
identifier. For example, the following code defines a new value x in an FSI session:
> let x = 1;;
val x : int = 1
Numeric Primitives
Numeric primitives come in two varieties: integers and floating-point numbers. Integer
types vary by size, so that some types take up less memory and can represent a smaller
range of numbers. Integers can also be signed or unsigned based on whether or not they
can represent negative numbers.
Floating-point types vary in size, too; in exchange for taking up more memory, they
provide more precision on the numbers they hold.
To define new numeric values, use a let binding followed by an integer or floating-
point literal with an optional suffix. The suffix determines the type of integer or floating-
point number. For a full list of available primitive numeric types and their suffixes, see
Table 2-1.
> let answerToEverything = 42UL;;
14 | Chapter 2: Fundamentals
Type Suffix .NET type Range
float System.Double A double-precision floating point based on the IEEE64 standard. Rep-
resents values with approximately 15 significant digits.
float32 f System.Float A single-precision floating point based on the IEEE32 standard. Rep-
resents values with approximately 7 significant digits.
decimal M System.Decimal A fixed-precision floating-point type with precisely 28 digits of
precision.
F# will also allow you to specify values in hexadecimal (base 16), octal (base 8), or
binary (base 2) using a prefix 0x, 0o, or 0b:
> let hex = 0xFCAF;;
If you are familiar with the IEEE32 and IEEE64 standards, you can also specify floating-
point numbers using hex, octal, or binary. F# will convert the binary value to the
floating-point number it represents. When using a different base to represent floating-
point numbers, use the LF suffix for float types and lf for float32 types:
> 0x401E000000000000LF;;
val it : float = 7.5
> 0x00000000lf;;
val it : float32 = 0.0f
Arithmetic
You can use standard arithmetic operators on numeric primitives. Table 2-2 lists all
supported operators. Like all CLR-based languages, integer division rounds down to
the next lowest number discarding the remainder.
Table 2-2. Arithmetic operators
Operator Description Example Result
+ Addition 1 + 2 3
− Subtraction 1 − 2 −1
* Multiplication 2 * 3 6
Primitive Types | 15
Operator Description Example Result
/ Division 8L / 3L 2L
** Powera 2.0 ** 8.0 256.0
% Modulus 7 % 3 1
a Power, the ** operator, only works for float and float32 types. To raise the power of an integer value, you must either convert it to
a floating-point number first or use the pown function.
By default, arithmetic operators do not check for overflow, so if you exceed the range
allowed by an integer value by addition, it will overflow to be negative. (Similarly,
subtraction will result in a positive number if the number is too small to be stored in
the integer type.)
> 32767s + 1s;;
val it : int16 = −32768s
F# features all the standard math functions you would expect; see a full listing in
Table 2-3.
Table 2-3. Common math functions
Routine Description Example Result
abs Absolute value of a number abs −1.0 1.0
ceil Round up to the nearest integer ceil 9.1 10
exp Raise a value to a power of e exp 1.0 2.718
floor Round down to the nearest integer floor 9.9 9.0
sign Sign of the value sign −5 −1
log Natural logarithm log 2.71828 1.0
log10 Logarithm in base 10 log10 1000.0 3.0
sqrt Square root sqrt 4.0 2.0
cos Cosine cos 0.0 1.0
sin Sine sin 0.0 0.0
tan Tangent tan 1.0 1.557
pown Compute the power of an integer pown 2L 10 1024L
16 | Chapter 2: Fundamentals
Conversion Routines
One of the tenets of the F# language is that there are no implicit conversions. This
means that the compiler will not automatically convert primitive data types for you
behind the scenes, for example, converting an int16 to an int64. This eliminates subtle
bugs by removing surprise conversions. Instead, to convert primitive values, you must
use an explicit conversion function listed in Table 2-4. All of the standard conversion
functions accept all other primitive types—including strings and chars.
Table 2-4. Numeric primitive conversion routines
Routine Description Example Result
sbyte Converts data to an sybte sbyte −5 −5y
byte Converts data to a byte byte "42" 42uy
int16 Converts data to an int16 int16 'a' 97s
uint16 Converts data to a uint16 uint16 5 5us
int32, int Converts data to an int int 2.5 2
uint32, uint Converts data to a uint uint32 0xFF 255
int64 Converts data to an int64 int64 −8 −8L
uint64 Converts data to a uint64 uint64 "0xFF" 255UL
float Converts data to a float float 3.1415M 3.1415
float32 Converts data to a float32 float32 8y 8.0f
decimal Converts data to a decimal decimal 1.23 1.23M
While these conversion routines accept strings, they parse strings using
the underlying System.Convert family of methods, meaning that for in-
valid inputs they throw System.FormatException exceptions.
BigInt
If you are dealing with data larger than 264, F# has the BigInt type for representing
arbitrarily large integers. While the BigInt type is simply an alias for the System.Numer
ics.BigInteger type, it is worth noting that neither C# nor VB.NET has syntax to
support arbitrarily large integers.
BigInt uses the I suffix for literals. Example 2-1 defines data storage sizes as BigInts.
Primitive Types | 17
let terabyte = gigabyte * 1024I
let petabyte = terabyte * 1024I
let exabyte = petabyte * 1024I
let zettabyte = exabyte * 1024I;;
Bitwise Operations
Primitive integer types support bitwise operators for manipulating values at a binary
level. Bitwise operators are typically used when reading and writing binary data from
files. See Table 2-5.
Table 2-5. Bitwise operators
Operator Description Example Result
&&& Bitwise ‘And’ 0b1111 &&& 0b0011 0b0011
||| Bitwise ‘Or’ 0xFF00 ||| 0x00FF 0xFFFF
^^^ Bitwise ‘Exclusive Or’ 0b0011 ^^^ 0b0101 0b0110
<<< Left Shift 0b0001 <<< 3 0b1000
>>> Right Shift 0b1000 >>> 3 0b0001
Characters
The .NET platform is based on Unicode, so characters are represented using 2-byte
UTF-16 characters. To define a character value, you can put any Unicode character in
single quotes. Characters can also be specified using a Unicode hexadecimal character
code.
The following code defines a list of vowel characters and prints the result of defining a
character using a hexadecimal value:
> let vowels = ['a'; 'e'; 'i'; 'o'; 'u'];;
18 | Chapter 2: Fundamentals
To represent special control characters, you need to use an escape sequence, listed in
Table 2-6. An escape sequence is a backslash followed by a special character.
Table 2-6. Character escape sequences
Character Meaning
\' Single quote
\" Double quote
\\ Backslash
\b Backspace
\n Newline
\r Carriage return
\t Horizontal tab
If you want to get the numeric representation of a .NET character’s Unicode value, you
can pass it to any of the conversion routines listed earlier in Table 2-3. Alternatively,
you can get the byte value of a character literal by adding a B suffix:
> // Convert value of 'C' to an integer
int 'C';;
val it : int = 67
> // Convert value of 'C' to a byte
'C'B;;
val it : byte = 67uy
Strings
String literals are defined by enclosing a series of characters in double quotes, which
can span multiple lines. To access a character from within a string, use the indexer
syntax, .[ ], and pass in a zero-based character index:
> let password = "abracadabra";;
> multiline.[0];;
val it : char = 'T'
> multiline.[1];;
val it : char = 'h'
> multiline.[2];;
val it : char = 'i'
Primitive Types | 19
> multiline.[3];;
val it : char = 's'
If you want to specify a long string, you can break it up across multiple lines using a
single backslash, \. If the last character on a line in a string literal is a backslash, the
string will continue on the next line after removing all leading whitespace characters:
let longString = "abc-\
def-\
ghi";;
> longString;;
val it : string = "abc-def-ghi"
You can use the escape sequence characters such as \t or \\ within a string if you want,
but this makes defining file paths and registry keys problematic. You can define a
verbatim string using the @ symbol, which takes the verbatim text between the quotation
marks and does not encode any escape sequence characters:
> let normalString = "Normal.\n.\n.\t.\t.String";;
Similar to adding the B suffix to a character to return its byte representation, adding B
to the end of a string will return the string’s characters in the form of a byte array.
(Arrays will be covered in Chapter 4.)
> let hello = "Hello"B;;
Boolean Values
For dealing with values that can only be true or false, F# has the bool type
(System.Boolean), as well as standard Boolean operators listed in Table 2-7.
Table 2-7. Boolean operators
Operator Description Example Result
&& Boolean ‘And’ true && false false
|| Boolean ‘Or’ true || false true
not Boolean ‘Not’ not false true
Example 2-2 builds truth tables for Boolean functions and prints them. It defines a
function called printTruthTable that takes a function named f as a parameter. That
20 | Chapter 2: Fundamentals
function is called for each cell in the truth table and its result is printed. Later the
operators && and || are passed to the printTruthTable function.
Example 2-2. Printing truth tables
> // Print the truth table for the given function
let printTruthTable f =
printfn " |true | false |"
printfn " +-------+-------+"
printfn " true | %5b | %5b |" (f true true) (f true false)
printfn " false | %5b | %5b |" (f false true) (f false false)
printfn " +-------+-------+"
printfn ""
();;
val it : unit = ()
> printTruthTable (||);;
|true | false |
+-------+-------+
true | true | true |
false | true | false |
+-------+-------+
val it : unit = ()
Functions
Now that we have all of F#’s primitive types under our control, let’s define functions
in order to manipulate them.
You define functions the same way you define values, except everything after the name
of the function serves as the function’s parameters. The following defines a function
called square that takes an integer, x, and returns its square:
> let square x = x * x;;
Unlike C#, F# has no return keyword. So when you define a function, the last ex-
pression to be evaluated in the function is what the function returns.
Let’s try another function to add 1 to the function’s input:
> let addOne x = x + 1;;
The output from FSI shows the function has type int -> int, which is read as “a
function taking an integer and returning an integer.” The signature gets a bit more
complicated when you add multiple parameters:
> let add x y = x + y
22 | Chapter 2: Fundamentals
Technically speaking, the type int -> int -> int is read as “a function taking an
integer, which returns a function which takes an integer and returns an integer.” Don’t
worry about this “functions returning functions” jazz just yet. The only thing you need
to know for now is that to call a function, simply provide its parameters separated by
spaces:
> add 1 2;;
Val it : 3
Type Inference
Because F# is statically typed, calling the add method you just created with a floating-
point value will result in a compiler error:
> add 1.0 2.0;;
You might be wondering, then, why does the compiler think that this function takes
only integers? The + operator also works on floats, too!
The reason is because of type inference. Unlike C#, the F# compiler doesn’t require
you to explicitly state the types of all the parameters to a function. The compiler figures
it out based on usage.
Because the + operator works for many different types such as byte, int, and decimal,
the compiler simply defaults to int if there is no additional information.
The following FSI session declares a function that will multiply two values. Just as when
+ was used, the function is inferred to work on integers because no usage information
is provided:
> // No additional information to infer usage
let mult x y = x * x;;
Functions | 23
Now if we have FSI snippet that not only defines the mult function but also calls it
passing in floats, then the function’s signature will be inferred to be of type float ->
float -> float:
> // Type inference in action
let mult x y = x * y
let result = mult 4.0 5.5;;
However, you can provide a type annotation, or hint, to the F# compiler about what
the types are. To add a type annotation, simply replace a function parameter with the
following form:
ident -> (ident : type)
where type is the type you wish to force the parameter to be. To constrain the first
parameter of our add function to be a float, simply redefine the function as:
> let add (x : float) y = x + y;;
Notice that because you added the type annotation for value x, the type of the function
changed to float -> float -> float. This is because the only overload for + that takes
a float as its first parameter is float -> float -> float, so the F# compiler now infers
the type of y to be float as well.
Type inference dramatically reduces code clutter by having the compiler figure out what
types to use. However, the occasional type annotation is required and can sometimes
improve code readability.
Generic Functions
You can write functions that work for any type of a parameter, for example, an identity
function that simply returns its input:
> let ident x = x;;
Because the type inference system could not determine a fixed type for value x in the
ident function, it was generic. If a parameter is generic, it means that the parameter can
be of any type, such as an integer, string, or float.
24 | Chapter 2: Fundamentals
The type of a generic parameter can have the name of any valid identifier prefixed with
an apostrophe, but typically letters of the alphabet starting with ‘a’. The following code
redefines the ident function using a type annotation that forces x to be generic:
> let ident2 (x : 'a) = x;;
Writing generic code is important for maximizing code reuse. We will continue to dive
into type inference and generic functions as the book progresses, so don’t sweat the
details just yet. Just note that whenever you see an 'a, it can be an int, float, string,
user-defined type, etc.
Scope
Each value declared in F# has a specific scope, which is the range of locations where
the value can be used. (More formally, this is called a declaration space.) By default
values have module scope, meaning that they can be used anywhere after their decla-
ration. However, values defined within a function are scoped only to that function. So
a function can use any value defined previously on the “outside” of the function, but
the “outside” cannot refer to values defined inside of a function.
In the following example, a value named moduleValue is defined with module scope and
used inside of a function, while another value named functionValue is defined within
a function and raises an error when used outside of its scope:
> // Scope
let moduleValue = 10
let functionA x =
x + moduleValue;;
functionValue;;
^^^^^^^^^^^^^
error FS0039: The value or constructor 'functionValue' is not defined.
The scoping of values may not seem that important a detail, but it is important in F#
because it allows nested functions. In F#, you can declare new function values within
the body of a function. Nested functions have access to any value declared in a higher
scope, such as the parent function or module, as well as any new values declared within
the nested function.
Functions | 25
The following code shows nested functions in action. Notice how function g is able to
use its parent function f ’s parameter fParam:
> // Nested functions
let moduleValue = 1
let f fParam =
let g gParam = fParam + gParam + moduleValue
let a = g 1
let b = g 2
a + b;;
It may seem as though defining functions within functions can only lead to confusion,
but the ability to limit the scope of functions is very useful. It helps prevent pollution
of the surrounding module by allowing you to keep small, specific functions local to
just where they are needed. This will become more apparent once we start programming
in the functional style in Chapter 3.
Once you do start using nested functions, it might become tedious to keep all the values
in scope straight. What if you want to declare a value named x, but that value is already
used in a higher scope? In F#, having two values with the same name doesn’t lead to
a compiler error; rather it simply leads to shadowing. When this happens, both values
exist in memory, except there is no way to access the previously declared value. Instead,
the last one declared “wins.”
The following code defines a function which takes a parameter x, and then defines
several new values each named x as well:
> // Convert bytes to gigabytes
let bytesToGB x =
let x = x / 1024I // B to KB
let x = x / 1024I // KB to MB
let x = x / 1024I // MB to GB
x;;
After each let binding in the previous example, the value named x is shadowed and
replaced with a new one. It may look as if the value of x is changing, but actually it is
just creating a new value of x and giving it the same name. The following shows an
example of how the code gets compiled:
let bytesToGB x =
let x_2 = x / 1024I // B to KB
let x_3 = x_2 / 1024I // KB to MB
26 | Chapter 2: Fundamentals
let x_4 = x_3 / 1024I // MB to GB
x_4
This technique of intentionally shadowing values is useful for giving the illusion of
updating values without relying on mutation. If you want to actually update the value
of x, you would need to resort to mutability, which is covered in Chapter 4.
Control Flow
Within a function, you can branch control flow using the if keyword. The condition
expression must be of type bool and if it evaluates to true, the given code is executed,
which in the following snippet prints a message to the console:
> // If statements
let printGreeting shouldGreet greeting =
if shouldGreet then
printfn "%s" greeting;;
You can nest if expressions to model more complicated branching, but this quickly
becomes difficult to maintain:
Functions | 27
let isWeekend day =
if day = "Sunday" then
true
else
if day = "Saturday" then
true
else
false
F# has some syntactic sugar to help you combat deeply nested if expressions with the
elif keyword. With it, you can chain together multiple if expressions without the need
for nesting:
let isWeekday day =
if day = "Monday" then true
elif day = "Tuesday" then true
elif day = "Wednesday" then true
elif day = "Thursday" then true
elif day = "Friday" then true
else false
Because the result of the if expression is a value, every clause of an if expression must
return the same type:
> // ERROR: Different types for if expression clauses
let x =
if 1 > 2 then
42
else
"a string";;
But what if you only have a single if and no corresponding else? Then, the clause must
return unit. unit is a special type in F# that means essentially “no value.” (C# devel-
opers can think of unit as a manifestation of void.) We’ll cover it in more detail shortly.
The if statements in the following example work because printfn returns unit:
> // If statement bodies returning unit
let describeNumber x =
if x % 2 = 0 then
printfn "x is a multiple of 2"
if x % 3 = 0 then
printfn "x is a multiple of 3"
if x % 5 = 0 then
printfn "x is a multiple of 5"
();;
28 | Chapter 2: Fundamentals
val describeNumber : int -> unit
Core Types
Earlier we covered the primitive types available on the .NET platform, but those alone
are insufficient for creating meaningful programs. The F# library includes several core
types that will allow you to organize, manipulate, and process data. Table 2-9 lists a
set of foundational types you will use throughout your F# applications.
Table 2-9. Common types in F#
Signature Name Description Example
unit Unit A unit value ()
int, float Concrete type A concrete type 42, 3.14
'a, 'b Generic type A generic (free) type
'a -> 'b Function type A function returning a value fun x -> x + 1
'a * 'b Tuple type An ordered collection of values (1, 2), ("eggs", "ham")
'a list List type A list of values [ 1; 2; 3], [1 .. 3]
'a option Option type An optional value Some(3), None
Unit
The unit type is a value signifying nothing of consequence. unit can be thought of as
a concrete representation of void and is represented in code via ():
> let x = ();;
val x : unit
> ();;
val it : unit = ()
if expressions without a matching else must return unit because if they did return a
value, what would happen if else was hit? Also, in F#, every function must return a
value, so if the function doesn’t conceptually return anything—like printf—then it
should return a unit value.
The ignore function can swallow a function’s return value if you want to return unit.
It is typically used when calling a function for its side effect and you want to ignore its
return value:
> let square x = x * x;;
Core Types | 29
val square : int -> int
Tuple
A tuple (pronounced “two-pull”) is an ordered collection of data, and an easy way to
group common pieces of data together. For example, tuples can be used to track the
intermediate results of a computation.
To create an instance of a tuple, separate a group of values with commas, and optionally
place them within parentheses. A tuple type is described by a list of the tuple’s elements’
types, separated by asterisks. In the following example, dinner is an instance of a tuple,
while string * string is the tuple’s type:
> let dinner = ("green eggs", "ham");;
Tuples can contain any number of values of any type. In fact, you can even have a tuple
that contains other tuples!
The following code snippet defines two tuples. The first, named zeros, contains a tuple
of various manifestations of zero. The second, nested, defines a nested tuple. The tuple
has three elements, the second and third of which are themselves tuples:
> let zeros = (0, 0L, 0I, 0.0);;
val zeros : int * int64 * bigint * float = (0, 0L, 0I, 0.0)
To extract values from two-element tuples, you can use the fst and snd functions.
fst returns the first element of the tuple and snd returns the second:
> let nameTuple = ("John", "Smith");;
30 | Chapter 2: Fundamentals
Alternately, you can extract values from tuples by simply using a let binding. If you
have let followed by multiple identifiers separated by commas, those names capture
the tuple’s values.
The following example creates a tuple value named snacks. Later the tuple’s values are
extracted into new identifiers named x, y, and z:
> let snacks = ("Soda", "Cookies", "Candy");;
> y, z;;
val it : string * string = ("Cookies", "Candy")
You will get a compile error if you try to extract too many or too few values from a tuple:
> let x, y = snacks;;
let x, y = snacks;;
-----------^^^^^^
It is possible to pass tuples as parameters to functions, like any value. Functions taking
a single tuple as a parameter have a very different meaning when it comes to the func-
tional style of programming; see “Partial function application” on page 51.
In the following example, the function tupledAdd takes two parameters, x and y, in
tupled form. Notice the difference in type signature between the add and the tupledAdd
functions:
> let add x y = x + y;;
Core Types | 31
Lists
Whereas tuples group values into a single entity, lists allow you link data together to
form a chain. Doing so allows you to process list elements in bulk using aggregate
operators, discussed shortly.
The simplest way to define a list is as a semicolon-delimited list of values enclosed in
brackets, though later you will learn to declare lists using the more powerful list com-
prehension syntax. The empty list, which contains no items, is represented by []:
> // Declaring lists
let vowels = ['a'; 'e'; 'i'; 'o'; 'u']
let emptyList = [];;
In our example, the empty list had type 'a list because it could be of any type and the
type inference system was unable to pin down a specific type.
Unlike list types in other languages, F# lists are quite restrictive in how you access and
manipulate them. In fact, for a list there are only two operations you can perform. (To
see how this limitation can be used to your advantage, refer to Chapter 7.)
The first primitive list operation is cons, represented by the :: or cons operator. This
joins an element to the front or head of a list. The following example attaches the value
'y' to the head of the vowels list:
> // Using the cons operator
let sometimes = 'y' :: vowels;;
val sometimes : char list = ['y'; 'a'; 'e'; 'i'; 'o'; 'u']
The second primitive list operation, known as append, uses the @ operator. Append
joins two lists together. The following example joins the list odds and the list evens
together, resulting in a new list:
> // Using the append operator
let odds = [1; 3; 5; 7; 9]
let evens = [2; 4; 6; 8; 10]
List ranges
Declaring list elements as a semicolon-delimited list quickly becomes tedious, espe-
cially for large lists. To declare a list of ordered numeric values, use the list range syntax.
32 | Chapter 2: Fundamentals
The first expression specifies the lower bound of the range and the second specifies the
upper bound. The result then is a list of values from the lower bound to the upper
bound, each separated by one:
> let x = [1 .. 10];;
If an optional step value is provided, then the result is a list of values in the range
between two numbers separated by the stepping value. Note that the stepping value
can be negative:
> // List ranges
let tens = [0 .. 10 .. 50]
let countDown = [5L .. −1L .. 0L];;
val tens : int list = [0; 10; 20; 30; 40; 50]
val countDown : int list = [5L; 4L; 3L; 2L; 1L; 0L]
List comprehensions
The most expressive method for creating lists is to use list comprehensions, which is a
rich syntax that allows you to generate lists inline with F# code. At the simplest level,
a list comprehension is some code surrounded by rectangular brackets [ ]. The body
of the list comprehension will be executed until it terminates, and the list will be made
up of elements returned via the yield keyword. (Note that the list is fully generated in
memory; lists elements are not lazily evaluated, like F#’s seq<_> type, discussed in the
next chapter.)
> // Simple list comprehensions
let numbersNear x =
[
yield x - 1
yield x
yield x + 1
];;
Most any F# code can exist inside of list comprehensions, including things like function
declarations and for loops. The following code snippet shows a list comprehension
that defines a function negate and returns the numbers 1 through 10, negating the even
ones:
> // More complex list comprehensions
let x =
[ let negate x = -x
for i in 1 .. 10 do
if i % 2 = 0 then
yield negate i
Core Types | 33
else
yield i ];;
When using for loops within list comprehensions, you can simplify the code by using
-> instead of do yield. The following two code snippets are identical:
// Generate the first ten multiples of a number
let multiplesOf x = [ for i in 1 .. 10 do yield x * i ]
Using list comprehension syntax will enable you to quickly and concisely generate lists
of data, which can then be processed in your code. Example 2-3 shows how you can
use list comprehensions to generate all prime numbers smaller than a given integer.
The example works by looping through all numbers between 1 and the given max value.
Then, it uses a list comprehension to generate all the factors of that number. It checks
if the generated list of factors has only two elements, then it yields the value because it
is prime. There certainly are more efficient ways to compute primes, but this demon-
strates just how expressive list comprehensions can be.
Example 2-3. Using list comprehensions to compute primes
> // List comprehension for prime numbers
let primesUnder max =
[
for n in 1 .. max do
let factorsOfN =
[
for i in 1 .. n do
if n % i = 0 then
yield i
]
34 | Chapter 2: Fundamentals
Table 2-10. Common List module functions
Function and type Description
List.length Returns the length of a list.
'a list -> int
List.head Returns the first element in a list.
'a list -> 'a
List.tail Returns the given list without the first element.
'a list -> 'a list
List.exists Returns whether or not an element in the list satisfies the search
function.
('a -> bool) -> 'a list -> bool
List.rev Reverses the elements in a list.
'a list -> 'a list
List.tryfind Returns Some(x) where x is the first element for which the given
function returns true. Otherwise returns None. (Some and None
('a -> bool) -> 'a list -> 'a option
will be covered shortly.)
List.zip Given two lists with the same length, returns a joined list of tuples.
'a list -> 'b list -> ('a * 'b) list
List.filter Returns a list with only the elements for which the given function
returned true.
('a -> bool) -> 'a list -> 'a list
List.partition Given a predicate function and a list returns two new lists, the first
where the function returned true, the second where the function
('a -> bool) -> 'a list -> ('a list *
returned false.
'a list)
Initially, it may not be clear how to use some of the List module functions, but you’ll
soon be able to identify what a function does by simply looking at its type signature.
The following example demonstrates the List.partition function, partitioning a list
of numbers from 1 to 15 into two new lists: one comprised of multiples of five and the
other list made up of everything else. The tricky part to note is that List.partition
returns a tuple, and in the example values multOf5 and nonMultOf5 are elements of that
tuple being bound at the same time:
> // Using List.partition
let isMultipleOf5 x = (x % 5 = 0)
Core Types | 35
What is List anyway? All of these functions are defined in the List
module in the Microsoft.FSharp.Collections namespace. Because the
Microsoft.FSharp.Collections module is imported by default to access
any of these methods, you need to qualify the List module name and
call the function.
Aggregate Operators
Although lists offer a way to chain together pieces of data, there really isn’t anything
special about them. The true power of lists lies in aggregate operators, which are a set
of powerful functions that are useful for any collection of values. You’ll see this set of
methods again in the discussion of sequences (Chapter 3) and arrays (Chapter 4).
List.map
List.map is a projection operation that creates a new list based on a provided function.
Each element in the new list is the result of evaluating the function. It has type:
('a -> 'b) -> 'a list -> 'b list
You can visually represent mapping a function f to list [x; y; z] as shown in Figure 2-1.
Example 2-4 shows the result of mapping a square function to a list of integers.
Example 2-4. Using List.map to square numbers in a list
> let squares x = x * x;;
36 | Chapter 2: Fundamentals
It may not seem like it right now, but List.map is one of the most useful functions in
the F# language. It provides an elegant way for you to transform data and when used
repeatedly can simplify the structure of the code you write.
List.fold
Folds represent the most powerful type of aggregate operator and not surprisingly the
most complicated. When you have a list of values and you want to distill it down to a
single piece of data, you use a fold.
There are two main types of folds you can use on lists. Let’s start with List.reduce,
which has type:
('a -> 'a -> 'a) -> 'a list -> 'a
The following table shows how the accumulator was built up after processing each list
element:
While the reduce fold is helpful, it forces the type of the accumulator to have the same
type as the list. But what if you wanted something more powerful? For example, re-
ducing a list of items in a shopping cart to a cash value.
Core Types | 37
If you want to use a custom accumulator type, you can use List.fold. The fold function
takes three parameters. First, a function that when provided an accumulator and list
element returns a new accumulator. Second, an initial accumulator value. The final
parameter is the list to fold over. The return value of the function is the final state of
the accumulator. Officially the type is:
('acc -> 'b -> 'acc) -> 'acc -> 'b list -> 'acc
To provide a simple example, consider folding a list of integers into their sum:
> let addAccToListItem acc i = acc + i;;
But again, the accumulator for fold does not need to be the same as the list’s elements.
Example 2-6 folds the characters of a string into a tuple counting the occurrences of
each vowel. (The number of a’s, e’s, i’s, etc.)
When the folding function is applied to each letter in the list, if the letter is a vowel,
we return an updated accumulator value, otherwise, we just return the existing
accumulator.
Example 2-6. Counting vowels using List.fold
> // Count the number of vowels in a string
let countVowels (str : string) =
let charList = List.ofSeq str
> countVowels "The quick brown fox jumps over the lazy dog";;
val it : int * int * int * int * int = (1, 3, 1, 4, 2)
Folding right-to-left
List.reduce and List.fold process the list in a left-to-right order. There are alternative
functions List.reduceBack and List.foldBack for processing lists in right-to-left order.
Depending on what you are trying to do, processing a list in reverse order can have a
substantial impact on performance. (For a more in-depth look at the performance im-
plications of list processing, refer to Chapter 7.)
38 | Chapter 2: Fundamentals
List.iter
The final aggregate operator, List.iter, iterates through each element of the list and
calls a function that you pass as a parameter. It has type:
('a -> unit) -> 'a list -> unit
Because List.iter returns unit, it is predominately used for evaluating the side effect
of the given method. The term side effect simply means that executing the function has
some side effect other than its return value; for example, printfn has the side effect of
printing to the console in addition to returning unit.
Example 2-7 uses List.iter to iterate through each number in a list and print it to the
console.
Example 2-7. Using List.iter to print numbers in a list
> // Using List.iter
let printNumber x = printfn "Printing %d" x
List.iter printNumber [1 .. 5];;
Printing 1
Printing 2
Printing 3
Printing 4
Printing 5
Option
If you want to represent a value that may or may not exist, the best way to do so is to
use the option type. The option type has only two possible values: Some('a) and None.
Consider the problem of parsing a string as an int. If the string is properly formatted,
the function should return the integer value; but what if the string is improperly for-
matted? This is a prime situation where you would use an option type.
Example 2-8 defines a function isInteger, which tries to parse an integer using the
Int32.TryParse function. If the parsing is successful, the function will return
Some(result); otherwise, it will return None. This enables consumers of the function to
know that for some inputs the result may not be defined, hence returning None.
Example 2-8. The option type storing if a string parses as an integer
> // Using option to return a value (or not)
open System
Core Types | 39
val isInteger : string -> int option
To retrieve the value of an option, you can use Option.get. (If Option.get is called on
None, an exception will be thrown.) The following snippet defines a function contains
NegativeNumbers which returns Some(_) for all negative numbers in a list. Then, the list’s
negative numbers are retrieved using Option.get:
> // Using Option.get
Let isLessThanZero x = (x < 0)
The Option module contains other helpful functions listed in Table 2-11.
Table 2-11. Common Option module methods
Function and type Description
Option.isSome Returns true if the option is Some, otherwise, false
'a option -> bool
Option.isNone Returns false if the option is Some, otherwise, true
'a option -> bool
40 | Chapter 2: Fundamentals
Printfn
Writing data to the console is the simplest way to perform I/O and is done using the
printf family of functions. printf comes in three main flavors: printf, printfn, and
sprintf.
printf takes the input and writes it to the screen, whereas printfn writes it to the screen
and adds a line continuation:
> // printf and printfn
printf "Hello, "
printfn "World";;
Hello, World
The existing .NET System.Console class can be used for writing text to
the screen, but printf is better suited for the functional style because its
arguments are strongly typed and therefore contribute to type inference.
System.Console should still be used for input, however.
Printing text to the console isn’t especially exciting, but printf adds a lot of power in
that it has formatting and checking built-in. By providing a format specifier, listed in
Table 2-12, you can drop in data as well. This greatly simplifies printing data:
> // Format specifiers
let mountain = "K2"
let height = 8611
let units = 'm';;
Best of all, when using F#’s type inference system, the compiler will give you an error
if the data doesn’t match the given format specifier:
> printfn "An integer = %d" 1.23;;
stdin(2,27): error FS0001: The type 'float' is not compatible with any of the
types byte,int16,int32,int64,sbyte,uint16,uint32,uint64,nativeint,unativeint,
arising from the use of a printf-style format string.
stopped due to error
In addition, because the F# compiler knows what type to expect given a list of format
specifiers, the type inference system can pin down the types of those values. For
Core Types | 41
example, in the following snippet, the types of the function’s parameters are inferred
based on usage:
> // Type inference from printf
let inferParams x y z =
printfn "x = %f, y = %s, z = %b" x y z;;
The %O format specifier boxes the object and calls the Object.ToString
virtual method. The %A printf format specifier works the same
way, except that it checks for any special printing instructions from a
[<StructuredFormatDisplay>] attribute before calling Object.ToString.
sprintf is used when you want the result of the printing as a string:
> let location = "World";;
Anatomy of an F# Program
By now, you might want to learn how to take the F# code we have been writing in the
FSI window and convert it into actual F# programs. But in reality, every code snippet
you have seen so far has been a full program!
Most other languages, like C#, require an explicit program entry point, often called a
main method. Yet our F# programs so far haven’t declared any special markup indi-
cating where the program should begin. In F#, for single-file applications, the contents
of the code file are executed from top to bottom in order (without the need for declaring
a specific main method).
42 | Chapter 2: Fundamentals
For multifile projects, however, code needs to be divided into organization units called
modules or namespaces.
Modules
All the code we have written so far has been in a module. By default, F# puts all your
code into an anonymous module with the same name as the code file with the first letter
capitalized. So, if you have a value named value1, and your code is in file1.fs, you can
refer to it by using the fully qualified path: File1.value1.
Creating modules
You can explicitly name your code’s module by using the module keyword at the top of
a code file. After that point, every value, function, or type defined will belong to that
module:
module Alpha
Nested modules
Files can contain nested modules as well. To declare a nested module, use the module
keyword followed by the name of your module and an equals sign =. Nested modules
must be indented to be disambiguated from the “top-level” module:
module Utilities
module ConversionUtils =
// Utilities.ConversionUtils.intToString
let intToString (x : int) = x.ToString()
module ConvertBase =
// Utilities.ConversionUtils.ConvertBase.convertToHex
let convertToHex x = sprintf "%x" x
// Utilities.ConversionUtils.ConvertBase.convertToOct
let convertToOct x = sprintf "%o" x
module DataTypes =
// Utilities.DataTypes.Point
type Point = Point of float * float * float
Namespaces
The alternative to modules is namespaces. Namespaces are a unit of organizing code
just like modules with the only difference being that namespaces cannot contain values,
Anatomy of an F# Program | 43
only type declarations. Also, namespaces cannot be nested in the same way that mod-
ules can. Instead, you can simply add multiple namespaces to the same file.
Example 2-9 defines several types inside of two namespaces.
Example 2-9. Namespaces
namespace PlayingCards
// PlayingCard.Suit
type Suit =
| Spade
| Club
| Diamond
| Heart
// PlayingCards.PlayingCard
type PlayingCard =
| Ace of Suit
| King of Suit
| Queen of Suit
| Jack of Suit
| ValueCard of int * Suit
namespace PlayingCards.Poker
// PlayingCards.Poker.PokerPlayer
type PokerPlayer = { Name : string; Money : int; Position : int }
It may seem strange to have both namespaces and modules in F#. Modules are opti-
mized for rapid prototyping and quickly exploring a solution, as you have seen so far.
Namespaces, on the other hand, are geared toward larger-scale projects with an object-
oriented solution.
Program Startup
In F#, the program starts executing at the top of the last code file, which needs to be
a module. Consider this simple F# program consisting of a single code file:
// Program.fs
let numbers = [1 .. 10]
let square x = x * x
open System
44 | Chapter 2: Fundamentals
Now open that project in Visual Studio, and then add a new, empty F# code file. When
you press F5 to run your program, nothing will happen. This is because the newest file
added to the project—which is blank—was added “last” and thus is what ran when
the program started up.
For more formal program-startup semantics, you can use the [<EntryPoint>] attribute
to define a main method. To qualify, your main method must:
• Be the last function defined in the last compiled file in your project. This ensures
there is no confusion as to where the F# program starts.
• Take a single parameter of type string array, which are the arguments to your
program. (Arrays will be covered in Chapter 4.)
• Return an integer, which is your program’s exit code.
To make the main method explicit, you could rewrite the previous application as:
// Program.fs
open System
[<EntryPoint>]
let main (args : string[]) =
let numbers = [1 .. 10]
let square x = x * x
// Return 0
0
Now you have all the tools you need to write simple F# programs. In the next chapter,
you will learn to program using the functional style, enabling you to write more
powerful F# applications and advance on your journey to becoming a level-9 F# ninja
master.
Anatomy of an F# Program | 45
CHAPTER 3
Functional Programming
With the basics out of the way, you can begin to examine F# from the approach of a
particular style. This chapter is devoted to F#’s main paradigm: functional program-
ming. In a nutshell, functional programming is about being more declarative in your
code. In imperative programming (covered in Chapter 4), you spend your time listing
out the specific steps to perform a task. In functional programming, you specify what
is to be done, but not how. Even though functional programming is no silver bullet,
the result is that programs are much clearer, and some problems like concurrency and
parallel programming are solved much more easily.
Functional programming isn’t going to replace imperative or object-oriented program-
ming on its own; rather, it just provides a different approach to use so that in certain
applications you can be much more productive.
For a language to be considered “functional,” it typically needs to support a few key
features:
• Immutable data
• Ability to compose functions
• Functions can be treated as data
• Lazy evaluation
• Pattern matching
We will go into each of these raw, functional concepts and what they offer throughout
the chapter. By the end you will be able to write purely functional code, and leverage
the elegance and simplicity of declarative programming. A deeper look at functional
concepts, such as tail recursion and closures, will come in Chapter 7.
47
Programming with Functions
The heart of functional programming is thinking about code in terms of mathematical
functions. Consider two functions f and g:
f(x) = x^2 + x
g(x) = x + 1
It follows that:
f(2) = (2)^2 + (2)
g(2) = (2) + 1
And if you compose the two functions, or put them together, you get:
f g (2) = f(g(2))
= (g(2))^2 + (g(2))
= (2+1)^2 + (2+1)
= 12
You don’t have to be a mathematician to program in F#, but some of the ideas of
mathematics translate to functional programming. For example, in the previous snip-
pets there was no explicit return type specified. Does f(x) take an integer or a float?
This mathematical notation isn’t concerned with data types or return values. The
equivalent F# code is:
let f x = x ** 2.0 + x
let g x = x + 1.0
The fact the F# code resembles the mathematical notation isn’t a coincidence. Func-
tional programming in essence is thinking about computations in an abstract way—
again, what is to be computed but not how it gets computed.
You can even think of entire programs as functions with their inputs being mouse and
keyboard strokes and the output being the process exit code. When you begin to view
programming in this way, some of the complexity associated with normal programming
models goes away.
First, if you think about your program as a series of functions, then you don’t need to
spend all your time in the details explaining step by step how to complete a task. Func-
tions simply take their input and produce an output. Second, algorithms are expressed
in terms of functions and not classes or objects, so it is easier to translate those concepts
into functional programming.
You will see examples of how functional programming can simplify complex code
throughout the chapter, but first you need to start thinking in terms of functions. To
do so, you need to abandon some of the mindset built up from existing imperative
languages. In the next sections, I will introduce the notion of immutability, functions
as values, and function composition to demonstrate how to use functional
programming.
Both snippets of code return the same value for the same input, so what is the benefit
of the functional style? The first thing you might notice is readability. With the imper-
ative style, you have to read through the code to understand what is going on. The
functional style, however, is more declarative; the code maps directly to what you want
to have happen. (That is, squaring each element in the list, and then summing them.)
Also, if you wanted to run the imperative version in parallel, you would have to rewrite
the code entirely. Because you were so detailed in how you specified the program to be
run, if you want it in parallel, you have to detail the steps for doing it in parallel. The
Function Values
In most other programming languages, functions and data are regarded as two very
different things. However, in a functional programming language, functions are treated
just like any other piece of data. For example, functions can be passed as parameters
to other functions. In addition, functions can create and return new functions! Func-
tions that take or return other functions as their inputs or outputs are known as higher-
order functions, and are key for idiomatic functional programming.
This capability enables you to abstract and reuse algorithms in your code. You saw an
example of this in the previous chapter with List.iter, List.map, and List.fold.
Example 3-2 defines a function, negate, which negates a single integer. When that
function is passed as a parameter to List.map, the function is then applied to a whole
list, negating every list element.
Example 3-2. Example of higher-order functions
> let negate x = -x;;
Using function values is very convenient, but the result is that you end up writing many
simple functions that don’t have a lot of value on their own. For example, our negate
function in Example 3-2 will probably never be used anywhere else in the program
except when negating a list.
Rather than naming all the little functions you pass as parameters, you can use an
anonymous function, also known as a lambda expression, to create a function inline.
To create a lambda expression, simply use the fun keyword followed by the function’s
parameters and an arrow, ->.
The following snippet creates a lambda expression and passes the value 5 as a param-
eter. When the function is executed, the parameter x is incremented by 3 and the result
is 8:
We can rewrite our negate list example using a lambda that takes a single parameter i:
> List.map (fun i -> -i) [1 .. 10];;
val it : int list = [−1; −2; −3; −4; −5; −6; −7; −8; −9; −10]
The appendFile function seems simple enough, but what if you wanted to repeatedly
write to the same log file? You would have to keep around the path to your log file and
always pass it in as the first parameter. It would be nice, however, to create a new version
of appendFile where the first parameter is fixed to our verbatim string, @"D:\Log.txt".
Partial function application is the ability to specify some parameters of a function, and
produce a new function where those parameters are fixed. You can “curry” the first
parameter of appendFile and produce a new function that takes only one parameter,
the message to be logged:
> // Curry appendFile so that the first parameter is fixed
let curriedAppendFile = appendFile @"D:\Log.txt";;
val it : unit = ()
so after the first string parameter was passed in, the result was a function that took a
string and returned unit, or string -> unit.
While curried functions can make code simpler, they can also make code
harder to debug. Be careful not to abuse currying to make your programs
any more complex than they need to be.
All you need to do to curry a function is specify a subset of its required parameters, and
the result will be a function that only requires the parameters not yet specified. For this
reason, you can only curry a function’s arguments from left to right.
Currying and partial function application may not look particularly powerful, but they
can dramatically improve the elegance of your code. Consider the printf function that
takes a format string as a parameter followed by the values to fill in that format string.
If you just supply the first parameter of "%d", the result is a partially applied function
that accepts an integer and prints it to the screen.
The following example shows how you can pass a partially applied version of printf
to avoid the need for a lambda expression:
> // Non curried
List.iter (fun i -> printfn "%d" i) [1 .. 3];;
1
2
3
val it : unit = ()
> // Using printfn curried
List.iter (printfn "%d") [1 .. 3];;
1
2
3
val it : unit = ()
You will see how to take full advantage of partial function application when you get to
function composition and the pipe-forward operator.
If you look closer at our generatePowerOfFunc function, notice that its parameter base
is used in the two lambdas it returned. But when you call values powerOfTwo and
powerOfThree later, where does base come from if it isn’t a parameter to the function?
When generatePowerOfFunc was initially called with the value 2.0 as a parameter, where
was that 2.0 stored?
There is a bit of magic going on here known as a closure. Don’t concern yourself with
this for now, or how it works. Just know that if a value is in scope, it can be used and
perhaps returned by a function. In Chapter 7, I will cover closures in depth and show
you the sorts of things you can do by abusing this magic performed by the F# compiler.
Recursive Functions
A function that calls itself is known to be recursive, and when programming in the
functional style, these can be very useful, as you will see shortly.
To define a recursive function, you simply need to add the rec keyword. The following
snippet defines a function for calculating the factorial of a number. (That is, the product
of all positive integers up to and including the number. For example, the factorial of 4
is 4 * 3 * 2 * 1.)
> // Define a recursive function
let rec factorial x =
if x <= 1 then
1
else
x * factorial (x - 1);;
Using recursion combined with higher-order functions, you can easily simulate the sort
of looping constructs found in imperative languages without the need for mutating
values. The following example creates functional versions of common for and while
loops. Notice that in the example of the for loop, an updated counter is simply passed
as a parameter to the recursive call:
> // Functional for loop
let rec forLoop body times =
if times <= 0 then
()
else
body()
forLoop body (times - 1)
whileLoop
(fun () -> DateTime.Now.DayOfWeek <> DayOfWeek.Saturday)
(fun () -> printfn "I wish it were the weekend...");;
I wish it were the weekend...
I wish it were the weekend...
I wish it were the weekend...
* * * This goes on for several days * * *
val it : unit = ()
In order to define mutually recursive functions, you must join them together with the
and keyword, which tells the F# compiler to perform type inference for both functions
at the same time:
> // Define mutually recursive functions
let rec isOdd n = (n = 1) || isEven (n - 1)
and isEven n = (n = 0) || isOdd (n - 1);;
Symbolic Operators
Think how difficult programming would be if you couldn’t write 1 + 2 and instead had
to write add 1 2 every time. Fortunately, F# not only has built-in symbolic operators
for things like addition and subtraction, but it also allows you to define your own
symbolic operators, enabling you to write code in a cleaner, more elegant way.
> !5;;
val it : int = 120
By default, symbolic functions use infix notation when they have more than one pa-
rameter. This means that the first parameter comes before the symbol, which is how
you most commonly apply symbolic functions. The following example defines a func-
tion ===, which compares a string with a regular expression:
> // Define (===) to compare strings based on regular expressions
open System.Text.RegularExpressions;;
To have symbolic operators that come before their parameters, also known as prefix
notation, you must prefix the function with a tilde ~, exclamation point !, or question
mark ?. In the following code, the function ~+++ is prefixed with a tilde, and thus to
call it, you write ~+++ 1 2 3 rather than 1 ~+++ 2 3. This enables you to use symbolic
operators that more naturally fit the style of the function being defined:
> let (~+++) x y z = x + y + z;;
In addition to allowing you to name functions that map more closely to mathematics,
symbolic operators can also be passed around to higher-order functions if you simply
put parentheses around the symbol. For example, if you want to sum or multiply the
elements of a list, you can simply write:
> // Sum a list using the (+) symbolic function
List.fold (+) 0 [1 .. 10];;
val it : int = 55
> // Multiply all elements using the (*) symbolic function
List.fold (*) 1 [1 .. 10];;
val it : int = 3628800
Function Composition
Once you get a strong grasp on functions, you can begin to look at combining them to
form larger, more powerful functions. This is known as function composition and is
another tenet of functional programming.
Before we go over how to combine functions, let’s look at the problem it solves. Here
is an example of what not to do: put everything into one massive function. Consider
this code for getting the size of a given folder on disk (I’ve added type annotations to
help clarify return values):
open System
open System.IO
Pipe-forward operator
Fortunately, F# solves this problem of passing an intermediate result on to the next
function concisely with the pipe-forward operator, |>. It is defined as:
let (|>) x f = f x
The pipe-forward operator allows you to rearrange the parameters of a function so that
you present the last parameter of the function first. Whereas the last parameter to
List.iter is the list to iterate through, by using the pipe-forward operator, you can
now “pipe” the list into List.iter so you specify which list to iterate through first:
> [1 .. 3] |> List.iter (printfn "%d");;
1
2
3
val it : unit = ()
The benefit of the pipe-forward operator is that you can continually reapply it to chain
functions together. So, the result of one function is then piped into the next. We can
let totalSize =
folder
|> getFiles
|> Array.map (fun file -> new FileInfo(file))
|> Array.map (fun info -> info.Length)
|> Array.sum
totalSize
The simplicity achieved by using the pipe-forward operator is an example of how useful
function currying is. The pipe-forward operator takes a value and a function that only
takes one parameter; however, the functions that we used, such as Array.map, clearly
take two (a function to map and the array itself). The reason this works is that we curried
one argument, resulting in a function that takes only one parameter and therefore can
easily be used with the pipe-forward operator.
An added benefit of the pipe-forward operator is that it can help the type inference
process. You cannot access any properties or methods of a value if the compiler doesn’t
know what its type is. Therefore, you must use a type annotation to specifically pin
down types:
> // ERROR: Compiler doesn't know s has a Length property
List.iter
(fun s -> printfn "s has length %d" s.Length)
["Pipe"; "Forward"];;
Because the pipe-forward operator allows the compiler to “see” the last parameter of
a function earlier, the type inference system can determine the correct types of a func-
tion sooner, eliminating the need for type annotations.
When using the pipe-forward operator in functions, you need a placeholder variable
to “kick off” the pipelining. In our last example, the function took a folder parameter
that we directly passed into the first piped function:
let sizeOfFolderPiped2 folder =
folder
|> getFiles
|> Array.map (fun file -> new FileInfo(file))
|> Array.map (fun info -> info.Length)
|> Array.sum
open System.IO
Pipe-backward operator
At first glance, the pipe-backward operator, <|, accepts a function on the left and applies
it to a value on the right. This seems unnecessary because all it does is separate a func-
tion and its last parameter, which you can do without the need of some special operator:
let (<|) f x = f x
You might be surprised, but the pipe-backward operator actually does serve an im-
portant purpose: it allows you to change precedence. I’ve avoided mentioning operator
precedence so far, but it is the order in which functions are applied. Function arguments
are evaluated left to right, meaning if you want to call a function and pass the result to
another function, you have two choices, add parentheses around the expression or use
the pipe-backward operator:
> printfn "The result of sprintf is %s" (sprintf "(%d, %d)" 1 2);;
The result of sprintf is (1, 2)
> printfn "The result of sprintf is %s" <| sprintf "(%d, %d)" 1 2;;
The result of sprintf is (1, 2)
val it : unit = ()
The following code shows how to take the square of the negation of a number. Using
the backward composition operator allows the program text to read exactly in the way
the function operates, in other words, have the program code read more like it actually
operates:
> // Backward Composition
let square x = x * x
let negate x = -x;;
Another example of the backward composition operator would be using it to filter out
empty lists in a list of lists. Again, the backward composition operator is used to change
the way the code reads to the programmer:
> // Filtering lists
[ [1]; []; [4;5;6]; [3;4]; []; []; []; [9] ]
|> List.filter(not << List.isEmpty);;
val it : int list list = <1]; [4; 5; 6]; [3; 4]; [9>
Pattern Matching
All programs need to sift and sort through data; to do this in functional programming,
you use pattern matching. Pattern matching is similar to a switch statement from C#
let describeNumber x =
match isOdd x with
| true -> printfn "x is odd"
| false -> printfn "x is even";;
The simplest sort of pattern matching is against constant values. Example 3-5 con-
structs a truth table for the Boolean function And by matching both values of a tuple
simultaneously.
Example 3-5. Constructing a truth table using pattern matching
> // Truth table for AND via pattern matching
let testAnd x y =
match x, y with
| true, true -> true
| true, false -> false
| false, true -> false
| false, false -> false;;
Note that type inference works on pattern matches. In Example 3-5, because the first
rule matches x with the Boolean value true and y with another Boolean value, both x
and y are inferred to be Boolean values.
The underscore, _, is a wildcard that matches anything. So you can simplify the previous
example by using a wildcard to capture any input but true true:
Pattern Matching | 63
let testAnd x y =
match x, y with
| true, true -> true
| _, _ -> false
Pattern-matching rules are checked in the order they are declared. So if you had wild-
card matches first, subsequent rules would never be checked.
Match Failure
You might have been wondering, what would happen if we left out one of the possible
truth table matches in the original version of testAnd? For example:
let testAnd x y =
match x, y with
| true, true -> true
| true, false -> false
// | false, true -> false - Oops! false, true case omitted!
| false, false -> false
match l with
----------^
Named Patterns
So far we have just matched constant values, but you can also use named patterns to
actually extract data and bind it to a new value. Consider the following example. It
matches against a specific string, but for anything else it captures a new value, x:
The last match rule doesn’t match a constant value; instead, it binds the value being
matched to a new value you can then use in the match rule’s body. Value captures, like
wildcards, match anything, so make sure to put more specific rules first.
Matching Literals
Naming patterns is great, but it also prevents you from using an existing value as part
of a pattern match. In the previous example, hardcoding the names as part of the pattern
match would make the code more difficult to maintain and potentially lead to bugs.
If you want to match against a well-known value, but don’t want to copy and paste the
literal value into every match rule, you could make the mistake of using a named pat-
tern. This, however, does not work as you intend and just outscopes the existing value.
In the following example, the first pattern-match rule doesn’t compare the value name
against the value bill, rather it just introduces a new value named bill. This is why
the second pattern-match rule yields a warning, because the previous rule already
catches all pattern-match input:
> // ERROR: Unintentional value captures
let bill = "Bill Gates"
let greet name =
match name with
| bill -> "Hello Bill!"
| x -> sprintf "Hello, %s" x;;
In order to match against an existing value, you must add the [<Literal>] attribute,
which allows any literal value (a constant) to be used inside of a pattern match. Note
that values marked with [<Literal>] must begin with a capital letter.
Pattern Matching | 65
Attributes are a way to annotate .NET code. For more information
about attributes and .NET metadata, refer to Chapter 12.
Only integers, characters, Booleans, strings, and floating-point numbers can be marked
as literals. If you want to match against more complex types, like a dictionary or map,
you must use a when guard.
when Guards
While pattern matching is a powerful concept, sometimes you need custom logic to
determine whether a rule should match. This is what when guards are for. If a pattern
is matched, the optional when guard will execute and the rule will fire if and only if the
when expression evaluates to true.
The following example implements a simple game where you guess a random number.
when guards are used to check if the guess is higher, lower, or equal to the secret number:
> // High / Low game
open System
let highLowGame () =
> highLowGame();;
Guess the secret number: 50
The secret number is lower.
Guess the secret number: 25
The secret number is higher.
Guess the secret number: 37
You've guessed correctly!
val it : unit = ()
Grouping Patterns
As your pattern matches contain more and more rules, you might want to combine
patterns. There are two ways to combine patterns. The first is to use Or, represented
by a vertical pipe |, which combines patterns together so the rule will fire if any of the
grouped patterns match. The second is to use And, represented by an ampersand &,
which combines patterns together so that the rule will fire only if all of the grouped
patterns match:
let vowelTest c =
match c with
| 'a' | 'e' | 'i' | 'o' | 'u'
-> true
| _ -> false
let describeNumbers x y =
match x, y with
| 1, _
| _, 1
-> "One of the numbers is one."
| (2, _) & (_, 2)
-> "Both of the numbers are two"
| _ -> "Other."
The And pattern has little use in normal pattern matching; however, it is invaluable
when using active patterns (Chapter 7).
Pattern Matching | 67
Matching the Structure of Data
Pattern matching can also match against the structure of data.
Tuples
You have already seen how to match against tuples. If the tuple’s elements are separated
by commas in the pattern match, each element will be matched individually. However,
if a tuple input is used in a named pattern, the bound value will have a tuple type.
In the following example, the first rule binds value tuple, which captures both values
x and y. Other rules match against tuple elements individually:
let testXor x y =
match x, y with
| tuple when fst tuple = snd tuple
-> true
| true, true -> false
| false, false -> false
Lists
Example 3-6 demonstrates how you can pattern match against the structure of lists.
The function listLength matches against lists of fixed size, otherwise it recursively calls
itself with the tail of the list.
Example 3-6. Determining the length of a list
let rec listLength l =
match l with
| [] -> 0
| [_] -> 1
| [_; _] -> 2
| [_; _; _] -> 3
| hd :: tail -> 1 + listLength tail
The first four pattern-match rules match against lists of specific lengths, using wildcards
to indicate that list elements are not important. The last line of the pattern match,
however, uses the cons operator :: to match the first element of the list, hd, to the rest
of the list; tail. tail could be any list, from an empty list [] on up to a list with a million
elements. (Though owing to the previous rules in the function, we can infer that tail
is at least three elements long.)
Options
Pattern matching also provides a more functional way to use option types:
let describeOption o =
match o with
| Some(42) -> "The answer was 42, but what was the question?"
| Some(x) -> sprintf "The answer was %d" x
| None -> "No answer found."
let bindings
let bindings are actually pattern-match rules. So if you write:
let x = f()
...
Anonymous functions
Parameters to anonymous functions are pattern matches in disguise, too!
// Given a tuple of option values, return their sum
let addOptionValues = fun (Some(x), Some(y)) -> x + y
Wildcard patterns
Imagine you wanted to write a function but didn’t care about one of the parameters,
or perhaps wanted to ignore values from a tuple. In that case, you could use a wildcard
pattern:
> List.iter (fun _ -> printfn "Step...") [1 .. 3];;
Step...
Step...
Step...
val it : unit = ()
Pattern Matching | 69
Alternate Lambda Syntax
The final use for pattern matching is a simplified lambda syntax. When writing F#
code, you will find that it’s common to pass the parameter directly into a pattern-match
expression, such as:
let rec listLength theList =
match theList with
| [] -> 0
| [_] -> 1
| [_; _] -> 2
| [_; _; _] -> 3
| hd :: tail -> 1 + listLength tail
A simpler way to write this would be to use the function keyword, which acts much
like the fun keyword for creating lambdas, except that function lambdas accept only
one parameter that must be placed within a pattern match. The following example
rewrites the listLength function using the function keyword:
> // The 'function' keyword
let rec funListLength =
function
| [] -> 0
| [_] -> 1
| [_; _] -> 2
| [_; _; _] -> 3
| hd :: tail -> 1 + funListLength tail;;
Discriminated Unions
A foundational type in functional programming is the discriminated union, which is a
type that can only be one of a set of possible values. Each possible value of a discrimi-
nated union is referred to as a union case. With the invariant that discriminated unions
can only be one of a set of values, the compiler can do additional checks to make sure
your code is correct, in particular, making sure that pattern matches against discrimi-
nated union types are exhaustive.
To define a discriminated union, use the type keyword, followed by the type’s name,
and then each union case separated by a pipe, |. In a standard deck of cards, a card’s
suit can be represented with the following discriminated union:
> // Discriminated union for a card's suit
type Suit =
| Heart
| Diamond
| Spade
| Club;;
You can also optionally associate data with each union case. To continue the playing-
card example, each card can be associated with a suit, and value cards with an integer
and suit pair. (The int * Suit syntax may look familiar—it is the type signature of a
tuple.)
// Discriminated union for playing cards
type PlayingCard =
| Ace of Suit
| King of Suit
| Queen of Suit
| Jack of Suit
| ValueCard of int * Suit
Discriminated unions can also be recursive. If you need to define a set of mutually
recursive discriminated unions, then just like functions, they need to be linked together
with the and keyword.
The following defines a simple format for describing a programming language:
// Program statements
type Statement =
| Print of string
| Sequence of Statement * Statement
| IfStmt of Expression * Statement * Statement
Discriminated Unions | 71
// Program expressions
and Expression =
| Integer of int
| LessThan of Expression * Expression
| GreaterThan of Expression * Expression
(*
if (3 > 1)
print "3 is greater than 1"
else
print "3 is not"
print "greater than 1"
*)
let program =
IfStmt(
GreaterThan(
Integer(3),
Integer(1)),
Print("3 is greater than 1"),
Sequence(
Print("3 is not"),
Print("greater than 1")
)
)
(*
2
/ \
1 4
/ \
3 5
*)
When evaluated within an FSI session, the previous example prints the following:
> printInOrder binTree;;
Node 1
Node 2
Node 3
Node 4
Node 5
val it : unit = ()
Pattern Matching
You can pattern match against discriminated unions by using just the case labels as
patterns. If the union label has data associated with it, you can pattern match against
a constant, use a wildcard, or capture the value just like a normal pattern match.
The following example demonstrates the power of pattern matching and discriminated
unions by describing two hole cards in a game of poker:
// Describe a pair of cards in a game of poker
let describeHoleCards cards =
match cards with
| []
| [_]
-> failwith "Too few cards."
| cards when List.length cards > 2
-> failwith "Too many cards."
| [ Queen(_); Queen(_) ]
| [ Jack(_); Jack(_) ]
-> "Pair of face cards"
| [ first; second ]
-> sprintf "Two cards: %A and %A" first second
Discriminated Unions | 73
You can also have recursive discriminated unions in pattern matches. Notice in the
following example, the third rule uses a nested pattern to match only Manager values
that have exactly two Worker employees:
type Employee =
| Manager of string * Employee list
| Worker of string
Because the compiler knows every possible data tag associated with a discriminated
union at compile time, any incomplete pattern match will issue a warning. For example,
if the Ace union case were left out of the pattern match, F# compiler would know:
> // OOPS! Forgot about the Ace union case...
let getCardValue card =
match card with
| King(_) | Queen(_) | Jack(_) -> 10
| ValueCard(x, _) -> x;;
member this.Value =
match this with
| Ace(_) -> 11
| King(_) | Queen (_)| Jack(_) -> 10
| ValueCard(x, _) when x <= 10 && x >= 2
-> x
| ValueCard(_) -> failwith "Card has an invalid value!"
Records
Discriminated unions are great for defining a hierarchy of data, but when you are trying
to get values out of a discriminated union they have the same problem as tuples, namely,
that there is no meaning associated with each value—rather, data is lumped together
in some fixed ordering. For example, consider a single-case discriminated union for
describing a person. Are its two string fields referring to the first and then last name,
or the last and then first name? This can lead to confusion and bugs down the road.
type Person =
| Person of string * string * int
When you want to group your data into a structured format without needing hefty
syntax, you can use the F# record type. Records give you a way to organize values into
a type, as well as name those values through fields.
Records | 75
To define a record, you simply define a series of name/type pairs enclosed in curly
braces. To create an instance of a record, simply provide a value for each record field
and type inference will figure out the rest. See Example 3-8.
Example 3-8. Constructing and using records
> // Define a record type
type PersonRec = { First : string; Last : string; Age : int};;
type PersonRec =
{First: string;
Last: string;
Age: int;}
Cloning Records
Records can easily be cloned using the with keyword:
type Car =
{
Make : string
Model : string
Year : int
Pattern Matching
You can pattern match on records, providing value capture and literal matching. Note
that not every record field needs to be a part of the pattern match.
In the following example, a list of Car is filtered to just those where the Model field is
equal to "Coup":
let allCoups =
allNewCars
|> List.filter
(function
| { Model = "Coup" } -> true
| _ -> false)
Type Inference
One distinct advantage of records is how they work with F#’s type inference system.
Whereas .NET class types must be annotated in order to be used, record types can be
inferred by the fields you access.
In Example 3-9, no type annotation is provided for values pt1 and pt2. Because fields
X and Y were accessed and the compiler knows of a record type with an X and Y field,
the type of pt1 and pt2 was inferred to be of type Point.
Example 3-9. Type inference for records
> type Point = { X : float; Y : float };;
type Point =
{X: float;
Y: float;}
Records | 77
> distance {X = 0.0; Y = 0.0} {X = 10.0; Y = 10.0};;
val it : float = 14.14213562
When using two records that have identical field names, the type inference system will
either issue an error or infer types that were different from what you intended. To
combat this, you can either provide type annotations or fully qualify the record fields:
type Point = { X: float; Y: float;}
type Vector3 = { X : float; Y : float; Z : float }
This provides all the necessary information for the type inference system to figure out
what you mean.
type Vector =
{X: float;
Y: float;
Z: float;}
with
member Length : float
end
> v.Length;;
val it : float = 37.41657387
Lazy Types
A Lazy type is a thunk or placeholder for some computation. Once created, you can
pass around the lazy value as though it has been evaluated. But it will only be evaluated
once, and only when forced.
Example 3-10 creates two values, x and y, both of which have the side effect of printing
to the console. Because they are initialized lazily, they are not evaluated when declared.
Only when the value of y is desired is y’s evaluation forced, which in turn forces the
evaluation of x.
To construct a lazy value, you can use the lazy keyword or Lazy<_>.Create.
Example 3-10. Using lazy evaluation
> // Define two lazy values
let x = Lazy<int>.Create(fun () -> printfn "Evaluating x..."; 10)
let y = lazy (printfn "Evaluating y..."; x.Value + x.Value);;
> // Accessing y's value again will use a cached value (no side effects)
y.Value;;
val it : int = 20
Lazy Evaluation | 79
Sequences
The most common use of lazy evaluation is through the sequence or seq type in F#,
which represents an ordered sequence of items, much like a List. The following snippet
defines a sequence of five elements and iterates through them using the Seq.iter func-
tion. (Just like the List module, there is a whole slew of available functions to use on
sequences.)
> let seqOfNumbers = seq { 1 .. 5 };;
The difference between a seq and a list is that only one element of a
sequence exists in memory at any given time. seq is just an alias for
the .NET interface System.Collections.Generic.IEnumerable<'a>.
So why have this limitation? Why have two types when you can just use a list? Because
having all list contents in memory means you are resource-constrained. You can define
an infinite sequence quite easily, but an infinite list would run out of memory. Also,
with lists, you must know the value of each element ahead of time, whereas sequences
can unfold dynamically (in so-called pull fashion).
Example 3-11 defines a sequence of all possible 32-bit integers represented as strings.
It then tries to create an equivalent list, but fails due to memory.
Example 3-11. A sequence of all integers
> // Sequence of all integers
let allIntsSeq = seq { for i = 0 to System.Int32.MaxValue -> i };;
> allIntsSeq;;
val it : seq<int> = seq [0; 1; 2; 3; ...]
> // List of all integers - ERROR: Can't fit in memory!
let allIntsList = [ for i = 0 to System.Int32.MaxValue -> i ];;
System.OutOfMemoryException: Exception of type 'System.OutOf MemoryException' was thrown.
Sequences are evaluated lazily, so every time an element is yielded, the code executing
inside the sequence is still running. Let’s try the previous example again, but this time
adding a side effect every time an element is returned. Instead of returning a letter of
the alphabet, it will print that letter to the console as well:
> // Sequence with a side effect
let noisyAlphabet =
seq {
for c in 'A' .. 'Z' do
printfn "Yielding %c..." c
yield c
};;
Sequences | 81
// Yield all files in its sub folders
for subdir in Directory.GetDirectories(basePath) do
yield! allFilesUnder subdir
}
Seq.take
Returns the first n items from a sequence:
> // Sequence of random numbers
open System
let randomSequence =
seq {
let rng = new Random()
while true do
yield rng.Next()
};;
Seq.unfold
Generates a sequence using a given function. It has type ('a -> ('b * 'a) option) ->
'a -> seq<'b>.
The supplied function provides an input value, and its result is an option type with a
tuple of the next value in the sequence combined with the input to the next iteration
of Seq.unfold. The function returns None to indicate the end of the sequence. Exam-
ple 3-13 generates all Fibonacci numbers under 100. (The nth Fibonacci number is the
sum of the two previous items in the sequence; for example, 1, 1, 2, 3, 5, 8, and so on.)
The example also uses the Seq.tolist function, which converts a sequence to a list.
Example 3-13. Using Seq.unfold
> // Generate the next element of the Fibonacci sequence give the previous
// two elements. To be used with Seq.unfold.
let nextFibUnder100 (a, b) =
if a + b > 100 then
None
else
let nextValue = a + b
Some(nextValue, (nextValue, a));;
Aggregate Operators
The Seq module also contains the same aggregate operators available in the List
module.
Seq.iter
Iterates through each item in the sequence:
> // Print odd numbers under 10
let oddsUnderN n = seq { for i in 1 .. 2 .. n -> i }
Seq.iter (printfn "%d") (oddsUnderN 10);;
1
3
5
7
9
Sequences | 83
Seq.map
Produces a new sequence by mapping a function onto an existing sequence:
> // Sequence of words (Arrays are compatible with sequences)
let words = "The quick brown fox jumped over the lazy dog".Split( [| ' ' |]);;
Seq.fold
Reduces the sequence to a single value. Because there is only one way to iterate through
sequences, there is no equivalent to List.foldBack:
> Seq.fold (+) 0 <| seq { 1 .. 100 };;
val it : int = 5050
You now know the main aspects for writing functional code. Although, it is perfectly
OK if the advantages of function composition and lazy evaluation aren’t immediately
obvious. The majority of the examples in this book are written in the functional style
and over time, you will begin to see how they approach problem solving from a different
perspective.
Until now most programs we have written have been pure, meaning that they never
changed state. Whenever a function does something other than just return a value, it
is known as a side effect. While pure functions have some interesting features like com-
posability, the fact of the matter is that programs aren’t interesting unless they do
something: save data to disk, print values to the screen, issue network traffic, and so
on. These side effects are where things actually get done.
This chapter will cover how to change program state and alter control flow, which is
known as imperative programming. This style of programming is considered to be more
error-prone than functional programming because it opens up the opportunity for get-
ting things wrong. The more detailed the instructions you give the computer to branch,
or write certain values into certain memory locations, the more likely the programmer
will make a mistake. When you programmed in the functional style, all of your data
was immutable, so you couldn’t assign a wrong value by accident. However, if used
judiciously, imperative programming can be a great boon for F# development.
Some potential benefits for imperative programming are:
• Improved performance
• Ease of maintenance through code clarity
• Interoperability with existing code
Imperative programming is a style in which the program performs tasks by altering data
in memory. This typically leads to patterns where programs are written as a series of
statements or commands. Example 4-1 shows a hypothetical program for using a killer
robot to take over the earth. The functions don’t return values, but do impact some
part of the system, such as updating an internal data structure.
85
Example 4-1. Taking over the earth with imperative programming
let robot = new GiantKillerRobot()
robot.Initialize()
Although the code snippet makes taking over the earth look fairly easy, you don’t see
all the hard work going on behind the scenes. The Initialize function may require
powering up a nuclear reactor; and if Initialize is called twice in a row, the reactor
might explode. If Initialize were written in a purely functional style, its output would
depend only on the function’s input. Instead, what happens during the function call to
Initialize depends on the current state of memory.
While this chapter won’t teach you how to program planet-conquering robots, it will
detail how to write F# programs that can change the environment they run in. You will
learn how to declare variables, whose values you can change during the course of your
program. You’ll learn how to use mutable collection types, which offer an easier-to-use
alternative to F#’s list type. Finally, you will learn about control flow and exceptions,
allowing you to alter the order in which code executes.
Default Values
So far, each value we have declared in F# has been initialized as soon as it has been
created, because in the functional style of programming values cannot be changed once
declared. In imperative programming, however, there is no need to fully initialize values
because you can update them later. This means there is a notion of a default value for
both value and reference types, that is, the value something has before it has been
initialized.
To get the default value of a type, use the type function Unchecked.defaultof<'a>. This
will return the default value for the type specified.
For value types, their default value is simply a zero-bit pattern. Because the size of a
value type is known once it is created, its size in bytes is allocated on the stack, with
each byte being given the value 0b00000000.
The default value for reference types is a little more complicated. Before reference types
are initialized they first point to a special address called null. This is used to indicate
an uninitialized reference type.
In F#, you can use the null keyword to check if a reference type is equal to null. The
following code defines a function to check if its input is null or not, and then calls it
with an initialized and an uninitialized string value:
> let isNull = function null -> true | _ -> false;;
However, reference types defined in F# do not have null as a proper value, meaning
that they cannot be assigned to be null:
> type Thing = Plant | Animal | Mineral;;
type Thing =
| Plant
| Animal
| Mineral
This seems like a strange restriction, but it eliminates the need for excessive null check-
ing. (If you call a method on an uninitialized reference type, your program will throw
a NullReferenceException, so defensively checking all function parameters for null is
typical.) If you do need to represent an uninitialized state in F#, consider using the
Option type instead of a reference type with value null, where the value None represents
an uninitialized state and Some('a) represents an initialized state.
You can attribute some F# types to accept null as a proper value to ease
interoperation with other .NET languages; see Appendix B for more
information.
Changing Values
Now that you understand the basics of where and how data is stored in .NET, you can
look at how to change that data. Mutable values are those that you can change, and can
Changing Values | 89
be declared using the mutable keyword. To change the contents of a mutable value, use
the left arrow operator, <-:
> let mutable message = "World";;
There are several limitations on mutable values, all stemming from security-related CLR
restrictions. This prevents you from writing some code using mutable values. Exam-
ple 4-3 tries to define an inner-function incrementX, which captures a mutable value x
in its closure (meaning it can access x, even though it wasn’t passed in as a parameter).
This leads to an error from the F# compiler because mutable values can only be used
in the same function they are defined in.
Example 4-3. Errors using mutable values in closures
> // ERROR: Cannot use mutable values except in the function they are defined
let invalidUseOfMutable() =
let mutable x = 0
x;;
stdin(16,24): error FS0407: The mutable variable 'x' is used in an invalid way.
Mutable variables may not be captured by closures. Consider eliminating this use
of mutation or using a heap-allocated mutable reference cell via 'ref' and '!'.
The ref function takes a value and returns a copy of it wrapped in a ref cell. Exam-
ple 4-4 shows passing a list of planets to the ref function and then later altering the
contents of the returned ref cell.
Example 4-4. Using ref cells to mutate data
let planets =
ref [
"Mercury"; "Venus"; "Earth";
"Mars"; "Jupiter"; "Saturn";
"Uranus"; "Neptune"; "Pluto"
]
C# programmers should take care when using ref types and Boolean
values. !x is simply the value of x, not the Boolean not function applied
to x:
> let x = ref true;;
> !x;;
The F# library has two functions, decr and incr, to simplify incrementing and decre-
menting int ref types:
> let x = ref 0;;
Changing Values | 91
val it : int ref = {contents = 1;}
> decr x;;
val it : unit = ()
> x;;
val it : int ref = {contents = 0;}
Mutable Records
Mutability can be applied to more than just single values; record fields can be marked
as mutable as well. This allows you to use records with the imperative style. To make
a record field mutable, simply prefix the field name with the mutable keyword.
The following example creates a record with a mutable field Miles, which can be modi-
fied as if it were a mutable variable. Now you can update record fields without being
forced to clone the entire record:
> // Mutable record types
open System
type MutableCar =
{Make: string;
Model: string;
mutable Miles: int;}
val driveForASeason : MutableCar -> unit
driveForASeason kitt
driveForASeason kitt
driveForASeason kitt
driveForASeason kitt;;
Arrays
Until now, when you’ve needed to join multiple pieces of data together, you’ve used
lists. Lists are extremely efficient at adding and removing elements from the beginning
of a list, but they aren’t ideal for every situation. For example, random access of list
elements is very slow. In addition, if you needed to change the last element of a list,
you would need to clone the entire list. (The performance characteristics of lists will
be covered more in-depth in Chapter 7.)
> perfectSquares;;
val it : int array = [|1; 4; 9; 16; 25; 36; 49|]
Indexing an Array
To retrieve an element from the array, you can use an indexer, .[], which is a zero-
based index into the array:
> // Indexing an array
printfn
"The first three perfect squares are %d, %d, and %d"
perfectSquares.[0]
perfectSquares.[1]
perfectSquares.[2];;
The first three perfect squares are 1, 4, and 9
val it : unit = ()
Example 4-5 uses array indexers to change individual characters of a character array to
implement a primitive form of encryption known as ROT13. ROT13 works by simply
taking each letter and rotating it 13 places forward in the alphabet. This is achieved in
the example by converting each letter to an integer, adding 13, and then converting it
back to a character.
Example 4-5. ROT13 encryption in F#
open System
Arrays | 93
let newLetter =
(int letter)
|> (fun letterIdx -> letterIdx - (int 'A'))
|> (fun letterIdx -> (letterIdx + 13) % 26)
|> (fun letterIdx -> letterIdx + (int 'A'))
|> char
newLetter
else
letter
let text =
Array.ofSeq "THE QUICK BROWN FOX JUMPED OVER THE LAZY DOG"
Attempting to access an element in the array with an index either less than zero
or greater than or equal to the number of elements in the array will raise an Index
OutOfRangeException exception. (Exceptions will be covered later in this chapter.) For-
tunately, arrays have a Length property, which will return the number of items in the
array. Because array indexes are zero-based, you need to subtract 1 from Length to get
the index of the last element in the array:
> let alphabet = [| 'a' .. 'z' |];;
Array Slices
When you’re analyzing data stored in arrays, it is sometimes convenient to just work
with a subset of the data. In F#, there is a special syntax for taking a slice of an array,
where you specify optional lower and upper bounds for the subset of data. The syntax
for taking a slice is:
array.[lowerBound..upperBound]
Arrays | 95
The first way we sliced an array was specifying both an upper and lower bound; this
returned all array elements within that range:
> // Standard array slice, elements 2 through 4
daysOfWeek.[2..4];;
val it : string [] = [|"Tuesday"; "Wednesday"; "Thursday"|]
Next we specified just a lower or just an upper bound. This returns each element from
the lower bound to the end of the array, or from the beginning of the array to the upper
bound:
> // Just specify lower bound, elements 4 to the end
daysOfWeek.[4..];;
val it : string [] = [|"Thursday"; "Friday"; "Saturday"|]
And finally we just copied the entire array using *. Note that for every slice operation
a new array is returned, so there will never be problems with aliasing:
> // Specify no bounds, get all elements (copies the array)
daysOfWeek.[*];;
Creating Arrays
Array comprehensions and manually specifying each element aren’t the only ways to
construct arrays. You can also use the Array.init function; Array.init takes a function
used to generate each array element based on its index. To create an uninitialized array,
use the Array.zeroCreate function. With that function, each element is initialized to
its default value: zero or null.
Example 4-7 shows how to use Array.init and Array.zeroCreate to construct arrays.
Example 4-7. Initializing arrays using Array.init
> // Initialize an array of sin-wave elements
let divisions = 4.0
let twoPi = 2.0 * Math.PI;;
Pattern Matching
Pattern matching against arrays is just as easy as using lists. And just like pattern
matching against lists, when matching against arrays, you can capture element values,
as well as match against the structure of the array. Example 4-8 shows matching an
array value against null or an array with 0, 1, or 2 elements.
Example 4-8. Pattern matching against arrays
> // Describe an array
let describeArray arr =
match arr with
| null -> "The array is null"
| [| |] -> "The array is empty"
| [| x |] -> sprintf "The array has one element, %A" x
| [| x; y |] -> sprintf "The array has two elements, %A and %A" x y
| a -> sprintf "The array had %d elements, %A" a.Length a;;
Array Equality
Arrays in F# are compared using structural equality. Two arrays are considered equal
if they have the same rank, length, and elements. (Rank is the dimensionality of the
array, something we will cover in the next section.)
> [| 1 .. 5 |] = [| 1; 2; 3; 4; 5 |];;
val it : bool = true
> [| 1 .. 3 |] = [| |];;
val it : bool = false
Arrays | 97
Array Module Functions
Just like the List and Seq modules, there is an Array module detailed in Table 4-1 that
contains nearly identical functions for manipulating arrays.
Table 4-1. Common methods in the Array module
Function Description
Array.length Returns the length of an array. Arrays already have a Length property as well,
but this function is useful for function composition.
'a[] -> int
Array.init Creates a new array with the given number of elements; each element is initialized
by the result of the provided function.
int -> (int -> 'a) -> 'a[]
Array.zeroCreate Creates an array with the given length where each entry is the type’s default value.
int -> 'a[]
Array.exists Tests if any element in the array satisfies the given function.
('a -> bool) -> 'a[] -> bool
partition
Array.partition divides the given array into two new arrays. The first array contains
only elements where the provided function returns true; the second array contains
elements where the provided function returns false:
> // Simple Boolean function
let isGreaterThanTen x =
if x > 10
then true
else false;;
Aggregate operators
The Array module also contains the aggregate operators of the List module, namely:
fold, foldBack, map, and iter. In addition, there are also index-aware versions of these
methods. Example 4-9 demonstrates the iteri function, which behaves just like iter
except that in addition to the array element, the element’s index is provided as well.
Example 4-9. Using the iteri array aggregate function
> let vowels = [| 'a'; 'e'; 'i'; 'o'; 'u' |];;
> Array.iteri (fun idx chr -> printfn "vowel.[%d] = %c" idx chr) vowels
vowel.[0] = a
vowel.[1] = e
vowel.[2] = i
vowel.[3] = o
vowel.[4] = u
val it : unit = ()
Multidimensional Arrays
Arrays are helpful for storing data in a linear fashion, but what if you need to represent
data as a two-, three-, or higher-dimensional grid? You can create multidimensional
arrays, which enable you to treat data as a block indexed by several values.
Multidimensional arrays come in two flavors: rectangular and jagged; the difference is
illustrated in Figure 4-2. Rectangular arrays are in a solid block, while jagged arrays are
essentially arrays of arrays.
Arrays | 99
Figure 4-2. Rectangular and jagged arrays
Which type of multidimensional array to use depends on the situation. Using jagged
arrays allows you to save memory if each row doesn’t need to be the same length;
however, rectangular arrays are more efficient for random access because the elements
are stored in a contiguous block of memory. (And therefore can benefit from your
processor’s cache.)
Rectangular arrays
Rectangular arrays are rectangular blocks of n by m elements in memory. Rectangular
arrays are indicated by rectangular brackets with a comma separating them for each
new dimension. Also, just like single-dimensional arrays, there are the Array2D and
Array3D modules for creating and initializing rectangular arrays:
> // Creating a 3x3 array
let identityMatrix : float[,] = Array2D.zeroCreate 3 3
identityMatrix.[0,0] <- 1.0
identityMatrix.[1,1] <- 1.0
identityMatrix.[2,2] <- 1.0;;
2D rectangular arrays can also take slices, using the same syntax but providing a slice
for each dimension:
> // All rows for columns with index 1 through 2
identityMatrix.[*, 1..2];;
val it : float [,] = <0.0; 0.0]
[1.0; 0.0]
[0.0; 1.0>
List<'T>
The List type in the System.Collections.Generic namespace—not to be confused with
the F# list type—is a wrapper on top of an array that allows you to dynamically adjust
the size of the array when adding and removing elements. Because List is built on top
of standard arrays, retrieving and adding values is typically very fast. But once the
internal array is filled up a new, larger one will need to be created and the List’s contents
copied over.
Example 4-10 shows basic usage of a List. Again, we’re denying poor Pluto the title of
planet.
Example 4-10. Using the List type
> // Create a List<_> of planets
open System.Collections.Generic
let planets = new List<string>();;
Dictionary<'K,'V>
The Dictionary type in the System.Collections.Generic namespace that contains key-
value pairs. Typically, you would use a dictionary when you want to store data and
require a friendly way to look it up, rather than an index, such as using a name to look
up someone’s phone number.
Example 4-11 shows using a dictionary to map element symbols to a custom Atom type.
It uses a language feature called units of measure to annotate the weight of each atom
in atomic mass units. We will cover the units of measure feature in Chapter 7.
open System.Collections.Generic
let periodicTable = new Dictionary<string, Atom>()
// Lookup an element
let printElement name =
if periodicTable.ContainsKey(name) then
let atom = periodicTable.[name]
printfn
"Atom with symbol with '%s' has weight %A."
atom.Name atom.Weight
else
printfn "Error. No atom with name '%s' found." name
HashSet<'T>
A HashSet, also defined in the System.Collections.Generic namespace, is an efficient
collection for storing an unordered set of items. Let’s say you were writing an applica-
tion to crawl web pages. You would need to keep track of which pages you have visited
before so you didn’t get stuck in an infinite loop; however, if you stored the page URLs
you had already visited in a List, it would be very slow to loop through each element
to check whether a page had been visited before. A HashSet stores a collection of unique
values based on their hash code, so checking if an element exists in the set can be done
rather quickly.
Hash codes will be better explained in the next chapter; for now you can just think of
them as a way to uniquely identify an object. They are the reason why looking up
elements in a HashSet and a Dictionary is fast.
Example 4-12 shows using a HashSet to check whether a movie has won the Oscar for
Best Picture.
Example 4-12. Using the HashSet type
Open System.Collections.Generic
Looping Constructs
F# has the traditional sorts of looping constructs seen in imperative languages. These
allow you to repeat the same function or piece of code a set number of times or until a
condition is met.
While Loops
while expressions loop until a Boolean predicate evaluates to false. (For this reason,
while loops cannot be used in a purely functional style, otherwise, the loop would never
terminate because you could never update the predicate.)
Note that the while loop predicate must evaluate to bool and the body of the loop must
evaluate to unit. The following code shows how to use a while loop to count up from
zero:
The predicate for the while loop is checked before the loop starts, so if the initial value
of the predicate is false, the loop will never execute.
For Loops
When you need to iterate a fixed number of times, you can use a for expression, of
which there are two flavors in F#: simple and enumerable.
To count down, use the downto keyword. The loop won’t iterate if the counter is less
than the minimum value:
> // Counting down
for i = 5 downto 1 do
printfn "%d" i;;
5
4
3
2
1
val it : unit = ()
Numerical for loops are only supported with integers as the counter, so if you need to
loop more than System.Int32.MaxValue times, you will need to use enumerable for
loops.
What makes enumerable for loops more powerful than a foreach loop in C# is that
enumerable for loops can take advantage of pattern matching. The element that follows
the for keyword is actually a pattern-match rule, so if it is a new identifier like i, it
simply captures the pattern-match value. But more complex pattern-match rules can
be used instead.
In Example 4-13, a discriminated union is introduced with two union cases, Cat and
Dog. The for loop iterates through a list but executes only when the element in the list
is an instance of the Dog union case.
Example 4-13. for loops with pattern matching
> // Pet type
type Pet =
| Cat of string * int // Name, Lives
| Dog of string // Name
;;
type Pet =
| Cat of string * int
| Dog of string
> let famousPets = [ Dog("Lassie"); Cat("Felix", 9); Dog("Rin Tin Tin") ];;
val famousPets : Pet list = [Dog "Lassie"; Cat ("Felix",9); Dog "Rin Tin Tin"]
There are no break or continue keywords in F#. If you want to exit prematurely in the
middle of a for loop, you will need to create a mutable value and convert the for loop
to a while loop.
In the previous example FSI indicated an exception was thrown, displaying the two
most important properties on an exception type: the exception message and stack trace.
Each exception has a Message property, which is a programmer-friendly description of
the problem. The Stacktrace property is a string printout of all the functions waiting
on a return value before the exception occurred, and is invaluable for tracking down
the origin of an exception. Because the stack unwinds immediately after an exception
is thrown, the exception could be caught far away from where the exception originated.
While a descriptive message helps programmers debug the exception, it is a best prac-
tice in .NET to use a specific exception type. To throw a more specific exception, you
use the raise function. This takes a custom exception type (any type derived from
System.Exception) and throws the exception just like failwith:
> // Raising a DivideByZeroException
let divide2 x y =
if y = 0 then raise <| new System.DivideByZeroException()
x / y;;
Handling Exceptions
To handle an exception you catch it using a try-catch expression. Any exceptions raised
while executing code within a try-catch expression will be handled by a with block,
which is a pattern match against the exception type.
Because the exception handler to execute is determined by pattern matching, you can
combine exception handlers using Or or use a wildcard to catch any exception. If an
exception is thrown within a try-catch expression and an appropriate exception han-
dler cannot be found, the exception will continue bubbling up until caught or the
program terminates.
try-catch expressions return a value, just like a pattern match or if expression. So
naturally the last expression in the try block must have the same type as each rule in
the with pattern match.
Example 4-14 shows some code that runs through a minefield of potential problems.
Each possible exception will be caught with an appropriate exception handler. In the
example, the :? dynamic type test operator is used to match against the exception type;
this operator will be covered in more detail in the next chapter.
Example 4-14. try-catch expressions
open System.IO
[<EntryPoint>]
let main (args : string[]) =
let exitCode =
try
let filePath = args.[0]
Exceptions | 109
// Does the folder exist?
let directory = Path.GetPathRoot(filePath)
if not <| Directory.Exists(directory) then
raise <| new DirectoryNotFoundException(filePath)
with
// Combine patterns using Or
| :? DriveNotFoundException
| :? DirectoryNotFoundException
-> printfn "Unhandled Drive or Directory not found exception"
1
| :? FileNotFoundException as ex
-> printfn "Unhandled FileNotFoundException: %s" ex.Message
3
| :? IOException as ex
-> printfn "Unhandled IOException: %s" ex.Message
4
// Use a wild card match (result will be of type System.Exception)
| _ as ex
-> printfn "Unhandled Exception: %s" ex.Message
5
Because not catching an exception might prevent unmanaged resources from being
freed, such as file handles not being closed or flushing buffers, there is a second way to
catch process exceptions: try-finally expressions. In a try-finally expression, the
code in the finally block is executed whether or not an exception is thrown, giving
you an opportunity to do required cleanup work.
Example 4-15 demonstrates a try-finally expression in action.
Example 4-15. try-finally expressions
> // Try-finally expressions
let tryFinallyTest() =
try
printfn "Before exception..."
failwith "ERROR!"
printfn "After exception raised..."
finally
let test() =
try
tryFinallyTest()
with
| ex -> printfn "Exception caught with message: %s" ex.Message;;
> test();;
Before exception...
Finally block executing...
Exception caught with message: ERROR!
val it : unit = ()
Reraising Exceptions
Sometimes, despite your best efforts to take corrective action, you just can’t fix the
problem. In those situations, you can reraise the exception, which will allow the original
exception to continue bubbling up from within an exception handler.
Example 4-16 demonstrates reraising an exception by using the reraise function.
Example 4-16. Reraise exceptions
open Checked
let reraiseExceptionTest() =
try
let x = 0x0fffffff
let y = 0x0fffffff
x * y
with
| :? System.OverflowException as ex
-> printfn "An overflow exception occured..."
reraise()
Defining Exceptions
Throwing specialized exceptions is key for consumers of your code to only catch the
exceptions they know how to handle. Other exceptions will then continue to bubble
up until an appropriate exception handler is found.
Exceptions | 111
You can define your own custom exceptions by creating types that inherit from
System.Exception, which you will see in Chapter 5. However, in F#, there is an easier
way to define exceptions using a lightweight exception syntax. Declaring exceptions in
this way allows you to define them with the same syntax as discriminated unions.
Example 4-17 shows creating several new exception types, some of which are associated
with data. The advantage of these lightweight exceptions is that when they are caught,
it is easier to extract the relevant data from them because you can use the same syntax
for pattern matching against discriminated unions. Also, there is no need for a dynamic
type test operator :?, which we saw in previous examples.
Example 4-17. Lightweight F# exception syntax
open System
open System.Collections.Generic
exception NoMagicWand
exception NoFullMoon of int * int
exception BadMojo of string
with
| NoMagicWand
-> "Error: A magic wand is required to hex!"
| NoFullMoon(month, day)
-> "Error: Hexes can only be cast during a full moon."
| BadMojo(msg)
-> sprintf "Error: Hex failed due to bad mojo [%s]" msg
Exceptions | 113
CHAPTER 5
Object-Oriented Programming
In this chapter, we will cover the most widely used programming paradigm today:
object-oriented programming. Mastering object-oriented programming (OOP) is cru-
cial for taking advantage of the existing frameworks and libraries available on .NET,
as well as writing F# code that can be integrated into those libraries.
115
Tames complexity
Rather than dealing with myriad individual functions and global variables, OOP
allows you to deal with one item at a time. So any mutable state is scoped to just
the object.
Specialization through inheritance
Using polymorphism, you can write code that deals with a base type of object, and
still accepts any specialized instances of that type. This is another form of code
reuse and will be discussed later in this chapter.
Understanding System.Object
.NET offers a rich type system that can check the identity of objects and guarantee type
safety at runtime. By keeping track of the type of an object, the .NET runtime can ensure
that square pegs aren’t put into round holes.
To achieve this notion of object identity, everything from integers to strings to discri-
minated unions are instances of System.Object, abbreviated in F# by obj. An instance
of System.Object isn’t useful on its own because it doesn’t have any customizations.
However, it is important to know the methods available on System.Object because they
are available on every object you encounter in .NET.
ToString
ToString produces a human-readable string representation of the object. In F# this is
the value the printf format specifier %A or %O displays to the console. No formal guide-
lines exist for what this should return, though the result from ToString should be ad-
equate to differentiate the object from others, as well as an aid in debugging. The default
value is the full name of the class itself.
The following example defines a type PunctionMark and provides an implementation
of its ToString method. Without overriding ToString, the default message would look
something like FSI_0002+PunctuationMark:
> // Overriding ToString
type PunctuationMark =
| Period
| Comma
| QuestionMark
| ExclamationPoint
override this.ToString() =
match this with
| Period -> "Period (.)"
| Comma -> "Comma (,)"
| QuestionMark -> "QuestionMark (?)"
| ExclamationPoint -> "ExclamationPoint (!)";;
type PunctuationMark =
| Period
| Comma
| QuestionMark
| ExclamationPoint
with
override ToString : unit -> string
end
> x.ToString();;
val it : string = "Comma (,)"
GetHashCode
GetHashCode returns a hash value for the object. Overriding this function is crucial for
the type to be used in collections such as Dictionary, HashSet, and Set. A hash code is
The default implementation is good enough for most applications, but if you ever
override Equals, you should override GetHashCode.
Equals
The equals function compares if two objects are equal. Equality in .NET is a complex
topic (see “Object Equality” on page 119):
> "alpha".Equals(4);;
val it : bool = false
> "alpha".Equals("alpha");;
val it : bool = true
GetType
Finally, the GetType method returns an instance of System.Type, which is the type of the
actual object. This is important for understanding what something is at runtime, and
you will see much more of this when we cover reflection in Chapter 12.
> let stringType = "A String".GetType();;
> stringType.AssemblyQualifiedName;;
val it : string
Object Equality
Equality among .NET types is a difficult subject and revolves around the fundamental
type or identity of the object. As mentioned in Chapter 4, there are value types and
reference types, and each has a different default mechanism for equality.
By default, value types are compared using a bit pattern, so for two objects that exist
on the stack, each byte is compared to determine if they are identical. This is sufficient
for primitive types:
> // Value type equality
let x = 42
let y = 42;;
val x : int = 42
val y : int = 42
> x = y;;
val it : bool = true
Reference types, on the other hand, need more complex semantics. In Example 5-1,
values x and y are both instances of class ClassType constructed with the same value.
However, despite having the same conceptual meaning they are not considered equal.
Example 5-1. Referential equality
> // Referential Equality
type ClassType(x : int) =
member this.Value = x
type ClassType =
class
new : x:int -> ClassType
member Value : int
end
val x : ClassType
val y : ClassType
> x = y;;
val it : bool = false
> x = x;;
val it : bool = true
The default meaning of equality for reference types is referential equality, which means
that the two references point to the same object. In the previous example, x = x eval-
uated to true because x and x refer to the same memory address. However x = y
type ClassType2 =
class
new : x:int -> ClassType2
override Equals : o:obj -> bool
override GetHashCode : unit -> int
member Value : int
end
val x : ClassType2
val y : ClassType2
val z : ClassType2
> x = y;;
val it : bool = true
> x = z;;
val it : bool = false
Generated Equality
Tuples, discriminated unions, and records behave exactly as you would expect; that is,
two instances with the same set of values are considered equal, just like value types.
Tuples are considered equal if both tuples have the same number of elements, and each
element is equal:
> let x = (1, 'a', "str");;
> x = x;;
val it : bool = true
> x = (1, 'a', "different str");;
val it : bool = false
> // Nested tuples
(x, x) = (x, (1, 'a', "str"));;
val it : bool = true
type RecType =
{Field1: string;
Field2: float;}
val x : RecType = {Field1 = "abc";
Field2 = 3.5;}
val y : RecType = {Field1 = "abc";
Field2 = 3.5;}
val z : RecType = {Field1 = "XXX";
Field2 = 0.0;}
> x = y;;
val it : bool = true
> x = z;;
val it : bool = false
Discriminated unions are considered equal if both values are of the same data tag, and
the tuples of data associated with the tags are equal:
> // Discriminated Union Equality
type DUType =
| A of int * char
| B
type DUType =
| A of int * char
| B
val x : DUType = A (1,'k')
val y : DUType = A (1,'k')
val z : DUType = B
> x = y;;
val it : bool = true
> x = z;;
val it : bool = false
In reality tuples, discriminated unions, and records are all reference types. So by default,
they should use reference equality, meaning that all the comparisons in the previous
examples would evaluate to false. The reason this works is because the F# compiler
overrides Equals and GetHashCode for you (providing what is known as structural equal-
ity semantics).
type RefDUType =
| A of int * char
| B
> x = y;;
val it : bool = false
> x = x;;
val it : bool = true
Understanding Classes
Now that you understand the basics about what an object is, you can begin creating
custom types of your own. Classes on the simplest level are functions glued to data.
Data associated with a class are known as fields. Functions associated with a class are
known as properties or members. (Properties are simply parameter-less methods.)
I’ll cover each aspect of fields, methods, and properties in time. But creating a new class
isn’t going to be useful unless you can initialize it with some initial state. Classes have
special methods called constructors, whose job is to initialize the class’s fields.
A class’s constructor is called when it is created by a call to new, and it may not be
invoked again once the class has been instantiated.
F# features two separate syntaxes for defining class constructors: explicit constructors
and implicit constructors. One is in line with F#’s simple yet terse syntax, while the
other allows for more explicit control over how the class is generated.
member this.Length =
let sqr x = x * x
sqrt <| sqr this.m_x + sqr this.m_y
You can have arbitrary code execute before or after the class’s fields are initialized to
perform a limited set of setup actions. To perform an action before the class’s fields are
set, simply write code after the equals sign; to perform actions after the fields are ini-
tialized, use the then keyword followed by an expression, which must evaluate to unit.
Example 5-4 adds an explicit constructor to our Point type that takes a string, called
text, as a parameter. Then, several new values are introduced as the string is parsed.
Next, the class’s fields m_x and m_y are initialized. Finally, after the fields are initialized,
a message is printed to the console.
Example 5-4. Arbitrary execution before explicit constructor
open System
type Point2 =
val m_x : float
let length =
let sqr x = x * x
member this.X = x
member this.Y = y
member this.Length = length
The primary constructor’s parameters, as well as any let-bindings, are accessible any-
where within the class and do not need to be prefixed with the self-identifier. In the
previous example, the values x and y—parameters of the primary constructor—were
available as well as the length value defined in a let-binding.
Having two syntaxes for creating classes seems redundant. The prefer-
red way to create classes is to use the implicit class construction format,
and rely only on the explicit class construction syntax when you need
to have explicit control over the class’s fields.
Generic Classes
In Chapter 2, we covered generic functions, or functions that accepted input of any
type. This concept also extends to classes, enabling you to define a type that can be a
collection of other types. For example, the F# list and seq types are generic because
you can have lists and sequences of any type, such as a list of strings or a seq of floats.
To add a generic type parameter to a class, use angle brackets after the class name.
From then on, refer to the type parameter name if you need to refer to the generic type.
For example:
type GenericClass<'a, 'b>() = ...
Just like generic functions, generic type parameters are prefixed with an apostrophe,
'. It is the convention in F# to name generic type parameters 'a, 'b, 'c, and so on.
type Arrayify<'a> =
class
new : x:'a -> Arrayify<'a>
member ArraySize1 : 'a []
member ArraySize2 : 'a []
member ArraySize3 : 'a []
member EmptyArray : 'a []
end
> arrayfiyTuple.ArraySize3;;
val it : (int * int) [] = [|(10, 27); (10, 27); (10, 27)|]
It is worth noting that the generic type may be omitted by using a wildcard <_> and
having the parameter’s type be inferred by the F# compiler. In the following snippet,
based on the value of x passed to Arrayify’s constructor, the generic type parameter is
inferred to be of type string:
> let inferred = new Arrayify<_>( "a string" );;
Records and discriminated unions can be declared generic as well. The following snip-
pet defines a generic discriminate union type:
> // Generic discriminated union
type GenDU<'a> =
| Tag1 of 'a
| Tag2 of string * 'a list;;
type GenDU<'a> =
| Tag1 of 'a
| Tag2 of string * 'a list
type GenRec<'a,'b> =
{Field1: 'a;
Field2: 'b;}
The Self-Identifier
In the previous examples, you may have noticed a curious 'this' before each method
name. It was the name of the self-identifier, which allows you to refer to the instance
of the class inside of any of its methods. It doesn’t have to be called this; in fact it can
be called any valid identifier.
Example 5-7 shows defining a class with oddly named self-identifiers. Note that the
identifier used to name the self-identifier is scoped only to the method or property.
Example 5-7. Naming the this-pointer
open System
type Circle =
val m_radius : float
new(r) = { m_radius = r }
member foo.Radius = foo.m_radius
member bar.Area = Math.PI * bar.Radius * bar.Radius
Obviously, the name of the self identifier should be consistent for every
member of a class.
The examples in this book typically name it this, to closer match the
C# equivalent, though in idiomatic F# code, you’ll rarely need to refer
to the self-identifier, so it is common to give it a short name such as x,
or even __.
type WaterBottle() =
let mutable m_amount = 0.0<ml>
// Read-only property
member this.Empty = (m_amount = 0.0<ml>)
// Read-write property
member this.Amount with get () = m_amount
and set newAmt = m_amount <- newAmt;;
[<Measure>]
type ml
type WaterBottle =
class
new : unit -> WaterBottle
member Amount : float<ml>
member Empty : bool
member Amount : float<ml> with set
end
> bottle.Empty;;
val it : bool = true
> bottle.Amount <- 1000.0<ml>;;
val it : unit = ()
> bottle.Empty;;
val it : bool = false
Methods
To add methods to a class, specify a self identifier followed by the function name. Just
like function values, any pattern rules after the function’s name are its parameters. And,
just like functions, to define a method that takes no parameters, simply have it take
unit as a parameter:
type Television =
member this.TurnOn () =
printfn "Turning on..."
this.m_turnedOn <- true
member this.TurnOff () =
printfn "Turning off..."
this.m_turnedOn <- false
Class methods can be curried, or partially applied, just like function values. However,
this is not recommended because other .NET languages do not support function cur-
rying. The best practice is to have all parameters wrapped in a single tuple. When
referenced from other languages, your F# code won’t look like it takes a single param-
eter of type Tuple, but rather a parameter for each element of the tuple, as you would
expect:
> // Curryable class methods
type Adder() =
// Curried method arguments
member this.AddTwoParams x y = x + y
// Normal arguments
member this.AddTwoTupledParams (x, y) = x + y;;
type Adder =
class
new : unit -> Adder
member AddTwoParams : x:int -> y:int -> int
member AddTwoTupledParams : x:int * y:int -> int
end
A notable difference between class methods and function values is that class methods
can be recursive without needing to use the rec keyword.
type SomeClass =
class
new : unit -> SomeClass
static member StaticMember : unit -> int
end
> SomeClass.StaticMember();;
val it : int = 5
> let x = new SomeClass();;
val x : SomeClass
> x.StaticMember();;
x.StaticMember();;
^^^^^^^^^^^^^^^^
Static methods are usually associated with utility classes and are not typical in F# code.
When you find the need to write many static methods relating to a type, consider
organizing them into a module.
do
if m_numLeft <= 0 then
failwith "No more left!"
m_numLeft <- m_numLeft - 1
printfn "Initialized a rare type, only %d left!" m_numLeft
val a : RareType
val b : RareType
val c : RareType
Otherwise, you would need to convert the int parameter to a string first, which would
quickly become annoying:
System.Console.WriteLine((42).ToString())
In Example 5-12, the method CountBits is overridden to accept arguments of any prim-
itive integer types.
Example 5-12. Method overloading in F#
type BitCounter =
public new() =
let rng = new Random()
let s = float (rng.Next() % 100) * 0.01
let c = float (rng.Next() % 16) + 0.1
new Ruby(s, c)
public new(carats) =
let rng = new Random()
let s = float (rng.Next() % 100) * 0.01
new Ruby(s, carats)
The specifics for all three types of accessibility modifiers in F# are described in Ta-
ble 5-1. The default accessibility for types, values, and functions in F# is public. The
default accessibility for class fields (val and let bindings) is private.
module Logger =
F# signature files
Accessibility modifiers are key to limiting scope and encapsulation, but providing the
correct accessibility modifiers to every type method and value adds a lot of clutter to
your code. The best way to control accessibility across an entire file is to use an F#
While .fsi signature files are not provided for any of the samples in this
book, they are an important part of large-scale F# code bases.
// File.fs
namespace MyProject.Utilities
Inheritance
So far I have covered classes, but not polymorphism, which is the magic that makes
object-oriented programming so powerful. Consider the following two classes, a deli-
cious BLTSandwich and the standard TurkeyAndSwissSandwich:
type BLTSandwich() =
member this.Ingredients = ["Bacon"; "Lettuce"; "Tomato"]
member this.Calories = 450
override this.ToString() = "BLT"
Even though both classes are nearly identical, they are different entities. So in order to
write a function that accepts both an instance of BLTSandwich and an instance of
TurkeySwissSandwich, you will need to resort to method overloading. Also, we would
have to add a new overload method whenever we added a new sandwich type.
member this.EatLunch(sandwich : BLTSandwich) = (*...*)
The right way to think about this is that both of these tasty snacks are specializations
of the same base type: Sandwich; and can customize the general properties and methods
of a Sandwich. Each specialization of Sandwich will have a different calorie count and
list of ingredients.
Moreover, you can continue to create a class hierarchy to specialize even further: Per-
haps create a BLTWithPickelsSandwich type. This is exactly how inheritance and poly-
morphism work.
Inheritance is the ability to create a new class that inherits the methods and properties
of a base class, as well as the ability to add new methods and properties and/or cus-
tomize existing ones. So in this example, BLTSandwich is a specialization, or derived
class, of Sandwich, with its own custom implementations of the Calories and
Ingredients properties.
Polymorphism is the ability to treat any derived class like an instance of its base class.
(Since you know that the derived class has all of the same methods and properties of
its base.)
The syntax for inheriting from a class is to add the line inherit type under the type
declaration. The syntax is slightly different between explicit and implicit class con-
struction.
When using the implicit class construction syntax, simply add the base class
constructor you want to call immediately after the inherit declaration in the primary
constructor. Make sure to provide any applicable parameters to the base class’s
constructor:
// Base class
type BaseClass =
val m_field1 : int
new(x) = { m_field1 = x }
member this.Field1 = this.m_field1
Inheritance | 137
// Derived class using implicit class construction
type ImplicitDerived(field1, field2) =
inherit BaseClass(field1)
When using explicit class construction, you call the desired base class constructor by
putting inherit type in the field initialization of the class:
// Derived class using explicit class construction
type ExplicitDerived =
inherit BaseClass
new(field1, field2) =
{
inherit BaseClass(field1)
m_field2 = field2
}
Method Overriding
The key to customization in derived classes is a technique called method overriding,
which allows you to redefine what a method does.
To communicate that a method or property can be overridden in F#, mark it as
abstract. In Example 5-16, type Sandwich has two abstract properties, Ingredients and
Calories. To provide a default implementation for these methods, you use the
default keyword before the member declaration.
To override or customize the implementation of a base class’s method in a derived class,
use the override keyword before the member declaration. (Just as you have seen for
overriding System.Object’s ToString method.) From then on, when that method is
called, the derived class’s version will be used instead of the base class’s.
Example 5-16. Method overriding in F#
type Sandwich() =
abstract Ingredients : string list
default this.Ingredients = []
type BLTSandwich() =
inherit Sandwich()
type TurkeySwissSandwich() =
inherit Sandwich()
To access the base class explicitly, you use the base keyword. Example 5-17 defines a
new class BLTWithPickleSandwich, which accesses its base class to print its new ingre-
dients and increase its calories.
Example 5-17. Accessing the base class
> // BLT with pickles
type BLTWithPickleSandwich() =
inherit BLTSandwich()
type BLTWithPickleSandwich =
class
inherit BLTSandwich
new : unit -> BLTWithPickleSandwich
override Calories : int
override Ingredients : string list
end
> lunch.Ingredients;;
val it : string list = ["Pickles"; "Bacon"; "Lettuce"; "Tomato"]
Categories of Classes
With the ability to create a class hierarchy, you have the choice of what to do with the
classes higher in the tree and the ones at the very bottom.
Nodes at the top may represent abstract classes, which are so general that they might
not actually exist. In a previous example, we defined a Sandwich class, for which we
provided a default implementation for its abstract members. But not every abstract
member has a meaningful default implementation.
To continue with our sandwich example, type Sandwich might inherit from Food, which
inherits from System.Object. Class Food cannot provide any meaningful defaults for its
methods, and thus would need to be declared abstract.
At the bottom of the class hierarchy are things so specific that they cannot further
be specialized. Although BLTNoPicklesLightMayo could be the foundation for
Inheritance | 139
BLTNoPicklesLightMayoOrganicLettuce, there isn’t a need to customize the behavior of
System.String.
In .NET, you can annotate these topmost and bottommost classes a special way to help
define how your classes get used.
Abstract classes
Abstract classes are typically the root objects in hierarchies and are not whole on their
own. In fact, you cannot instantiate a class marked as abstract; otherwise, you could
potentially call a method that hasn’t been defined.
To create an abstract class, simply apply the [<AbstractClass>] attribute to the class
declaration. Otherwise, you will get an error for not providing a default implementation
for abstract remembers.
> // ERROR: Define a class without providing an implementation to its members
type Foo() =
member this.Alpha() = true
abstract member Bravo : unit -> bool;;
type Foo() =
-----^^^
type Bar =
class
new : unit -> Bar
abstract member Bravo : unit -> bool
member Alpha : unit -> bool
end
Sealed classes
If abstract classes are those that you must inherit from, sealed classes are those that you
cannot inherit from. To seal a class, simply add the [<Sealed>] attribute:
> // Define a sealed class
[<Sealed>]
type Foo() =
member this.Alpha() = true;;
type Foo =
class
new : unit -> Foo
member Alpha : unit -> bool
end
inherit Foo()
----^^^^^^^^^^^^^
Sealing a class enables certain compiler optimizations, because the compiler knows that
virtual methods cannot be overwritten. It also prevents a certain kind of security bug
where a malicious user creates a derived class for an object used for authentication and
can spoof credentials.
Casting
A key advantage of polymorphism is that you can take an instance of a derived class
and treat it like its base class. So in our previous examples, every class in the Sandwich
hierarchy had a Calories property and an Ingredients property. To take an instance of
a type and convert it to another type in the class hierarchy, you can use a cast operator.
Casting is a way to convert types between one another. There are two types of casts—
static and dynamic—which are based on the direction you cast (up or down) an inher-
itance chain.
Static upcast
A static upcast is where an instance of a derived class is cast as one of its base classes,
or cast to something higher up the inheritance tree. It is done using the static cast
operator :>. The result is an instance of the targeted class.
Example 5-18 creates a class hierarchy where Pomeranian is a type of Dog, which is a
type of Animal. By using a static upcast, we can convert an instance of Pomeranian into
an instance of Dog or Animal. Anything can be upcast to obj.
Example 5-18. Static upcasts
> // Define a class hierarchy
[<AbstractClass>]
type Animal() =
abstract member Legs : int
[<AbstractClass>]
type Dog() =
inherit Animal()
abstract member Description : string
override this.Legs = 4
Inheritance | 141
type Pomeranian() =
inherit Dog()
override this.Description = "Furry";;
Dynamic cast
A dynamic cast is when you cast a base type as a derived type, or cast something down
the inheritance tree. (Therefore, this type of cast cannot be checked statically by the
compiler.) This allows you to cast any peg—circular or not—into a round hole, which
could lead to failures at runtime.
The most common use of a dynamic cast is when you have an instance of
System.Object and you know that it is actually something else. To perform a dynamic
cast, use the dynamic cast operator :?>. Building off our previous example, we can cast
our steveAsObj value, which is of type obj, to a Dog by employing a dynamic cast:
> let steveAsObj = steve :> obj;;
> steveAsDog.Description;;
val it : string = "Furry"
If a type is converted to something it is not, the error will be caught at runtime and
result in a System.InvalidCastException exception:
> let _ = steveAsObj :?> string;;
System.InvalidCastException: Unable to cast object of type 'Pomeranian' to type
'System.String'.
at <StartupCode$FSI_0022>.$FSI_0022._main()
stopped due to error
While there is plenty more to learn about OOP, and especially object-oriented design,
you now can create classes and use inheritance to organize and reuse your code. This
enables you to write code in F# to utilize its strengths—data exploration, quantitative
computing, and so on—and package the code to integrate with object-oriented lan-
guages like C#.
Inheritance | 143
CHAPTER 6
.NET Programming
The .NET platform is a general-purpose programming platform that can do more than
just object-oriented or functional programming. This chapter will cover some addi-
tional concepts available when using .NET that will offer advantages when you put
them to use in your F# code.
In this chapter, you will learn how to use interfaces to build better abstractions, enums,
and structs, which can help performance, and how to use F#’s object expressions for
boosting programmer productivity.
The CLI
The foundation of the .NET platform is the CLI, or Common Language Infrastructure.
This is a specification for a runtime system that actually runs your F# code. The F#
compiler produces a binary file called an assembly, which is composed of a high-level
assembly language called MSIL or Microsoft Intermediate Language.
An implementation of the CLI compiles this MSIL to machine code at runtime to ex-
ecute the code faster than if it were just interpreted. The compilation happens “just-in-
time” as the code is needed, or JIT.
Code that is compiled to MSIL and executed through the JITter is referred to as man-
aged code, as opposed to unmanaged code, which is simply raw machine code (typically
from programs written in C or C++).
145
There are several benefits of running through the CLI/managed code and not compiling
your F# directly down to the machine level:
Interoperability among languages
It would be much more difficult to share code among C#, VB.NET, and F# projects
if it weren’t for a common language (MSIL).
Ability to work cross-platform
Since the CLI specification can be implemented anywhere, it is possible to
run .NET code on other platforms, such as on the Web with Microsoft Silverlight,
on the X-Box with XNA and the .NET Compact Framework, or even on Linux
through the Mono project.
Machine independence
The JIT layer takes care of compiling down to machine code, so you don’t have to
worry about compiling your source code to target a specific architecture like x86
or x64.
In addition, the .NET platform offers garbage collection. This frees the programmer
from the error-prone process of tracking the allocation and freeing of memory, and
(mostly) eliminates an entire class of bugs.
Most of the benefits you don’t even need to think about, as things “just work.” But the
one aspect of .NET programming you should be cognizant about is how the garbage
collector works.
Garbage Collection
The garbage collector is an aspect of the .NET runtime that will automatically free any
memory that is no longer referenced. As soon as a value is no longer accessible, typically
because the execution has left the function scope, the memory the object occupied is
marked for deletion and will be freed later by the garbage collector. For example, if a
function declares a value named x, as soon as that function exits, there is no longer a
way to refer to x and therefore the garbage collector can safely reclaim its memory.
As the programmer, you usually don’t need to worry about whether or not a section of
memory should be freed or not; the garbage collector will take care of that for you.
However, it is still possible to have a memory leak in managed code. If you uninten-
tionally keep a reference to something that you don’t need, then the garbage collector
won’t free it because you could potentially access that memory later.
Having the garbage collector to manage all memory works fine for managed resources,
which are the ones created and referenced by the CLI. But things get more complicated
when you’re dealing with unmanaged resources, which include pretty much anything
existing outside of the CLI environment, such as file handles, operating system handles,
shared memory, etc. Those resources must be explicitly managed and freed by the
developer.
type MultiFileLogger() =
do printfn "Constructing..."
let m_logs = new List<StreamWriter>()
type MultiFileLogger =
class
interface IDisposable
new : unit -> MultiFileLogger
member AttachLogFile : file:string -> unit
member LogMessage : msg:string -> unit
end
> task1();;
Constructing...
Exiting the function task1..
Cleaning up...
val it : unit = ()
Interfaces
So far in object-oriented programming, we have modeled a couple of different rela-
tionships. Is a relationships can be modeled through inheritance, and has a relation-
ships are modeled through aggregation (fields and properties). For example, a BMW
is a car (inherits from type Car) and has a motor (contains a field named m_motor of type
Engine).
There is, however, a third type of relationship, the can do relationship, which means
that type X can do the operations described by type Y. For example, people, cars, and
wine all age. While there may be no clear relationship between people, cars, and wine,
they are all capable of aging.
In .NET programming, the can do relationship is modeled via an interface, which is a
contract that a type can implement to establish that it can perform a certain set of
actions.
An interface is just a collection of methods and properties. A type can declare that it
implements the interface if it provides an implementation for each method or property.
Once this contract has been implemented, then the type can do whatever the interface
describes.
In Example 6-1, several types implement the IConsumable interface. To implement an
interface, use the interface keyword followed by the implementation of each interface
method or property to fulfill the contract. If a class implements the IConsumable inter-
face, it means that the class has a Tastiness property and an Eat method.
Example 6-1. Interfaces in F#
type Tastiness =
| Delicious
| SoSo
| TrySomethingElse
// Not that tastey, but if you are really hungry will do a bind
type CardboardBox() =
interface IConsumable with
member this.Eat() = printfn "Yuck!"
member this.Tastiness = TrySomethingElse
Using Interfaces
Unlike other .NET languages, in F#, the methods and properties of implemented in-
terfaces are not in scope by default, so in order to call interface methods, you must cast
the type to the corresponding interface. Once you have an instance of the interface,
that object can do exactly what the interface specifies, no more and no less.
> apple.Tastiness;;
apple.Tastiness;;
------^^^^^^^^^
stdin(81,7): error FS0039: The field, constructor or member 'Tastiness' is not defined.
> let iconsumable = apple :> IConsumable;;
> iconsumable.Tastiness;;
val it : Tastiness = Delicious
Interfaces | 149
Defining Interfaces
To create an interface, you simply define a new class type with only abstract methods.
The type inference system will infer the type to be an interface. Alternatively, you can
use the interface keyword, followed by abstract members and properties, followed by
the end keyword.
type IDoStuff =
interface
abstract member DoThings : unit -> unit
end
type IDoStuffToo =
interface
abstract member DoThings : unit -> unit
end
Interfaces can inherit from one another, with the derived interface extending the con-
tract of the first. However, consumers must implement the entire inheritance chain,
implementing each interface fully.
type IDoMoreStuff =
inherit IDoStuff
type Bar =
class
interface IDoMoreStuff
interface IDoStuff
new : unit -> Bar
end
Object Expressions
Interfaces are useful in .NET, but sometimes you just want an implementation of an
interface without going through the hassle of defining a custom type. For example, in
order to sort the elements in a List<_>, you must provide a type that implements the
IComparer<'a> interface. As you require more ways to sort that List<_>, you will quickly
find your code littered with types that serve no purpose other than to house the single
method that defines how to compare two objects.
type Person =
{ First : string; Last : string }
override this.ToString() = sprintf "%s, %s" this.Last this.First
let people =
new List<_>(
[|
{ First = "Jomo"; Last = "Fisher" }
{ First = "Brian"; Last = "McNamara" }
{ First = "Joe"; Last = "Pamer" }
|] )
let printPeople () =
Seq.iter (fun person -> printfn "\t %s" (person.ToString())) people
type Sandwich =
class
abstract member Calories : int
abstract member Ingredients : string list
new : unit -> Sandwich
end
val lunch : Sandwich
> lunch.Ingredients;;
val it : string list = ["Peanutbutter"; "Jelly"]
Object expressions are especially useful when writing unit tests. In unit tests, you typ-
ically want to create a mock object, or a class that simulates behavior that is otherwise
slow or complicated to isolate, such as mocking out the database connection so your
tests don’t hit a real database. Using object expressions, you can easily create anony-
mous types that can serve as mock objects without the need for defining a plethora of
custom types.
Extension Methods
There are times when you are working with a type and may not be able to make changes,
either because you don’t have the source code or because you are programming against
an older version. This poses a problem when you want to extend that type in some way.
Extension methods provide a simple extension mechanism without needing to modify
a type hierarchy or modify existing types. Extension methods are special types of meth-
ods you can use to augment existing types and use as if they were originally members
of the type. This prevents you from having to rebuild base libraries or resort to using
inheritance in order to add a few methods. Once extension methods have been added,
you can use types as though they had the additional methods and properties, even if
they weren’t originally written for the type.
To declare an extension method, write type ident with followed by the extension
methods, where ident is the fully qualified class name.
Note that because extension methods are not actually part of the class, they cannot
access private or internal data. They are simply methods that can be called as though
they were members of a class.
In Example 6-5, System.Int32 is extended to provide the ToHexString method.
(1094).ToHexString();;
-------^^^^^^^^^^^
stdin(131,8): error FS0039: The field, constructor or member 'ToHexString' is not defined.
> (1094).ToHexString();;
val it : string = "0x446"
Type extensions must be defined within modules and are only available once the given
module has been opened. In other words, people need to opt-in to using extension
methods by opening the defining module.
Extending Modules
Modules in F# can be extended by simply creating a new module with the same name.
As long as the new module’s namespace is opened, all new module functions, types,
and value will be accessible.
The following snippet creates a new namespace called FSCollectionExtensions. Once
this namespace is opened, its two modules will be available, adding new functions to
the List and Seq modules:
namespace FSCollectionExtensions
open System.Collections.Generic
module List =
module Seq =
Enumerations
We have covered discriminated unions, which are helpful for defining types of things
within a set. However, each discriminated union case is a distinctly different class and
is too heavyweight a construct in some situations. Many times you simply want to
define a group of related constant integral values, and in those situations, you can use
enumerations.
An enumeration is a primitive integral type, such as int or sbyte, which also contains
a series of named constant values. An enumeration is just a wrapper over that integral
type, however, so an instance of an enumerated type can have a value not defined within
that enumeration.
Creating Enumerations
To create an enumeration, you use the same syntax as for creating discriminated unions,
but each data tag must be given a constant value of the same type. Example 6-6 shows
creating an enumeration type for chess pieces. Each enumeration field value must be
unique. In the example, the field values correspond to the chess pieces’ material value.
Example 6-6. Declaring an enumeration
type ChessPiece =
| Empty = 0
| Pawn = 1
| Knight = 3
| Bishop = 4
| Rook = 5
| Queen = 8
| King = 1000000
To use an enumeration value, simply use the fully qualified field name. Example 6-7
initializes an 8×8 array to represent locations on a chessboard.
With our ChessPiece enum as is, it would be impossible to differentiate between a black
piece and a white piece. However, because each ChessPiece value is simply an integer,
we can get sneaky and treat black pieces as their ChessPiece values, and white pieces
as the negative. So −1 * ChessPiece.Bishop would represent a white bishop. I will cover
// Place pawns
for i = 0 to 7 do
board.[1,i] <- ChessPiece.Pawn
board.[6,i] <- enum<ChessPiece> (-1 * int ChessPiece.Pawn)
Enumerations can be used in pattern matching, but unlike discriminated unions, each
enumeration field again must be fully qualified through its type name:
let isPawn piece =
match piece with
| ChessPiece.Pawn
-> true
| _ -> false
Conversion
To construct an enumeration value, simply use the enum<_> function. The function
converts its argument to the enum type of its generic type parameter.
The following snippet converts the value 42 to an instance of ChessPiece:
let invalidPiece = enum<ChessPiece>(42)
To get values from an enumeration, simply use the typical primitive value conversion
functions, such as int:
let materialValueOfQueen = int ChessPiece.Queen
Enumerations | 157
When to Use an Enum Versus a Discriminated Union
Enumerations and discriminated unions have two significant differences. First, enu-
merations don’t offer a safety guarantee. Whereas a discriminated union can only be
one of a known set of possible values, an enumeration is syntactic sugar over a primitive
type. So if you are given an enumeration value, there is no implicit guarantee that the
value is something meaningful.
For example, if the value −711 were converted to a ChessPiece enum, it would make
no sense and likely introduce bugs when encountered. When you obtain an enum value
from an external source, be sure to check it with the Enum.IsDefined method:
> System.Enum.IsDefined(typeof<ChessPiece>, int ChessPiece.Bishop);;
val it : bool = true
> System.Enum.IsDefined(typeof<ChessPiece>, −711);;
val it : bool = false
Second, enumerations only hold one piece of data—their value. Discriminated union
data tags can each hold a unique tuple of data.
However, enumerations represent a common .NET idiom and offer significant per-
formance benefits over discriminated unions. Whereas enums are simply a few bytes
on the stack, discriminated unions are reference types and require separate memory
allocations. So populating an array with a large number of enums will be much faster
than populating that array with discriminated unions.
A useful benefit to enums is bit flags. Consider Example 6-8. By giving the enum fields
powers of two as values, you can treat an instance of the enumeration as a set of bit
flags. You can then use bitwise OR, |||, to combine the values, which you can then
mask against to check if the given bit is set. Using this technique, you can store 32
binary flags within an int-based enum, or 64 in an int64-based enum.
Example 6-8. Combining enumeration values for flags
open System
Structs
We have covered classes before and gone through the complexities of overriding
Equals to create equality semantics that make sense. However, there is a better way for
simple types. Rather than trying to simulate value type semantics through overriding
Equals, why not just create a new value type? Structs are similar to classes, the main
difference being that they are a value type, and therefore may be put on the stack. Struct
instances take up much less memory than a class and, if stack allocated, will not need
to be garbage collected.
Creating Structs
To create a struct, define a type as you normally would, but add the [<Struct>] attrib-
ute. Or, you could use the struct and end keywords before and after the struct’s body:
[<Struct>]
type StructPoint(x : int, y : int) =
member this.X = x
member this.Y = y
override this.ToString() =
sprintf "[%d, %d, %d, %d]" top bottom left right
end
Structs have many of the features of classes but may be stored on the stack rather than
the heap. Therefore, equality (by default) is determined by simply comparing the values
on the stack rather than references:
Structs | 159
> // Define two different struct values
let x = new StructPoint(6, 20)
let y = new StructPoint(6, 20);;
> x = y;;
val it : bool = true
One difference between structs and classes, however, is how they are constructed.
Classes have only the constructors you provide, whereas structs automatically get a
default constructor, which assigns the default value to each struct field. (Which you
might recall is zero for value types and null for reference types.)
> // Struct for describing a book
[<Struct>]
type BookStruct(title : string, pages : int) =
member this.Title = title
member this.Pages = pages
override this.ToString() =
sprintf "Title: %A, Pages: %d" this.Title this.Pages;;
type BookStruct =
struct
new : title:string * pages:int -> BookStruct
override ToString : unit -> string
member Pages : int
member Title : string
end
To create a struct in which you can modify its fields, you must explicitly declare each
mutable field in a val binding. In addition, in order to mutate a struct’s fields the
instance of that struct must be declared mutable.
The following snippet defines a struct MPoint with two mutable fields. It then creates
a mutable instance of that struct, pt, and updates the struct’s fields:
> // Define a struct with mutable fields
[<Struct>]
type MPoint =
val mutable X : int
type MPoint =
struct
val mutable X: int
val mutable Y: int
override ToString : unit -> string
end
stdin(54,1): error FS0256: A value must be mutable in order to mutate the contents
of a value type, e.g. 'let mutable x = ...'
Restrictions
Structs have several restrictions in place in order to enforce their characteristics:
• Structs cannot be inherited; they are implicitly marked with [<Sealed>].
• Structs cannot override the default constructor (because the default constructor
always exists and zeros out all memory).
• Structs cannot use let bindings as with the implicit constructor syntax from class
types.
Structs | 161
new structs is much faster than allocating many small objects on the heap. Similarly,
with structs, there is no additional garbage collection step because the stack is cleared
as soon as the function exits.
For the most part, however, the performance gain for struct allocation is negligible. In
fact, if used thoughtlessly, structs can have a detrimental impact on performance as a
result of excessive copying when passing them as parameters. Also, the .NET garbage
collector and memory manager are designed for high-performance applications. When
you think you have a performance problem, use the Visual Studio code profiler to
identify what the bottleneck is first before prematurely converting all of your classes
and records to structs.
Most of the programming we have done so far has been in the functional style. While
this has enabled us to write succinct, powerful programs, we haven’t quite used the
functional style to its full potential. Functional programming means more than just
treating functions as values. Embracing the functional programming paradigm and
letting it help shape your thought process will enable you to write programs that would
otherwise be prohibitively difficult in an imperative style.
In this chapter, we will build on what you learned about functional programming back
in Chapter 3, and I’ll introduce new language features that will help you be more pro-
ductive in the functional style. For example, using active patterns will allow you to
increase the power of your pattern matching and eliminate the need for when guards;
and by creating auto-opened modules,you can extend common F# modules.
In addition, we will look at some of the more mind-bending aspects of functional pro-
gramming. You will learn how to use advanced forms of recursion to avoid stack over-
flows in your code and write more efficient programs using lists. And we will take a
look at some common design patterns for functional code.
Our journey into exploring applied functional programming begins with looking at
how you can harness the power of F#’s type checking and type inference systems to
eliminate bugs in your code.
Units of Measure
There are several universal truths in this world: gravity is 9.8 meters per second squared;
water will boil at over 100 degrees Celsius; and any programmer, no matter how tal-
ented or careful, will have bugs related to units of measure.
If you are ever writing code that deals with real-world units, you will invariably get it
wrong. For example, you might pass in seconds when the function takes minutes, or
mistake acceleration for velocity. The result of these sorts of bugs in software has ranged
from minor annoyances to loss of life.
163
The problem is that if you represent a value with just a floating-point number, you have
no additional information about what that number means. If I give you a float with
value 9.8, you have no idea if it is in miles, meters per second, hours, or even megabytes.
A powerful language feature for combating these dimensional analysis issues is units of
measure. Units of measure allow you to pass along unit information with a floating-
point value—float, float32, decimal—or signed integer types in order to prevent an
entire class of software defects. Consider Example 7-1, which describes a temperature.
Notice how the parameter temp is encoded to take only fahrenheit values. I will cover
exactly what float<_> means later in this section.
Example 7-1. Converting Fahrenheit to Celsius with units of measure
[<Measure>]
type fahrenheit
Because the function accepts only fahrenheit values, it will fail to work with any
floating-point values encoded with a different unit of measure. Calling the function
with an invalid unit of measure will result in a compile-time error (and prevent poten-
tially disastrous results at runtime):
> let seattle = 59.0<fahrenheit>;;
[<Measure>]
type celsius
val cambridge : float<celsius> = 18.0
[<Measure>]
type m
[<Measure>]
type far =
class
static member ConvertToCel : x:float<far> -> float<cel>
end
[<Measure>]
and cel =
class
static member ConvertToFar : x:float<cel> -> float<far>
end
> far.ConvertToCel(100.0<far>);;
val it : float<cel> = 37.77777778
Unit of measure types can also be abbreviated to be relative to other units of measure.
In Example 7-2, a new unit of measure s, for seconds, is defined as well as a relative
unit of measure Hz, for hertz, which stands for cycles per second. Because the units are
relative to one another, two values with the same semantic meaning are considered
equal.
Example 7-2. Defining new units of measure
> // Define seconds and hertz
[<Measure>]
type s
[<Measure>]
type Hz = s ^ −1;;
[<Measure>]
type s
[<Measure>]
type Hz =/s
[<Measure>]
type rads
sin halfPI;;
----^^^^^^^
stdin(7,5): error FS0001: The type 'float<rads>' does not match type 'float'.
> // Drop the units from value halfPi, to convert float<_> to float
sin (float halfPI);;
val it : float = 1.0
If you want to create a type that is generic with regard to a unit of measure, add the
[<Measure>] attribute to a generic type parameter. That generic type parameter will
allow you to refer to the unit of measure, but it cannot be anything else. In fact, the
compiled form of the type will not expose the generic type parameter at all.
Example 7-4 shows defining a point type that preserves a unit of measure.
member this.X = x
member this.Y = y
member this.Length =
let sqr x = x * x
sqrt <| sqr this.X + sqr this.Y
override this.ToString() =
sprintf
"{%f, %f}"
this.UnitlessX
this.UnitlessY
When executed in an FSI session, Example 7-4 looks like the following. Notice how
the unit of measure is persisted through the Length property—taking the square root
of the sum of two squares:
> let p = new Point<m>(10.0<m>, 10.0<m>);;
val p : Point<m>
> p.Length;;
val it : float<m> = 14.14213562
Units of measure are specific to the F# language and not to the Common
Language Runtime. As a result, custom types you create in F# will not
have their units of measure types exposed across assembly boundaries.
So types that are float<'a> will only be exported as float when refer-
enced from C#.
Units of measure are not only suited for “real-world” values, but they can also be helpful
when dealing with abstract units, such as clicks, pixels, dollar amounts, and so on.
Active Patterns
Pattern matching adds power to your programming by giving you a way to be much
more expressive in code branching than using if expressions alone. It allows you to
match against constants, capture values, and match against the structure of data. How-
ever, pattern matching has a significant limitation. Namely, it only has the ability to
match against literal values such as string, or a limited range of class types like arrays
and lists.
To check if elements exist in a seq<_>, you need to resort to using when guards, which
are ugly at best. The following snippet creates a Set type filled with the letters of a
string, and then searches for specific letters in a pattern-match rule’s when guard:
let containsVowel (word : string) =
let letters = word |> Set.ofSeq
match letters with
| _ when letters.Contains('a') || letters.Contains('e') ||
letters.Contains('i') || letters.Contains('o') ||
letters.Contains('u') || letters.Contains('y')
-> true
| _ -> false
Fortunately, in F#, there is a feature that captures all the succinct expressiveness you
want with the elegance of pattern matching, called active patterns.
Active patterns are just special functions that can be used inside of pattern-match rules.
Using them eliminates the need for when guards as well as adding clarity to the pattern
match, which you can use to make the code look as though it maps more to the problem
you are solving.
Active patterns take several forms; each takes an input and converts it into one or more
new values:
• Single-case active patterns convert the input into a new value.
• Partial-case active patterns carve out an incomplete piece of the input space, such
as only matching against strings which contain the letter ‘e’.
• Multi-case active patterns partition the input space into one of several values, such
as partitioning all possible integers into odds, evens, or zero.
Partial-case active patterns allow you to define active patterns that don’t always convert
the input data. To do this, a partial active pattern returns an option type. (Which if you
recall has only two values, Some('a) and None.) If the match succeeds, it returns Some;
otherwise, it returns None. The option type is removed as part of the pattern match when
the result is bound.
Example 7-6 shows how to define a partial active pattern that doesn’t throw an excep-
tion for malformed input. To define a partial active pattern, simply enclose your active
pattern with (|ident|_|) instead of the usual (|ident|). When used in the pattern
match, the rule will only match if the partial active pattern returns Some.
Another way to think about partial active patterns is that they are op-
tional single-case active patterns.
let (|ToInt|_|) x =
let success, result = Int32.TryParse(x)
if success then Some(result)
else None
let (|ToFloat|_|) x =
let success, result = Double.TryParse(x)
if success then Some(result)
else None
if result.Success then
match (List.tail [ for g in result.Groups -> g.Value ]) with
| fst :: snd :: trd :: []
-> Some (fst, snd, trd)
| [] -> failwith <| "Match succeeded, but no groups found.\n" +
"Use '(.*)' to capture groups"
| _ -> failwith "Match succeeded, but did not find exactly three groups."
else
None
Another way to think about multi-case active patterns is that they simply
act as a way of converting the input data into a discriminated union type.
// This active pattern divides all strings into their various meanings.
let (|Paragraph|Sentence|Word|WhiteSpace|) (input : string) =
let input = input.Trim()
| _ -> false
And, &, allows you to test against multiple active patterns simultaneously. Exam-
ple 7-10 combines active patterns to determine if an image file is too large to send in
an email. The example combines a multi-case active pattern that converts a file path
into one of several size-active pattern tags and a second, partial active pattern that will
determine if a file path is an image file.
When executed through FSI, Example 7-11 has the following output:
> // Load the XML Document
let xmlDoc =
let doc = new System.Xml.XmlDocument()
let xmlText =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>
<Parts>
<Widget Diameter='5.0' />
<Sprocket Model='A' SerialNumber='147' />
<Sprocket Model='B' SerialNumber='302' />
</Parts>
"
doc.LoadXml(xmlText)
doc;;
Using Modules
If you recall from Chapter 2, namespaces are a way to organize types. Modules behave
much like namespaces, except that instead of being able to contain only types, modules
can contain values as well.
While modules’ ability to contain values makes it easy to write simple applications,
they are not well suited for being shared with other programmers and .NET languages.
A well-designed class can stand on its own, and is simple and intuitive to use because
all of its complex inner workings are hidden. With a module, you are given a loose
collection of values, functions, and types, and it can be unclear how they all relate.
Identifying when and how to convert a module into a namespace and class is important
for mastering functional programming in F#.
let fullyQualified =
allMatches
|> List.filter (fun url -> url.StartsWith("http://"))
fullyQualified
|> List.map(fun url -> let parts = url.Split( [| '/' |] )
url, parts.[parts.Length - 1])
|> List.iter(fun (url, filename) -> downloadToDisk url (@"D:\Images\" + filename))
The problem with Example 7-12 is that it is an unorganized series of values. While the
code makes perfect sense when executing a series of steps from top to bottom, the next
programmer just sees module properties like results and allMatches, which are mean-
ingless out of context.
The first step to convert this code is to abstract elements into a series of functions,
removing the need for any purely local values. By indenting everything four spaces and
adding a function declaration up top, the module’s contents could be placed inside of
a function. Doing this allows us to break up the code into several pieces:
let downloadWebpage (url : string) =
let req = WebRequest.Create(url)
let resp = req.GetResponse()
let stream = resp.GetResponseStream()
let reader = new StreamReader(stream)
reader.ReadToEnd()
This refactoring would enable you to use the web scraper in the following fashion:
downloadWebpage "http://oreilly.com/"
|> extractImageLinks
|> scrapeWebsite @"C:\Images\"
But loose functions aren’t that useful for the same reasons that loose values aren’t
useful. Namely, they don’t abstract or simplify anything. You still need to figure out
how to get the functions to work together. So the next step would be to create classes
that could then be used by other programmers. All you need to do then is replace the
function value with a type declaration, and its parameters are simply parameters to the
constructor.
In Example 7-13, all the loose functions are brought into the class, so that they are
encapsulated. This leaves only a class with a constructor and a member SaveImagesTo
Disk.
Intentional Shadowing
In F#, the notion of a value is simply a name bound to some data. By declaring a new
value with the same name, you can shadow the previous value with that name:
let test() =
let x = 'a'
let x = "a string"
let x = 3
// The function returns 3
x
In the same vein, opening a module dumps a series of new name/value pairs into the
current environment, potentially shadowing existing identifiers. So if you have a value
in scope named x, and you open a module that also contains a value named x, after the
module is opened x will refer to the module’s value. This will silently shadow the pre-
vious value x, leaving no way to access it.
While this has the potential for introducing subtle bugs, using modules to intentionally
shadow values can be beneficial too. This is exactly the approach F# uses for checking
for arithmetic overflow.
Consider the case of checked arithmetic, that is, arithmetic that throws an exception if
there is an overflow. By default, F#’s mathematical operators do not support this, so
when you overflow the bounds of an integer value, it just wraps around:
> let maxInt = System.Int32.MaxValue;;
> maxInt;;
val it : int = 2147483647
> maxInt + 1;;
val it : int = −2147483648
This ability to intentionally shadow values can be handy for customizing the behavior
of code in a file, but you should be careful to make sure you have the right usage
semantics for the modules you write.
To prevent a module from being opened in this way, you can simply add the
[<RequireQualifiedAccess>] to the module, which bars them from being imported:
[<RequireQualifiedAccess>]
module Foo
let Value1 = 1
[<RequireQualifiedAccess>]
module Bar =
let Value2 = 2
When you try to open a module with the [<RequireQualifiedAccess>] attribute you
will get an error; e.g., in the F# library, the List module requires qualified access:
open List;;
^^^^^^^^^
However, some modules are especially useful and always having their values and types
in scope would be good. In Chapter 6, you learned about extension methods, which
allowed you to augment the members of a class. If you define a set of extension methods
that are especially useful when used with a given namespace, you can mark the module
to be automatically opened as soon as the parent namespace is opened with the
[<AutoOpen>] attribute.
Example 7-14 defines a namespace that contains an auto-opened module. After open-
ing the parent namespace with open Alpha.Bravo, you can access any values in the
module Charlie without needing to fully qualify them because module Charlie was
opened implicitly.
Example 7-14. The AutoOpen attribute
namespace Alpha.Bravo
[<AutoOpen>]
module Charlie =
let X = 1
The first half of this chapter showed you how to take advantage of language elements
in F# and syntax to enable you to write code in the functional style. The rest of the
chapter will focus not on syntax, but theory. Specifically, how to take full advantage of
the unique characteristics of functional programming.
Mastering Lists
Whereas arrays and the List<_> type make up the backbone data structure for imper-
ative programming, lists are the cornerstone of functional programming. Lists offer a
guarantee of immutability, which helps support function composition and recursion,
as well as free you from needing to allocate all the memory up front (such as fixed size
array).
Although lists are pervasive when programming in the functional style, I have not yet
covered how to use them efficiently. Lists in F# have unique performance character-
istics that you should be aware of or else you run the risk of unexpectedly poor
performance.
List Operations
Perhaps the greatest benefit of the FSharpList type is how simple it is to use. While the
List module offers plenty of functionality for using existing lists, when you build up
lists dynamically, there are only two operations to choose from, cons and append.
Cons
The cons operator, ::, adds an element to the front of an existing list. When the cons
operator is used, the result is a new list; the input list is unchanged because all the work
is done by simply allocating a new list element and setting its next element to the start
of the input list.
This can be seen visually in Figure 7-2. Notice that a new list is returned, and the input
list is reused:
> let x = [2; 3; 4];;
> 1 :: x;;
val it : int list = [1; 2; 3; 4]
> x;;
val it : int list = [2; 3; 4]
Because only one list node is introduced, the cons operation for lists is very fast. Re-
member, if you were adding an element to a List<_>, and the internal array were already
Append
The append function, @, joins two lists together. Rather than the last element of the
first list ending, it instead points to the beginning of the second list (forming a new,
longer list). However, because the last element of the first list provided to append needs
to be modified, an entire copy of that list is made.
You can see this in Figure 7-3:
> // Append
let x = [2; 3; 4]
let y = [5; 6; 7];;
> x @ y;;
val it : int list = [2; 3; 4; 5; 6; 7]
Looking at Figures 7-2 and 7-3, it is easy to see that cons is a very fast operation because
all you do is create a new element and you are done. Append, on the other hand, is
potentially more expensive because you need to clone the first list, meaning that for
very long lists, append takes a proportionally long time. (Copying the list not only takes
twice as much time but also twice as much memory!)
Using Lists
Once you know the performance characteristics of lists, you can then use them effec-
tively. Cons should almost always be preferred over append, except in special
circumstances.
// Slow implementation...
let removeConsecutiveDupes1 lst =
// Fast implementation...
let removeConsecutiveDupes2 lst =
let f item acc =
match acc with
| []
-> [item]
| hd :: tl when hd <> item
-> item :: acc
| _ -> acc
List.foldBack f lst []
When using lists, think of append as a “code smell”—it is usually indicative of a sub-
optimal solution.
let createMutableList() =
let l = new List<int>()
for i = 0 to 100000 do
l.Add(i)
l;;
Both solutions look identical, but the createImmutableList function won’t work. (In
fact, it will crash.) I’ll explain the reason why the function is flawed as well as how to
fix it by introducing tail recursion.
You may have heard of tail recursion before; in fact, you may have even written tail-
recursive functions before without even knowing it. Tail recursion is a special form of
recursion, where the compiler can optimize the recursive call to not consume any ad-
ditional stack space. When a function makes a recursive call, the rest of the calling
function may still need to execute some code, so the state of what’s left to execute is
stored on the stack.
However, as recursive functions call themselves deeper and deeper, the limited stack
space can be exhausted, leading to an unrecoverable type of CLR error: the dreaded
StackOverflowException. Because tail recursion doesn’t use any additional stack space,
it provides you a way to use recursion safely.
Understanding tail recursion is crucial for writing efficient and safe functional code. As
you saw in the previous sections, many functional data structures require recursion to
be built up. With this reliance on recursion, stack overflows can become a real threat
and limit the size of data a recursive function can process. However, writing functions
in a tail-recursive style can help avoid this.
If you set a breakpoint on the base case and call factorial with a value of 10, and attach
the Visual Studio debugger, you will see this call stack in Figure 7-4. Notice that each
recursive call creates a new stack frame. Here you can clearly see the recursive function
breaking the parameter x down into smaller and smaller values until x is less than or
equal to 1 and the base case is executed.
Once the base case executes, it will return the value 1. The stack will be restored to
when factorial was called with 2 as its parameter, which will return 2×1. Then the
stack will be restored to when factorial was called with 3 as its parameter, returning
3×2, and so on.
But remember that the stack is in a fixed block of memory, and generally limited to
1MB. So the factorial function will exhaust the stack for sufficiently large values. An
easier demonstration of this is this infLoop function in Example 7-16. Notice how
fsi.exe is launched from the command line, and when the StackOverflow exception is
thrown, the process terminates because you cannot catch the StackOverflowException.
Figure 7-5 shows the same infLoop program but from within the Visual Studio
debugger.
Example 7-17 is the tail-recursive version of factorial. By passing the data around as
an extra parameter, you remove the need to execute code after the recursive function
call, so no additional stack space is required. Instead, the result is built up to be returned
in the base case rather than being unwound from all the recursive calls.
Example 7-17. Tail-recursive version of factorial
let factorial x =
// Keep track of both x and an accumulator value (acc)
let rec tailRecursiveFactorial x acc =
if x <= 1 then
acc
else
tailRecursiveFactorial (x - 1) (acc * x)
tailRecursiveFactorial x 1
When the F# compiler identifies a function as tail-recursive, it generates the code dif-
ferently. For example, the tailRecursiveFactorial function would be generated as the
following C# code. Notice how the recursion is simply replaced with a while loop:
// tailRecursiveFactorial function as it would get compiled in C#.
public static int tailRecursiveFactorial (int x, int acc)
{
while (true)
{
if (x <= 1)
{
return acc;
}
acc *= x;
x--;
}
}
If you fire up the Visual Studio debugger and set a breakpoint on the base case of the
tailRecursiveFactorial function, you’ll see that only one stack frame has been used,
as shown in Figure 7-6.
Tail-Recursive Patterns
There are two main ways to ensure that a recursive function is tail-recursive. The sim-
plest applies to when you are whittling down the input while at the same time building
up the answer. The second, and much more mind-bending approach, is used when you
need to have two recursive calls and branch code flow.
However, that code is not tail-recursive because on the fourth line the recursive call to
map completes and then the cons operation completes. To combat this, you can use the
accumulator pattern to simply store the completed result. In Example 7-18, a nested
recursive function is defined that takes the mapped list as a parameter and returns it in
the base case.
Example 7-18. The accumulator pattern
let map f list =
let rec mapTR f list acc =
match list with
| [] -> acc
| hd :: tl -> mapTR f tl (f hd :: acc)
mapTR f (List.rev list) []
For simple recursive functions the accumulator pattern is all you need. But what if there
is some sort of branching? A function where in order for the function to return, you
need to make two recursive calls? (So-called binary recursion.) One recursive call at the
very least won’t be in tail position. Consider Example 7-19, which is an implementation
of a binary tree and an iter operation that applies a function to each node in the tree.
Example 7-19. Non-tail-recursive iter implementation
type BinTree<'a> =
| Node of 'a * BinTree<'a> * BinTree<'a>
| Empty
Continuations
Imagine rather than passing the current state of the accumulator so far as a parameter
to the next function call, you passed a function value representing the rest of the code
to execute; i.e., rather than storing “what’s left” on the stack, you store it in a function.
This is known as the continuation passing style or simply using a continuation.
Continuations are when you return a function value that represents the rest of the code
to execute when the current recursive call completes. This allows you to write tail-
recursive functions even though there is more work to be done. Conceptually, you are
trading stack space (the recursive calls) with heap space (the function values).
Consider Example 7-20, which prints a list of integers in reverse. The printing is done
as part of the continuation, which grows larger with each recursive call. In the base
case, the continuation is called. The function printListRevTR as well as the built con-
tinuation are both tail-recursive, so no matter how much input was processed or how
large the resulting continuation becomes, it will not overflow the stack.
Example 7-20. Building up a function value
> // Print a list in revere using a continuation
let printListRev list =
let rec printListRevTR list cont =
match list with
// For an empy list, execute the continuation
| [] -> cont()
// For other lists, add printing the current
// node as part of the continuation.
| hd :: tl ->
printListRevTR tl (fun () -> printf "%d " hd
cont() )
Admittedly the act of building up a function value is a little mind-bending. But this
technique allows you to write functions that otherwise would be impossible to imple-
ment in a tail-recursive fashion.
Let’s revisit the binary tree iter operation. This operation needed to perform two re-
cursive calls, and therefore the function cannot be tail-recursive using the accumulator
processSteps steps
While this example will take some time staring at to make sense, it is one way to break
apart recursive functions and write tail-recursive code.
Admittedly, such a complicated solution is likely not the best one. Perhaps using a
mutable data structure to store nodes to visit, in the order in which to visit them, would
make more sense. (For example, keeping a mutable Stack<_> of nodes.) But when pro-
gramming in F#, you have options to find the solution that works best for you.
Currying
Recall that function currying is the ability to specify a subset of a function’s parameters
and return new functions taking fewer parameters. On the surface, this seems like a
strange feature to add. What does only providing a subset of a function’s parameters
actually buy you? The most visible benefit of function currying is the pipe-forward
operator, which makes data transformation much more elegant:
getCustomers()
|> List.filter (fun cust -> datesEqual cust.Birthday DateTime.Today)
|> List.map (fun cust -> cust.EmailAddress)
|> List.filter Option.isSome
|> sendSpam "Happy Birthday... Now buy our product!"
Each line of the previous expression is a curried function whose last parameter is pro-
vided by the previous line of code. Writing code in this style enables you to write code
exactly the way you conceptualize it in your head. But function currying has another
way of cleaning up your code as well.
Note that the lambda’s parameter x is passed as the last parameter to the function f.
Whenever you see code like this, you can remove the lambda altogether because f can
be passed as a parameter to the function List.map (which is the way you see most
functions passed to List.map):
List.map f lst
In much the same vein, you can simplify functions even if they accept more than one
parameter. In the following example, g takes four parameters a, b, c, and x. However,
because the lambda takes only one parameter, x, you can just pass the partially applied
function value as a parameter:
List.map (fun x -> g a b c x) lst
Again, the code can be simplified again by just currying the function:
Both of these functions are nearly identical and differ only in how they compare the
given element. Why not factor that out? If you define the compare function as a pa-
rameter, you can swap out implementations much more easily, potentially resulting in
more readable code. Note that the functions pickCheapest2 and pickHealthiest2 rely
on function currying from their partially applied call to pickItem:
let private pickItem cmp menuItems =
let reduceFunc acc item =
match cmp acc item with
| true -> acc
| false -> item
List.reduce reduceFunc menuItems
let pickCheapest2 = pickItem (fun acc item -> item.Price < acc.Price)
let pickHealthiest2 = pickItem (fun acc item -> item.Calories < acc.Calories)
In essence, with closures a function may capture some additional data with it, which
enables the function to know about values in the scope where the function was declared.
With this we can achieve some interesting results.
Imagine creating an abstract data type, like a class, without any explicit fields to store
the type’s state. It doesn’t sound like it is possible—because in order for the type to
have some meaning, it must store its internal data somewhere. But with closures that
isn’t the case.
Consider Example 7-22, which defines a record type Set where its two fields are func-
tions, one to add items to the Set, and another to check if an item exists in the set. To
create an instance of Set, simply access the static property Empty. Once you have an
instance of Set, adding and removing items works entirely by capturing the data in
closures. The function makeSet is passed in a list—which comprises the items contained
in the set. When new items are added to the list, a recursive call to makeSet is returned,
capturing the original set’s item list and the new item to be added in the closure of the
updated Add function.
Example 7-22. Data encapsulation via closures
// Data type for a set. Notice the implementation is
// stored in record fields...
type Set =
{
// Add an item to the set
Add : int -> Set
// Checks if an element exists in the set
You can see the Set type in action in the following FSI session:
> // Set in action
let s = Set.Empty
let s' = s.Add(1)
let s'' = s'.Add(2)
let s''' = s''.Add(3);;
val s : Set
val s' : Set
val s'' : Set
val s''' : Set
Just like writing super-complicated continuations, there are likely better ways to write
types than by storing internal data entirely in closures. However, understanding that
functions can contain more than just their parameters opens up new possibilities.
Functional Patterns
Now that we have covered some advanced concepts in the functional style of pro-
gramming, let’s examine some common design patterns they enable. Describing solu-
tions to problems in terms of design patterns makes it easier to explain and reuse such
solutions.
Memoization
When you write in the functional style, most of the functions you write will be pure,
meaning that for the same input they will always produce the same output. However,
if you call the same pure function many times, it can be a waste of resources to recom-
pute known values. Instead, it would be more efficient to store those values in a
Dictionary<_,_> type that maps input values to function results.
The following FSI session shows the memoize function in action. The function f simply
waits a given number of seconds, and calling the function twice with the same input
will cause it to wait twice. Calling the memoized version will simply look up the cached
value the second time, so the function doesn’t need to actually be evaluated:
> // Some long running function...
let f x =
// Sleep for x seconds
System.Threading.Thread.Sleep(x * 1000)
// Return x
x;;
> // Benchmark
#time;;
> f 10;;
Real: 00:00:10.007, CPU: 00:00:00.000, GC gen0: 0, gen1: 0, gen2: 0
val it : int = 10
> f 10;;
Real: 00:00:10.008, CPU: 00:00:00.000, GC gen0: 0, gen1: 0, gen2: 0
val it : int = 10
memoize fib;;
memoize fib;;
> #time;;
type Widget =
| Sprocket of string * int
| Cog of string * float
val mutable generateWidget : (unit -> Widget)
To be clear, calling a function when you aren’t 100% sure what it does can lead to
heinous bugs. But being able to change the implementation of a function can be very
helpful in certain situations.
Lazy Programming
I mentioned lazy programming briefly in Chapter 3, but it is a style in and of itself. In
short, lazy programming means computing values only when needed—as opposed to
eagerly, which is as soon as they are declared.
Lazy programming can have a few distinct advantages if used correctly.
let allCsvData =
csvFiles
|> Seq.map processFile
|> Seq.concat
Operators
Using a symbolic notation for manipulating objects can simplify your code by elimi-
nating the need to call methods like Add and Remove, instead using more intuitive coun-
terparts like + and -. Also, if an object represents a collection of data, being able to
index directly into it with the .[ ] or .[expr .. expr] notation can make it much easier
to access the data an object holds.
Operator Overloading
Operator overloading is a term used for adding new meaning to an existing operator,
like + or -. Instead of only working on primitive types, you can overload those operators
to accept custom types you create. Operator overloading in F# can be done by simply
adding a static method to a type. After that, you can use the operator as if it natively
supported the type. The F# compiler will simply translate the code into a call to your
static method.
205
When you overload an operator, the operator’s name must be in parentheses, for ex-
ample (+) to overload the plus operator. To overload a unary operator, simply prefix
the operator with a tilde ~.
Example 8-1 overloads the + and − operators to represent operators on a bottle. Note
the ~- operator allows for unary negation.
Example 8-1. Operator overloading
[<Measure>]
type ml
override this.ToString() =
sprintf "Bottle(%.1fml)" (float capacity)
Once the proper operators have been added, then you can intuitively use the class using
the standard symbolic operators you would expect. (Well, a bottle really can’t have a
negative volume, but you get the idea.)
> let half = new Bottle(500.0<ml>);;
You can also add overloaded operators to discriminated unions and record types in the
same way as they are added to class types:
type Person =
| Boy of string
| Girl of string
| Couple of Person * Person
static member (+) (lhs, rhs) =
match lhs, rhs with
| Couple(_), _
Using the + operator, we can now take two Person objects and convert them into a
Couple union case:
Boy("Dick") + Girl("Jane");;
val it : Person = Couple (Boy "Dick",Girl "Jane")
Unary operators:
~+, ~-, ~!, ~++, ~--
Indexers
For types that are a collection of values, an indexer is a natural way to extend the
metaphor by enabling developers to index directly into the object. For example, you
can get an arbitrary element of an array by using the .[ ] operator.
In F#, you can add an indexer by simply adding a property named Item. This property
will be evaluated whenever you call the .[ ] method on the class.
Example 8-2 creates a class Year and adds an indexer to allow you to look up the nth
day of the year.
Example 8-2. Adding an indexer to a class
open System
Operators | 207
The following FSI session shows using the indexer for type Year:
> // Using a custom indexer
let eightyTwo = new Year(1982)
let specialDay = eightyTwo.[171];;
But what if you want to not only read values from an indexer, but write them back as
well? Also, what if you wanted the indexer to accept a non-integer parameter? The F#
language can accommodate both of those requests.
Example 8-3 defines an indexer for a type that accepts a non-integer parameter. In a
different take on the Year class example, the indexer takes a month and date tuple.
Example 8-3. Non-integer indexer
type Year2(year : int) =
Year2 uses a more intuitive indexer that makes the type easier to use:
> // Using a non-integer index
let O'seven = new Year2(2007)
let randomDay = O'seven.["April", 7];;
The read-only indexers you’ve seen so far aren’t ideal for every situation. What if you
wanted to provide a read/write indexer to not only access elements but also update
their values? To make a read/write indexer, simply add an explicit getter and setter to
the Item property.
Example 8-4 defines a type WordBuilder that allows you to access and update letters of
a word at a given index.
member this.Item
with get idx = m_letters.[idx]
and set idx c = m_letters.[idx] <- c
The syntax for writing a new value using the indexer is the same as updating a value in
an array:
> let wb = new WordBuilder("Jurassic Park");;
val wb : WordBuilder
> wb.Word;;
val it : string = "Jurassic Park"
> wb.[10];;
val it : char = 'a'
> wb.[10] <- 'o';;
val it : unit = ()
> wb.Word;;
val it : string = "Jurassic Pork"
Adding Slices
Similar to indexers, slices allow you to index into a type that represents a collection of
data. The main difference is that where an indexer returns a single element, a slice
produces a new collection of data. Slices work by providing syntax support for pro-
viding an optional lower bound and an optional upper bound for an indexer.
To add a one-dimensional slice, add a method named GetSlice that takes two option
types as parameters. Example 8-5 defines a slice for a type called TextBlock where the
slice returns a range of words in the body of the text.
Example 8-5. Providing a slice to a class
type TextBlock(text : string) =
let words = text.Split([| ' ' |])
member this.AverageWordLength =
words |> Array.map float |> Array.average
Operators | 209
| Some(lb), None -> words.[lb..]
| None, Some(ub) -> words.[..ub]
// No lower or upper bounds
| None, None -> words.[*]
words
val text : string = "The quick brown fox jumped over the lazy dog"
val tb : TextBlock
> tb.[..5];;
val it : string [] = [|"The"; "quick"; "brown"; "fox"; "jumped"; "over"|]
> tb.[4..7];;
val it : string [] = [|"jumped"; "over"; "the"; "lazy"|]
If you need a more refined way to slice your data, you can provide a two-dimensional
slicer. This allows you to specify two ranges of data. To define a two-dimensional slice,
simply add a method called GetSlice again, except that the method should now take
four option-type parameters. In order, they are first dimension lower bound, first di-
mension upper bound, second dimension lower bound, and second dimension upper
bound.
Example 8-6 defines a class that encapsulates a sequence of data points. The type pro-
vides a 2D slice that returns only those points within the given range.
Example 8-6. Defining a two-dimensional slice
open System
> points;;
val it : seq<float * float> =
seq
[(0.6313018304, 0.8639167859); (0.3367836328, 0.8642307673);
(0.7682324386, 0.1454097885); (0.6862907925, 0.8801649925); ...]
val d : DataPoints
> // Get all values where the x and y values are greater than 0.5.
d.[0.5.., 0.5..];;
Slices provide an expressive syntax for extracting data from types. However, you cannot
add a slice notation for a type higher than two dimensions.
exception NotGreaterThanHead
There are six ways to constrain types in F# and using multiple type constraints allows
you to refine a type parameter. To combine multiple type constraints, simply use the
and keyword:
open System
let compareWithNew (x : 'a when 'a : (new : unit -> 'a) and
'a :> IComparable<'a>) =
Subtype constraint
The subtype constraint enforces that the generic type parameter must match or be
a subtype of a given type. (This also applies to implementing interfaces.) This al-
lows you to cast 'a as a known type. The syntax for a subtype constraint is as
follows:
'a :> type
Delegate constraint
The delegate constraint requires that the type argument must be a delegate type,
with the given signature and return type. (Delegates will be covered in the next
section.) The syntax for defining a delegate constraint is as follows:
'a : delegate<tupled-args, return-type>
Struct constraint
The struct constraint enforces that the type argument must be a value type. The
syntax for the struct constraint is as follows:
'a : struct
Reference constraint
The reference constraint enforces that the type argument must be a reference type.
The syntax for the reference constraint is as follows:
'a : not struct
Enumeration constraint
With the enumeration constraint the type argument must be an enumeration type
whose underlying type must be of the given base type (such as int). The syntax for
the enumeration constraint is as follows:
'a : enum<basetype>
In addition, F# defines comparison and equality constraints. But in practice, they are
seldom required or even needed.
[<Measure>]
type ml
member this.Drink(amount) =
printfn "Drinking %.1f..." (float amount)
m_amountLeft <- min (m_amountLeft - amount) 0.0<ml>
if m_amountLeft <= 0.0<ml> then
this.LetPeopleKnowI'mEmpty()
member this.Refil(amountAdded) =
printfn "Coffee Cup refilled with %.1f" (float amountAdded)
m_amountLeft <- m_amountLeft + amountAdded
member this.WhenYou'reEmptyCall(func) =
m_interestedParties.Add(func)
When used in an FSI session, the following transpires after a refreshing cup of coffee
has been emptied:
> let cup = new CoffeeCup(100.0<ml>);;
> cup.Drink(75.0<ml>);;
Drinking 75.0...
val it : unit = ()
> cup.Drink(75.0<ml>);;
Drinking 75.0...
Uh oh, I'm empty! Letting people know...
Thanks for letting me know...
Coffee Cup refilled with 50.0
val it : unit = ()
Defining Delegates
To define a delegate, simply use the following syntax. The first type represents the
parameters passed to the delegate, and the second type is the return type of the delegate
when called:
type ident = delegate of type1 -> type2
Example 8-9 shows how to create a simple delegate and how similar it is to a regular
function value. Notice how to instantiate a delegate type you use the new keyword and
pass the body of the delegate in as a parameter, whereas to execute a delegate you must
call its Invoke method.
// Defining a delegate
type DelegateType = delegate of int * int -> int
F# has a bit of magic that eliminates the need for instantiating some delegate types. If
a class member takes a delegate as a parameter, rather than passing an instance of that
delegate type you can pass in a lambda expression, provided it has the same signature
as the delegate.
The following code snippet defines a delegate IntDelegate and a class method Apply
Delegate. ApplyDelegate is called twice, the first time using an explicit instance of
IntDelegate, and again using an implicit instance. The lambda expression provided
would create a valid instance of IntDelegate and so the F# compiler creates one behind
the scenes.
This dramatically cleans up F# code that interacts with .NET components that expect
delegates, such as the Parallel Extensions to the .NET Framework, which we’ll discuss
in Chapter 11:
type IntDelegate = delegate of int -> unit
type ListHelper =
/// Invokes a delegate for every element of a list
static member ApplyDelegate (l : int list, d : IntDelegate) =
l |> List.iter (fun x -> d.Invoke(x))
let printToConsole =
LogMessage(fun msg -> printfn "Logging to console: %s..." msg)
let appendToLogFile =
LogMessage(fun msg -> printfn "Logging to file: %s..." msg
use file = new StreamWriter("Log.txt", true)
file.WriteLine(msg))
The following is the output from the FSI session. Invoking the typedDoBoth delegate
executes both the printToConsole delegate and the appendToLogFile delegate. F# func-
tion values cannot be combined in this way.
> typedDoBoth.Invoke("[some important message]");;
Logging to console: [some important message]...
Logging to file: [some important message]...
val it : unit = ()
The second main difference between delegate types and function values is that you can
invoke a delegate asynchronously, so the delegate’s execution will happen on a separate
thread and your program will continue as normal. To invoke a delegate asynchronously,
call the BeginInvoke and EndInvoke methods.
Unfortunately, delegates created in F# do not have the BeginInvoke and EndInvoke
methods, so to create delegates that can be invoked asynchronously, you must define
them in VB.NET or C#. In Chapter 11, we will look at parallel and asynchronous
programming in F# and simple ways to work around this.
Creating Events
Let’s start with a simple example and work backward from that. Unlike C# or VB.NET,
there is no event keyword in F#.
Example 8-11 creates a NoisySet type that fires events whenever items are added and
removed from the set. Rather than keeping track of event subscribers manually, it uses
the Event<'Del, 'Arg> type, which will be discussed shortly.
Example 8-11. Events and the Event<_,_> type
type SetAction = Added | Removed
let m_itemAdded =
new Event<SetOperationDelegate<'a>, SetOperationEventArgs<'a>>()
let m_itemRemoved =
new Event<SetOperationDelegate<'a>, SetOperationEventArgs<'a>>()
member this.Add(x) =
m_set <- m_set.Add(x)
// Fire the 'Add' event
m_itemAdded.Trigger(this, new SetOperationEventArgs<_>(x, Added))
member this.Remove(x) =
m_set <- m_set.Remove(x)
// Fire the 'Remove' event
m_itemRemoved.Trigger(this, new SetOperationEventArgs<_>(x, Removed))
We will cover the Event<_,_> type shortly, but note that whenever you want to raise
the event or add subscribers to it, you must use that type. Raising the event is done by
calling the Trigger method, which takes the parameters to the delegates to be called:
// Fire the 'Add' event
m_itemAdded.Trigger(this, new SetOperationEventArgs<_>(x, Added))
Subscribing to events
Finally, to subscribe to a class’s events, use the AddHandler method on the
event property. From then on, whenever the event is raised, the delegate you passed to
AddHandler will be called. If you no longer want your event handler to be called, call
the RemoveHandler method.
let setOperationHandler =
new SetOperationDelegate<int>(
fun sender args ->
printfn "%d was %A" args.Value args.Action
)
Events | 219
s.ItemAddedEvent.AddHandler(setOperationHandler)
s.ItemRemovedEvent.AddHandler(setOperationHandler);;
val s : NoisySet<int>
val setOperationHandler : SetOperationDelegate<int>
> s.Add(9);;
9 was Added
val it : unit = ()
> s.Remove(9);;
9 was Removed
val it : unit = ()
As you can see from Example 8-12, all the work of combining delegates and eventually
raising the event was handled by the Event<'Del, 'Arg> class. But what is it exactly? And
why does it need two generic parameters?
type Clock() =
member this.Start() =
printfn "Started..."
while true do
// Sleep one second...
Threading.Thread.Sleep(1000)
val c : Clock
> c.Start();;
Started...
[14:48:27]
[14:48:28]
[14:48:29]
[<Measure>]
type bpm = 1/minute
Events | 221
type Song = { Title : string; Genre : MusicGenre; BPM : int<bpm> }
type JukeBox() =
let m_songStartedEvent = new Event<SongChangeDelegate, SongChangeArgs>()
member this.PlaySong(song) =
m_songStartedEvent.Trigger(
this,
new SongChangeArgs(song.Title, song.Genre, song.BPM)
)
[<CLIEvent>]
member this.SongStartedEvent = m_songStartedEvent.Publish
Rather than just adding event handlers directly to the JukeBox type, we can use the
Observable module to create new, more specific event handlers. In Example 8-15, the
Observable.filter and Observable.partition methods are used to create two new
events, slowSongEvent and fastSongEvent, which will be raised whenever the JukeBox
plays songs that meet a certain criterion.
The advantage is that you can take an existing event and transform it into new events,
which can be passed around as values.
Example 8-15. Using the Event module
> // Use the Observable module to only subscribe to specific events
let jb = new JukeBox()
val jb : JukeBox
val slowSongEvent : IObservable<SongChangeArgs>
val fastSongEvent : IObservable<SongChangeArgs>
However, Observable.filter and Observable.partition are not the only useful meth-
ods in the module.
Observable.add
Observable.add simply subscribes to the event. Typically, this method is used at the
end of a series of forward-pipe operations and is a cleaner way to subscribe to events
than calling AddHandler on the event object.
Observable.add has the following signature:
val add : ('a -> unit) -> IObservable<'a> -> unit
Example 8-16 pops up a message box whenever the mouse moves into the bottom half
of a form.
Example 8-16. Subscribing to events with Observable.add
open System.Windows.Forms
form.MouseMove
|> Observable.filter (fun moveArgs -> moveArgs.Y > form.Height / 2)
|> Observable.add (fun moveArgs -> MessageBox.Show("Moved into bottom half!")
|> ignore)
form.ShowDialog()
Observable.merge
Observable.merge takes two input events and produces a single output event, which
will be fired whenever either of its input events is raised.
Observable.merge has the following signature:
val merge: IObservable<'a> -> IObservable<'a> -> IObservable<'a>
Events | 223
This is useful when trying to combine and simplify events. For example, in the previous
example, if a consumer didn’t care about specifically dancing to slow or fast songs,
both the song events could be combined into a single justDance event:
> // Combine two song events
let justDanceEvent = Observable.merge slowSongEvent fastSongEvent
justDanceEvent.Add(fun args -> printfn "You start dancing, regardless of tempo!");;
Observable.map
Sometimes when working with an event value, you want to transform it into some sort
of function that is easier to work with. Observable.map allows you to convert an event
with a given argument type into another. It has the following signature:
val map: ('a -> 'b) -> IObservable<'a> -> IObservable<'b>
When using Windows Forms, all mouse-click events are given in pixels relative to the
top left of the form. So for a given form f the top left corner is at position (0, 0) and the
bottom right corner is at position (f.Width, f.Height). Example 8-17 uses Event.map to
create a new Click event that remaps the positions to points relative to the center of
the form.
Example 8-17. Transforming event arguments with Event.map
> // Create the form
open System.Windows.Forms
form.MouseClick.AddHandler(
new MouseEventHandler(
fun sender clickArgs ->
printfn "MouseClickEvent @ [%d, %d]" clickArgs.X clickArgs.Y
)
);;
> // Create a new click event relative to the center of the form
let centeredClickEvent =
form.MouseClick
|> Observable.map (fun clickArgs -> clickArgs.X - (form.Width / 2),
clickArgs.Y - (form.Height / 2))
> // The output is from clicking the dialog twice, first in the
// top left corner and then in the center.
form.ShowDialog();;
MouseClickEvent @ [4, 8]
CenteredClickEvent @ [−146, −142]
MouseClickEvent @ [150, 123]
CenteredClickEvent @ [0, −27]
val it : DialogResult = Cancel
Using the Observable module enables you to think about events in much the same way
as function values. This adds a great deal of expressiveness and enables you to do more
with events in F# than you can in other .NET events.
[<Measure>]
type ml
member this.Drink(amount) =
printfn "Drinking %.1f..." (float amount)
m_amountLeft <- min (m_amountLeft - amount) 0.0<ml>
if m_amountLeft <= 0.0<ml> then
m_emptyCupEvent.Trigger(this, new EventArgs())
Events | 225
member this.Refil(amountAdded) =
printfn "Coffee Cup refilled with %.1f" (float amountAdded)
m_amountLeft <- m_amountLeft + amountAdded
[<CLIEvent>]
member this.EmptyCup = m_emptyCupEvent.Publish
In previous chapters, you saw how F# can be effective across programming paradigms.
But F# is not only well suited for different styles of programming, it can also be effective
for writing different types of programs as well. F# is not only useful for client applica-
tions but it can also be effective as a scripting language too.
Scripting is the general term used to describe programs that don’t have a UI and are
focused on solving a particular task. In most scripting languages, the script consists
entirely of code—as opposed to an executable program—that is then interpreted by a
runtime engine.
Programming in this mode typically sacrifices execution speed for the convenience of
being able to easily modify and deploy the program. Rather than creating a complex
solution designed to work everywhere, you can simply move a script from machine to
machine and modify it as necessary.
While F# may be best suited for client application programming, using it in a scripting
context has one major advantage: you already know it. Rather than learning a separate
language for your scripts, employing F# for your scripting needs lets you reuse your
existing code and knowledge about F# programming. Also, F# code is never interpre-
ted; it is always compiled first. So F# can be comparatively faster than pure scripting
languages.
If you have a simple problem and don’t need a complicated interface, consider scripting
the solution. In this chapter, you will learn about some constructs available to script
files as well as see some recipes for creating useful F# scripts that you can put to good
use right away.
229
F# Script Files
When F# is installed on a system, it registers .fsx files as F# script files. These are F#
source files specifically designed to be easy to execute. Double-clicking on an F# script
file will bring up Visual Studio to edit the script. Right-clicking on the file in Windows
Explorer enables you to execute it directly by selecting the “Run with F# Interactive”
context menu item, seen in Figure 9-1.
Executing an F# script file simply sends the contents of the file to the console version
of F# Interactive, fsi.exe.
F# script files behave just as if you had opened the code file within Visual Studio and
sent it all directly to FSI. (In fact, that is exactly what happens.) The code will then be
compiled and executed, performing whatever actions are in the script. In F# script files,
you have full usage of the F# language and a few additional features to help make
writing scripts easier.
Directives
The most noticeable difference between normal F# source files and F# script files is
the usage of compiler directives. These are special hints and commands to the F#
compiler.
__SOURCE_DIRECTORY__
The Source Directory directive, available with __SOURCE_DIRECTORY__, returns the cur-
rent directory of the executing code. This is especially helpful in scripts for determining
where the script file is located. Any instance of __SOURCE_DIRECTORY__ will be replaced
with a constant string as soon as the file is compiled:
C:\Program Files\Microsoft F#\v4.0>fsi.exe
> __SOURCE_DIRECTORY__;;
val it : string = "C:\Program Files\Microsoft F#\v4.0"
> #q;;
__SOURCE_FILE__
The __SOURCE_FILE__ directive returns the current filename. When you combine this
directive with __SOURCE_DIRECTORY__, you can determine the actual script being
executed.
Example 9-1 runs a script that prints the __SOURCE_FILE__ to the console. Like
__SOURCE_DIRECTORY__, __SOURCE_FILE__ will be replaced with a string as soon as the file
is compiled.
Example 9-1. Using __SOURCE_FILE__ and __SOURCE_DIRECTORY__
C:\Program Files\Microsoft F#\v4.0>type AweomestScriptEver.fsx
printfn "__SOURCE_DIRECTORY__ = %s" __SOURCE_DIRECTORY__
printfn "__SOURCE_FILE__ = %s" __SOURCE_FILE__
F# Script-Specific Directives
The following directives are not available in regular F# source files. Rather, they are
specifically designed to be used in F# script files to provide an experience as though
Directives | 231
they were single-file projects within Visual Studio. These directives are also available
from within an FSI session in Visual Studio.
#r
The #r directive references an assembly. This allows your F# script file to reference
types defined in a separate .NET assembly, perhaps written in another language, such
as C#. The string passed to the #r directive can be as simple as the assembly’s shortened
file name or the full assembly name containing the name, version, culture, and so on.
Example 9-2 shows how to load the Windows Forms libraries to display a list of files
located in the Pictures folder on a data grid.
Example 9-2. Loading assembly references in scripts
#r "System.Windows.Forms.dll"
#r "System.Drawing.dll"
open System
open System.IO
open System.Collections.Generic
open System.Drawing
open System.Windows.Forms
Directory.GetFiles(myPictures, "*.JPG")
|> Seq.map (fun filePath ->
Path.GetFileName(filePath),
Bitmap.FromFile(filePath))
f.ShowDialog()
#r "Microsoft.DirectX.dll"
#r "Microsoft.DirectX.Direct3D.dll"
#r "Microsoft.DirectX.Direct3Dx.dll"
// ...
#load
The #load directive opens up another F# file with a relative path and executes it. This
is for when you want scripts to be broken up across multiple files, or if you want to
reuse some existing F# code. Loading a code file will work as if you had opened the
file and sent its entire contents directly into the FSI session. Example 9-3 shows loading
a code file for displaying charts and plots while the actual script file does the
computation.
Example 9-3. Loading a code file into an F# script
#load "Plotting.fs"
open System
let data =
seq {
let rng = new Random()
for i in 1 .. 10000 do
yield (rng.NextDouble(), rng.NextDouble())
}
Directives | 233
F# Script Recipes
This section will highlight some simple utilities that can help make your F# scripts
more effective—such as taking advantage of console coloring or automating data input
to Microsoft Excel.
Colorful Output
Scripts don’t primarily invest in a UI, rather they are designed not to need any user
input. However, taking advantage of a color console can dramatically improve the
readability of your script’s output. Example 9-4 defines a cprintfn function that be-
haves just like printfn, taking a string filled with format specifiers and additional ar-
guments, except that it also takes a color parameter.
Example 9-4. Producing colorful output
/// Colorful printing for helpful diagnostics
let cprintfn c fmt =
Printf.kprintf
(fun s ->
let orig = System.Console.ForegroundColor
System.Console.ForegroundColor <- c;
System.Console.WriteLine(s)
System.Console.ForegroundColor <- orig)
fmt
The cprintfn function takes advantage of the Printf.kprintf function in the F# library,
which does all the conversion of the format string, such as "%d %f %s", and the addi-
tional arguments, and calls the provided function once they have been combined into
the final result. The cprintfn function then simply updates the console color before
doing the actual printing.
The following code prints text to the console in various colors. The last section of the
code uses a little functional programming magic to print a string where each letter is
in a different color—see if you can figure out how it works (hint: a string type can be
treated as if it were a seq<char>):
open System
let rotatingColors =
seq {
let i = ref 0
let possibleColors = Enum.GetValues(typeof<ConsoleColor>)
while true do
yield (enum (!i) : ConsoleColor)
i := (!i + 1) % possibleColors.Length
}
Producing Sound
Similar to using the console’s ability to color output as a way of improving your scripts,
you can also take advantage of the console’s ability to beep. To have your script make
sounds, you can use the System.Console.Beep method, which takes a frequency and
duration as parameters. While potentially very annoying, having an audible cue for
when a script is complete or when a special event has occurred can be helpful.
The following code defines two functions, victory and defeat, which play sounds to
indicate success or failure. victory beeps ascending tones to convey the sheer joy of
success, while defeat beeps a series of increasingly depressing low tones:
open System
/// Victory!
let victory() =
for frequency in [100 .. 50 .. 2000] do
Console.Beep(frequency, 25)
/// Defeat :(
let defeat() =
for frequency in [2000 .. −50 .. 37] do
Console.Beep(frequency, 25)
This following script can be applied to walk a directory tree and identify specific files,
which can then be processed by your script. Notice that using the __SOURCE_DIREC
TORY__ directive allows you to scan each file under the directory where the script resides.
__SOURCE_DIRECTORY__
|> filesUnder
|> Seq.filter(fun filePath -> filePath.ToUpper().EndsWith("JPG"))
|> Seq.iter(fun filePath -> let fileName = Path.GetFileName(filePath)
let destPath = Path.Combine(NewImageFolder, fileName)
File.Copy(filePath, destPath))
For more sophisticated scripts, you will probably need to check the result of the process,
either the process exit code or its console output.
Example 9-6 defines a function called shellExecute that launches a program with the
given arguments and returns its exit code and output as a tuple.
Example 9-6. Launching a new process
open System.Text
open System.Diagnostics
proc.WaitForExit()
(proc.ExitCode, driverOutput.ToString())
open Microsoft.Office.Interop.Word
doc.PrintOut(
Background = comarg true,
Range = comarg WdPrintOutRange.wdPrintAllDocument,
Copies = comarg 1,
PageType = comarg WdPrintOutPages.wdPrintAllPages,
PrintToFile = comarg false,
Collate = comarg true,
ManualDuplexPrint = comarg false,
PrintZoomColumn = comarg 2, // Pages 'across'
PrintZoomRow = comarg 2) // Pages 'up down'
// -------------------------------------------------------------
open System
open System.IO
try
openWord()
Directory.GetFiles(__SOURCE_DIRECTORY__, "*.docx")
|> Array.iter
(fun filePath ->
let doc = openDocument filePath
printDocument doc
closeDocument doc)
finally
closeWord()
Example 9-8 creates a new Microsoft Excel workbook and dumps the contents of the
Pictures folder to a spreadsheet. You could just as easily store the results of some
computations.
The script starts up by loading up the Excel application, creating a new workbook, and
allowing spreadsheet cells to be set using the setCellText function. The script then
walks through each file in the My Pictures folder and prints the filename, size, etc.
Example 9-8. Creating Excel worksheets
#r "Microsoft.Office.Interop.Excel"
open System
open System.IO
open System.Reflection
open Microsoft.Office.Interop.Excel
// Print header
printCsvToExcel 0 "Directory, Filename, Size, Creation Time"
// Print rows
filesUnderFolder (Environment.GetFolderPath(Environment.SpecialFolder.MyPictures))
|> Seq.map (fun filename -> new FileInfo(filename))
|> Seq.map (fun fileInfo -> sprintf "%s, %s, %d, %s"
fileInfo.DirectoryName
fileInfo.Name
fileInfo.Length
(fileInfo.CreationTime.ToShortDateString()))
|> Seq.iteri (fun idx str -> printCsvToExcel (idx + 1) str)
F# scripts allow you to use F# code outside of the normal “projects and solution”
format of Visual Studio. F# scripts enable many new scenarios that would otherwise
be prohibitively annoying to automate in a language like C#, or easy in a language like
Python but would require additional software to be installed and configured.
Using F# scripts, you can automate tasks such as uploading files to a web server, an-
alyzing and extracting data from log files, and so on. Using F# in a scripting context is
just one more way you can apply the language to help you be more productive.
241
let daysInMonth month =
match month with
| "Feb"
-> 28
| "Apr" | "Jun" | "Sep" | "Nov"
-> 30
| _ -> 31
> daysOfTheYear;;
val it : seq<string * int> =
seq [("Jan", 1); ("Jan", 2); ("Jan", 3); ("Jan", 4); ...]
> Seq.length daysOfTheYear;;
val it : int = 365
It is clear that the code you can put inside of a sequence expression—and by extension
computation expression—is sufficient for performing powerful computations. In fact,
the only limitations to the F# code you can put inside of a computation expression are:
• No new types may be declared within a computation expression.
• Mutable values cannot be used inside computation expressions; you must use
ref cells instead.
I’ve claimed that computation expressions can dramatically simplify code. To demon-
strate this, let me provide an example. Example 10-2 tries to do some basic math, except
rather than leaving division up to chance—and potentially throwing a DivideByZeroEx
ception—each division is wrapped into a custom type that will indicate success or
failure.
The computation preformed is a simple electrical calculation: the total resistance of
three resistors in parallel, which requires four division operations. Because we want to
check the result of division against Success or DivByZero, the code ends up looking
excessively complex because after each division, we much check whether we want to
continue the computation or stop with a DivByZero.
Example 10-2. Setting the state for custom workflows
type Result = Success of float | DivByZero
let divide x y =
match y with
| 0.0 -> DivByZero
| _ -> Success(x / y)
While this code works as expected, it is clear that programming in this style is tedious
and error-prone. Ideally, you could define a new function called let_with_check to do
the result checking for you. This function could check the result of a call to divide, and
if it was Success, then the next line of code would execute; otherwise, the function
would return DivByZero:
let totalResistance r1 r2 r3 =
let_with_check x = divide 1.0 r1
let_with_check y = divide 1.0 r2
let_with_check z = divide 1.0 r3
return_with_check 1.0 (x + y + z)
let totalResistance r1 r2 r3 =
let_with_check
(divide 1.0 r1)
(fun x ->
let_with_check
(divide 1.0 r2)
(fun y ->
let_with_check
(divide 1.0 r3)
(fun z -> divide 1.0 (x + y + z))
)
)
With the code indented and the additional lambda expressions, the code looks worse
than it did originally. However, let’s try to rewrite our totalResistance function again,
but this time putting the lambdas on the same line.
If we write it the following way, the code almost looks like we originally wanted, giving
the appearance that the function will abruptly stop as soon as the first DivByZero is
encountered:
let totalResistance r1 r2 r3 =
let_with_check (divide 1.0 r1) (fun x ->
let_with_check (divide 1.0 r2) (fun y ->
let_with_check (divide 1.0 r3) (fun z ->
divide 1.0 (x + y + z) ) ) )
let divide x y =
match y with
| 0.0 -> DivByZero
| _ -> Success(x / y)
let totalResistance r1 r2 r3 =
defined {
let! x = divide 1.0 r1
let! y = divide 1.0 r2
let! z = divide 1.0 r3
return divide 1.0 (x + y + z)
}
You can test this in FSI and verify that the computation expression version gets the
same results, and in one-third the lines of code!
> totalResistance 0.01 0.75 0.33;;
val it : Result = Success 0.009581881533
> totalResistance 0.00 0.55 0.75;;
val it : Result = DivByZero
All computation expressions do is break the expression into multiple function calls to
the computation expression builder. In the previous example, it was of type Defined
Builder. To use the computation expression builder, all you need to do is put an in-
stance of it out in front of two curly braces { } surrounding the code you want to
execute. In this example, it was an instance of DefinedBuilder via value defined.
Every let! is replaced with a call to the builder’s Bind method, where the evaluation of
the righthand side of the equals sign is the first parameter, and the second parameter
is the function representing the rest of the computation to be performed. This is how
the computation stops after the first DivByZero is reached. In the computation expres-
sion builder’s implementation of Bind, the rest of the computation isn’t executed if the
result is DivByZero. If the result is Success, then the rest of the computation is performed.
Example 10-5 shows the expansion of this in a desugared form of the defined workflow,
which is strikingly similar to the code you saw in Example 10-3.
Example 10-5. Desugared form of a computation expression
// De-sugared form
let totalResistance r1 r2 r3 =
defined.Bind(
(divide 1.0 r1),
Inside of a custom workflow builder, you can write normal F# code, but in addition
you have access to new keywords and abilities such as let!, return, and yield. By using
these new primitives in the body of the computation expression, you can extend how
F# processes the code. (For example, adding custom semantics for when the yield
keyword is encountered.)
By providing a Bind function, you provided a way to determine how let! was computed.
Similarly, providing a Return function will enable you to determine how the return
keyword works. But a workflow builder can provide more methods for determining
how F# code gets executed. A full list is shown in Table 10-1.
If your computation expression builder contains an implementation for the given func-
tion, then you can use the associated construct inside the computation expression. In
the previous example, if you didn’t include the Return method, you would get an error
because you used the return keyword on the last line.
return divide 1.0 (x + y + z)
--------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stdin(259,9): error FS0039: The field, constructor or member 'Return' is not defined.
Asynchronous Workflows
Think about the most tedious aspects of parallel and asynchronous programming—
dealing with threads. If you want your program to have two things going on at once,
you need to spawn new threads, give them appropriate work, and then marshal their
results back. It is just as painful to exploit IO-level parallelism. The .NET Framework
provides asynchronous IO operations via FileStream.BeginRead and FileStream.Begin
Write. However, using these constructs requires a messy combination of IAsyncRe
sult interfaces, AsyncCallback delegates, and a lot of tedious code.
Even if you didn’t mind handling all the thread hopping and the managing of incom-
plete tasks yourself, it is still a pain to deal with exceptions. In addition, there is no
good way to cancel the operation once it starts.
If only there were a way to abstract the code and wire in the thread hopping.... Fortu-
nately, this library does exist and it is built into F#. F# asynchronous workflows are a
type of computation expression builder specifically designed for asynchronous tasks.
Example 10-6 shows how to use an asynchronous workflow for reading and writing
file data asynchronously. Note the use of let! to perform asynchronous operations; all
the thread hopping will be performed in the implementation of the async builder’s
Bind method. The details for how the async computation expression builder and
Async.Start work will be covered in more detail in Chapter 11, which is devoted entirely
to the subject of parallel and asynchronous programming.
Example 10-6. F# asynchronous workflows
open System.IO
printfn
"Opened [%s], read [%d] bytes"
(Path.GetFileName(filePath))
data.Length
If you want to get fancy, you could call computation expressions a form
of monoids in the category of endo-functors, but I don’t recommend it.
// Due to result being constrained to type float, you can only use
// let! against float values. (Otherwise will get a compiler error.)
member this.Bind(result : float, rest : float -> float) =
let result' = round result
rest result'
By using a computation expression, you can eliminate the need for passing a state object
throughout the computation. The intuition here is that computation expressions are
used to do “extra work” behind the scenes of normal F# code, so perhaps we can have
that extra work persist state information:
let reviewBook() =
state {
do! OpenPage "www.fsharp.net"
do! ClickLink "F# Books"
let! table = GetHtmlTableWithHeader "Programming F#"
do! ClickButton table "Five Star Rating"
}
When encoded into the StatefulFunc type, the steps would look something like the
following:
// do! OpenWebpage "www.Bing.com"
let step1 =
StatefulFunc(fun initialState ->
let result, updatedState = OpenWebpage "www.bing.com" initialState
result, updatedState)
// do! EnterText
let step2 =
StatefulFunc(fun initialState ->
let result, updatedState = EnterText "Jellyfish" initialState
result, updatedState)
The result of a state workflow then is a single StatefulFunc object that has associated
with it a function that will perform the full computation, passing along a state object,
built out of the calls to Bind within the workflow.
Once the StatefulFunc has been built up, the full computation would end up looking
similar to the following:
StatefulFunc(fun initialState ->
result3, updatedState3
)
)
// Type to represent a stateful function. A function which takes an input state and
// returns a result combined with an updated state.
type StatefulFunc<'state, 'result> = StatefulFunc of ('state -> 'result * 'state)
type StateBuilder() =
member this.Bind(
result : StatefulFunc<'state, 'a>,
restOfComputation : 'a -> StatefulFunc<'state, 'b>
) =
member this.Combine(
partOne : StatefulFunc<'state, unit>,
partTwo : StatefulFunc<'state, 'a>
) =
member this.Delay(
restOfComputation : unit -> StatefulFunc<'state, 'a>
) =
for e in elements do
let (), updatedState = Run (forBody e) (!state)
state := updatedState
member this.TryFinally(
tryBlock : StatefulFunc<'state, 'a>,
finallyBlock : unit -> unit
) =
member this.TryWith(
tryBlock : StatefulFunc<'state, 'a>,
exnHandler : exn -> StatefulFunc<'state, 'a>
) =
member this.While(
predicate : unit -> bool,
body : StatefulFunc<'state, unit>
) =
member this.Zero() =
StatefulFunc(fun initialState -> (), initialState)
With the full workflow defined, Example 10-10 uses the state workflow to script a
four-function calculator. (You can use the same state workflow for creating a web
crawler, but the code to do that properly would easily dwarf the text in this chapter.)
The state is represented by the current total and the calculator’s operational history.
The functions Add, Subtract, Multiply, and Divide will take an input state, modify it,
and return a new StatefulFunc object.
Example 10-10. Using the state workflow to create a calculator
let Add x =
state {
let! currentTotal, history = GetState
do! SetState (currentTotal + x, (sprintf "Added %d" x) :: history)
}
let Subtract x =
state {
let! currentTotal, history = GetState
do! SetState (currentTotal - x, (sprintf "Subtracted %d" x) :: history)
}
let Multiply x =
state {
let Divide x =
state {
let! currentTotal, history = GetState
do! SetState (currentTotal / x, (sprintf "Divided by %d" x) :: history)
}
Using functions like Add and Subtract within the state workflow will give the illusion
that you are not passing the state around, while in actuality a large StatefulFunc object
is built, which can be executed by passing it to the Run function:
> // Define the StatefulFunc we will use, no need to thread
// the state parameter through each function.
let calculatorActions =
state {
do! Add 2
do! Multiply 10
do! Divide 5
do! Subtract 8
return "Finished"
}
F# workflows are a very advanced concept that even expert developers can have a hard
time grasping. But they can be used to simplify code by giving you the ability to execute
code in between computation steps. If you find yourself writing a lot of redundant,
boilerplate code, consider looking into whether you can wrap that functionality inside
of a computation expression.
The reason for mastering asynchronous and parallel programming is that without it,
you simply cannot take advantage of your hardware’s potential. This is especially true
in intensive CPU-bound domains, like technical computing. Previously there was a
good reason for not writing programs in an asynchronous or parallel fashion: added
complexity and the risk of introducing bugs. However, with the great libraries available
in Visual Studio 2010, combined with a side effect–free style of functional program-
ming, this concern has been elegantly mitigated.
This chapter will focus on how to speed up computation in F# using asynchronous
and parallel programming. By the end of this chapter, you will be able to execute code
in different contexts (threads), use the F# asynchronous workflows library for mas-
tering asynchronous programming, and also take advantage of the Parallel Extensions
to .NET.
Before we begin, let’s first examine what exactly asynchronous and parallel program-
ming mean and why they matter.
Asynchronous programming
Asynchronous programming describes programs and operations that once started
are executed in the background and terminate at some “later time.” For example,
in most email clients, new emails are retrieved asynchronously in the background
so the user doesn’t have to force checking for new mail.
Parallel programming
Parallel programming is dividing up work among processing resources in order
to speed up execution. For example, converting a song into an MP3 can be
parallelized—dividing the song into pieces and converting each segment in parallel.
257
Asynchronous and parallel programming adds a great deal of complexity to applica-
tions; however, using these techniques provides some clear benefits:
Take advantage of multiple processors and cores
Modern computers come with multiple processors, each equipped with multiple
cores. These logical units allow multiple different computations to happen at the
same time, so rather than executing your program sequentially, you can take ad-
vantage of these extra processors and cores by splitting up your programs. For
example, a computer with two quad-core processors can execute eight operations
at once.
Asynchronous IO
Historically IO operations such as reading and writing to disk have been the bot-
tleneck in high-performance applications. Modern hardware has the ability to do
asynchronous IO, and can greatly improve overall performance.
Application responsiveness
Perhaps the most compelling reason to introduce asynchronous programming into
your applications is to increase application responsiveness. For any long-running
computation, if you run it asynchronously, your application is still free to process
user input, which enables users to continue working or perhaps even cancel oper-
ations in progress.
Spawning Threads
To spin up a new thread and have it execute in the background, simply create a new
instance of System.Threading.Thread and pass in a ThreadStart delegate to its con-
structor. Then, call the Thread object’s Start method.
Example 11-1 spawns a couple of threads that each count to five.
let spawnThread() =
let thread = new Thread(threadBody)
thread.Start()
When executed, Example 11-1 will output the following (or something like it; there is
no way to tell how the OS will schedule threads and/or assign thread IDs):
[Thread 5] 1...
[Thread 6] 1...
[Thread 5] 2...
[Thread 6] 2...
[Thread 5] 3...
[Thread 6] 3...
[Thread 5] 4...
Spawning multiple threads that count to five is a good start, but imagine that the
threadBody function now calculated insurance premiums or did gene sequence align-
ment. Getting multiple threads executing code concurrently is actually just as simple
as spinning up a new thread and calling Start.
Other than Start, there are two methods on a Thread object that you should be aware
of: Sleep and Abort.
Thread.Sleep
Thread.Sleep is a static method that puts the currently executing thread to sleep for a
given number of milliseconds. In Example 11-1, both threads had a 100 millisecond
sleep—or one-tenth of a second—in between when they printed to the console.
Typically Thread.Sleep is used to put in a static delay or simply yield execution of the
current thread so the OS can devote processor time to a different thread.
Thread.Abort
The Thread.Abort method kills the thread, attempting to stop its execution by raising
a ThreadAbortException execution somewhere in the executing thread. (With a few
exceptions, wherever the thread’s instruction pointer happens to be at the time the
thread is aborted.) With the .NET memory model, the abrupt termination of the thread
likely won’t leak any memory, but you should always avoid using Thread.Abort.
Later in this chapter, we will look at more advanced libraries built on top of
System.Thread to enable friendly notions such as task cancellation to avoid the need for
Thread.Abort.
Sharing Data
With multiple threads executing, you will want to share data between them for coor-
dination and storing intermediate results. This, however, leads to a couple of problems:
race conditions and deadlocks.
Race conditions
A race condition is when you have two threads trying to read or write the same value
at the same time. If two threads are writing a value, the last one wins, and the first write
operation is lost forever. Similarly, if one thread reads a value and the other immediately
overwrites that value, the first thread’s data is out of date and potentially invalid.
Example 11-2 simply loops through an array and adds up its elements on two threads.
Both threads are constantly updating the total reference cell, which leads to a data race.
Example 11-2. Race conditions
open System.Threading
ThreadPool.QueueUserWorkItem(
fun _ -> for i = 0 to arr.Length / 2 - 1 do
total := arr.[i] + !total
thread1Finished := true
) |> ignore
Thread.Sleep(0)
!total
The results of the sumArray function in an FSI session are quite troubling. Not only does
this function fail to work, it returns a different value each time! An array with a million
elements with value 1 should sum to exactly one million; instead, sumArray is returning
about half that:
> // Array of 1,000,000 ones
let millionOnes = Array.create 1000000 1;;
The problem arises from the fact that updating total consists of three separate opera-
tions: reading the value, incrementing it, and then writing it back. Ideally updating
total would occur as a single, atomic operation.
A solution for data races is to lock the shared data, that is, to prevent other threads from
accessing it while you update its value. For example, there would be no problem with
the implementation of sumArray if only one thread could read and update the value of
total at a time.
In F#, you can lock a value by using the lock function, which has the following signa-
ture. It takes a reference value, which is the value being locked on, and will lock that
value until the provided function is finished executing.
val lock : ('a -> (unit -> 'b) -> 'b) when 'a : not struct
Thread.Sleep(0)
!total
Now the function works exactly as we want; the sum of an array with a million ones is
exactly one million. However, with the locking there is no longer any performance gain
from summing the array elements in parallel because the counting is actually done one
element at a time as the lock is released and immediately acquired, over and over again.
Later in this chapter, you will learn how to use concurrent data structures, which are
built for high performance, while still using locking internally to maintain data
integrity:
> lockedSumArray millionOnes;;
val it : int = 1000000
> lockedSumArray millionOnes;;
val it : int = 1000000
> lockedSumArray millionOnes;;
val it : int = 1000000
> lockedSumArray millionOnes;;
val it : int = 1000000
The previous code snippet looks simple enough, but if there is ever a fund transfer
between the same two accounts at the same time, the code will deadlock.
In the following example the first call to transferFunds will lock John Smith’s account,
while on a separate thread, the second call to transferFunds will lock Jane Doe’s ac-
count. Then, when the transferFunds routine goes to lock the other account, both
threads will stall indefinitely:
let john = { AccountID = 1; OwnerName = "John Smith"; Balance = 1000 }
let jane = { AccountID = 2; OwnerName = "Jane Doe"; Balance = 2000 }
Deadlocking doesn’t mean that sharing data between threads is a no-win situation. You
just need to be judicious in your use of locking and be aware of what dependencies
threads have. There are many ways to write code with locks that don’t have deadlocks.
For example, in the previous example, if the function transferFunds always locked the
account with the lower AccountID first, then there would be no deadlock.
printfn
"Finished processing file [%s]"
(Path.GetFileName(writeStream.Name))
)
let _ =
resultFile.BeginWrite(
updatedBytes,
0, updatedBytes.Length,
asyncWriteCallback,
resultFile)
()
)
// Begin the async read, whose callback will begin the async write
let fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read,
FileShare.Read, 2048,
FileOptions.Asynchronous)
printfn "Finished!"
Didn’t we run into a similar problem back in Chapter 10? We identified classes of
problems where we wanted some background operation to occur behind the scenes
but still to be allowed to write normal F# code. Perhaps that technique could be useful
here as well....
That’s right, F# computation expressions to the rescue!
Example 11-6 repeats the same task of asynchronously reading and writing a file, except
rather than using the APM, all of the asynchronous operations are handled by the F#
asynchronous workflows library. The result is that the code is dramatically simpler to
write and understand.
The magic of how async workflows works will be discussed shortly. But for now, just
notice that the code is wrapped up in the async computation expression builder and
the asynchronous operations begin with let! and do!. Also, note that the result of the
async builder is passed to the mysterious Async.Start method.
printfn
"Opened [%s], read [%d] bytes"
(Path.GetFileName(filePath))
data.Length
Please refer back to Chapter 10 for more information about how let!
and do! translate to function calls on a computation expression builder.
open System.IO
open System.Net
open System.Microsoft.FSharp.Control.WebExtensions
return! reader.AsyncReadToEnd()
}
let html =
getHtml "http://en.wikipedia.org/wiki/F_Sharp_programming_language"
|> Async.RunSynchronously
Async.RunSynchronously isn’t especially useful on its own, because it blocks the starting
thread waiting for the operation to complete—which is exactly the opposite of asyn-
chronous execution. Usually this method is called immediately after the Async.Paral
lel method, which takes a seq< Async<'T> >, and starts all of the sequence’s compu-
tations in parallel. The results are combined into an instance of Async<'T[]>.
The following code snippet builds off the getHtml method, and retrieves a series of web
pages in parallel:s
let webPages : string[] =
[ "http://www.google.com"; "http://www.bing.com"; "http://www.yahoo.com" ]
|> List.map getHtml
|> Async.Parallel
|> Async.RunSynchronously
Exceptions
Earlier, when we were chopping up asynchronous tasks with BeginOperation and
EndOperation, what if you wanted to properly handle an exception? Rather than having
multiple exception handlers—each within an APM callback—when using asynchro-
nous workflows, you can put the exception handler directly in the async block.
Even if the body of the try has several asynchronous operations (let!s and do!s) inside
of it, the exception will be caught exactly the way you expect.
But what happens if an exception from an async workflow isn’t caught? Does the ex-
ception bubble up and eventually bring down the whole process? Unfortunately, yes.
In asynchronous workflows, unhandled exceptions bring down the whole process be-
cause by default they are not caught. To catch unhandled exceptions from async work-
flows, you can use the Async.StartWithContinuations method, discussed later, or use
the Async.Catch combinator.
The Async.Catch method takes an async workflow and wraps it into a success or ex-
ception result. It has the following signature:
val Catch : Async<'T> -> Async< Choice<'T,exn> >
The Choice type means that its value has one of several values; this is
what multi-case active patterns compile to behind the scenes.
The following example uses the Async.Catch combinator to enable catching exceptions
and then runs the async workflow. If the task completes successfully the result is printed
to the screen; otherwise, the exception’s message is printed:
asyncTaskX
|> Async.Catch
|> Async.RunSynchronously
|> function
| Choice1Of2 result -> printfn "Async operation completed: %A" result
| Choice2Of2 (ex : exn) -> printfn "Exception thrown: %s" ex.Message
Cancellation
When you execute code asynchronously, it is important to have a cancellation mech-
anism just in case the user notices things going awry or gets impatient. Earlier, when
we were working with raw threads, the only way to cancel an operation was to literally
kill the thread using Thread.Abort, and in doing so introduce numerous problems.
Asynchronous workflows can be cancelled, but unlike Thread.Abort, cancelling an
async workflow is simply a request. The task does not immediately terminate, but rather
the next time a let!, do!, etc. is executed, the rest of the computation will not be run
and a cancellation handler will be executed instead.
let cancelableTask =
async {
printfn "Waiting 10 seconds..."
for i = 1 to 10 do
printfn "%d..." i
do! Async.Sleep(1000)
printfn "Finished!"
}
Async.TryCancelled(cancelableTask, cancelHandler)
|> Async.Start
// ...
Async.CancelDefaultToken()
Async.Start(computation, cancellationSource.Token)
// ...
cancellationSource.Cancel()
The Async module may seem a bit complicated with so many ways to start and cus-
tomize async workflow tasks. Most likely you will only use one method for hooking
into unhandled exceptions and cancellations, and that is the Async.StartWithContinu
ations method. Essentially, it is Async.Catch, Async.TryCancelled, and Async.Start all
rolled into one. It takes four parameters:
• The async workflow to execute
• A function to execute when the operation completes
• A function to call in case an exception is thrown
• A function to call in case the operation gets cancelled
The following snippet shows the Async.StartWithContinuations method in action:
Async.StartWithContinuations(
superAwesomeAsyncTask,
(fun result -> printfn "Task completed with result %A" result),
(fun exn -> printfn "Task threw an exception with Message: %s" exn.Message),
(fun oce -> printfn "Task was cancelled. Message: %s" oce.Message)
)
Async Operations
F# asynchronous workflows show the ease with which you can write asynchronous
code, but how do you get an instance of Async<'T>? You can build your own async
objects, as you will see shortly, but most likely you will be using the extension methods
defined in the F# library.
The System.IO.Stream type is extended with several methods for reading and writing
data synchronously. You’ve already seen examples of AsyncRead and AsyncWrite.
The System.Net.WebRequest class is extended with AsyncGetResponse for retrieving web
pages asynchronously. However, to use this extension method, you must first open the
Microsoft.FSharp.Control.WebExtensions namespace:
open System.IO
open System.Net
open Microsoft.FSharp.Control.WebExtensions
return reader.ReadToEnd()
}
The Async.Sleep method does a sleep just like Thread.Sleep except that it does not
block the async workflow thread. Instead, the thread that begins the sleep will be reused
by some other task, and when the sleep completes, another thread will be woken up
and finish the execution.
The following code snippet suspends and resumes an async workflow each second to
update a progress bar attached to a WinForm:
#r "System.Windows.Forms.dll"
#r "System.Drawing.dll"
open System.Threading
open System.Windows.Forms
form.Show()
async {
for i = 0 to 15 do
do! Async.Sleep(1000)
pb.Value <- i
} |> Async.Start
Limitations
F# asynchronous workflows are great for enabling IO parallelism; however, because
the library simply is a lightweight wrapper on top of the thread pool, using it won’t
guarantee that you will maximize performance. When executing code in parallel, there
are numerous factors to take into account, for example:
• The number of processor cores
• Processor cache coherency
• The existing CPU workload
Parallel Programming
Parallel programming is about dividing up a computation into n parts to obtain—
hopefully—an n-times speedup (though in practice this is rarely achievable).
The simplest way to do parallel programming on .NET is through the Parallel Exten-
sions to the .NET Framework (PFX); which is Microsoft-speak for new primitives to
make it easier to write parallel programs. By using, the PFX you should have no need
to manually control threads or the thread pool.
Parallel.For
The first thing you can do to parallelize your applications is to just swap out your for
loops with the Parallel.For and Parallel.ForEach methods in the System.Threading
namespace. As their names imply, these functions can replace for loops and execute
loop iterations in parallel.
Example 11-10 uses Parallel.For to multiply two matrices. The code to produce each
row of the resulting matrix is in the rowTask function, so rows of the resulting matrix
will be computed in parallel.
Example 11-10. Using Parallel.For
open System
open System.Threading.Tasks
Blindly swapping out for loops with Parallel.For will introduce bugs if the code exe-
cuting inside the for loop depends on the previous iteration; for instance, if you are
using a for loop to perform a fold-type operation. In the previous example, the order
in which rows were computed didn’t matter, so the code could safely be parallelized.
You can start to see the benefits of containing mutation and not sharing global state.
In F#, you have to “‘opt in” for mutating data, so your code by default is thread-safe.
let secretData =
Directory.GetFiles(@"D:\TopSecret\", "*.classified")
|> Array.Parallel.map (fun filePath -> File.ReadAllText(filePath))
|> Array.Parallel.choose (fun contents ->
if contents.Contains(keyword)
then Some(contents)
else None)
secretData
You can improve upon this by using the thread pool as you saw earlier; however, this
makes a mess of the code and prevents you from cancelling the operation or handling
exceptions properly:
let mutable completed = false
let mutable result1 = null
ThreadPool.QueueUserWorkItem(fun _ ->
result1 <- longTask1()
completed <- true
) |> ignore
Here is how you can solve this problem using the PFX:
open System.Threading.Tasks
Primitives
New tasks can be created using one of the overloads of the Task.Factory.StartNew
method. Once created, the task will be scheduled to execute in parallel. However, the
machinery of the PFX library will determine just how many tasks to spawn at a time,
dependent upon factors such as current available CPU resources, number of processors,
and so on.
The point is that you don’t need to manage this explicitly; the PFX will do it for you.
So whereas spawning numerous operations using F# asynchronous workflows would
overwhelm the thread pool and CPU, the PFX properly modulates how many tasks are
executing at once to get the best performance possible.
To retrieve a task’s return value, all you need to do is access its Result property.
let x = task.Result
The WaitAny primitive may seem strange—why spawn several tasks and wait for only
one to complete? There are two good reasons to use this primitive.
First, if you have a family of algorithms you can use to solve a problem, each with
varying degrees of efficiency based on input, then it can make sense to start them all in
parallel and just get the first one that completes. (You might want to cancel all the other
tasks once you know their results won’t be needed, though.)
Second, it can be helpful for creating more responsive applications. For example, if
there were a UI attached to Example 11-13, the user might want to see the results of
the resized images as they came in—not just after all of the operations have completed.
Cancellation
Another benefit of using the Task class, unlike manually controlling threads you spawn,
is to take advantage of cancellation. Just like F# asynchronous workflows, cancellation
is a request and doesn’t immediately terminate the task. So if you want tasks to behave
well when cancelled, you must explicitly check for it.
As mentioned earlier, the Parallel Extensions to .NET use the same cancellation
mechanism as F# asynchronous workflows. Specifically, each task can be associated
with a CancellationToken object, which keeps track of its cancellation status. A
let longRunningTask() =
let mutable i = 1
let mutable loop = true
printfn "Complete!"
let startLongRunningTask() =
Task.Factory.StartNew(longRunningTask, longTaskCTS.Token)
let t = startLongRunningTask()
// ...
longTaskCTS.Cancel()
Exceptions
Handling exceptions in conjunction with the PFX is a bit more involved. If a task throws
an exception, it returns an exception of type System.AggregateException. It may seem
strange that tasks don’t surface the exception you originally threw, but remember that
tasks are executing in an entirely different context from where the exception will be
caught. What if the task had spawned child tasks—and they too threw exceptions?
The AggregateException type combines all exceptions originating from the target task,
which you can access later via the InnerExceptions collection.
type CaveNode =
| ManEatingYeti of string
| BottomlessPit
| Treasure
| DeadEnd
| Fork of CaveNode * CaveNode
// Leaf nodes
| DeadEnd -> 0
| Treasure -> 1
// Branches. Send half of your expedition left, the other half goes right...
// ... hopefully not to only be eaten by a Yeti
| Fork(left, right) ->
let goLeft = Task.Factory.StartNew<int>(fun () -> findAllTreasure left)
let goRight = Task.Factory.StartNew<int>(fun () -> findAllTreasure right)
goLeft.Result + goRight.Result
The following code snippet calls the findAllTreasure function and catches the Aggre
gateException. Notice how the decomposeAggregEx function is used to extract all the
non-AggregateExceptions:
let colossalCave =
Fork(
Fork(
DeadEnd,
Fork(Treasure, BottomlessPit)
),
Fork(
ManEatingYeti("Umaro"),
Fork(
ManEatingYeti("Choko"),
Treasure
)
)
)
try
let treasureFindingTask =
with
| :? AggregateException as ae ->
printfn "AggregateException:"
Concurrent queue
A common pattern in parallel programming is having a list of work to be done and
dividing it among various threads. However, there is an inherent contention for the list
of things to do. Fortunately, the ConcurrentQueue data type solves this problem.
Concurrent dictionary
The ConcurrentDictionary type behaves like a normal dictionary. Example 11-15 uses
a ConcurrentDictionary to associate words with the number of times they are encoun-
tered in a set of files.
In the example, the AddOrUpdate method is used. The ConcurrentDictionary has differ-
ent methods for adding new items to the dictionary, and AddOrUpdate takes a key, a
value to add if the key doesn’t exist, and a function to update the value if the key is
already present in the dictionary. Using this method makes it easier to insert items into
the dictionary rather than checking the result of TryAdd.
Example 11-15. Using the ConcurrentDictionary
open System
open System.IO
open System.Collections.Concurrent
// Add the word to our lookup table. Inserts value '1', or if the key
// is already present updates it to be 'count + 1'.
while continueWorking do
Concurrent bag
The ConcurrentBag class behaves much like the HashSet<_> type we covered in Chap-
ter 4, except that it allows duplicate items to be added. This isn’t a world-shattering
loss because once the bag has been filled you can filter out duplicates on your own.
A ConcurrentBag is ideal for aggregating results from several tasks you have spawned.
Example 11-16 spawns several tasks to compute a list of prime numbers up to a max-
imum value in parallel using a ConcurrentBag. If a HashSet<_> type were used instead,
some of the values added to the collection would be “dropped” because of the race
conditions when trying to update the HashSet’s internal data structures. (Or even worse,
the data itself could get corrupted.)
The function computePrimes returns the ConcurrentBag cast as a seq<int> and then
passed to a HashSet<_>. (ConcurrentBag, like most collection types, implements the
IEnumerable<_> interface and therefore can be cast as a seq<_>.)
Now you can write asynchronous and parallel programs in F# by taking advantage of
the great libraries available. You can also see why an emphasis on functional program-
ming is beneficial when doing parallel programming. Composing pure, immutable
functions makes it easy to decompose tasks to execute independently, without worry-
ing about introducing bugs.
We have covered almost all of the raw syntax of F#, and the programming paradigms
it enables. In this chapter, you won’t learn any new capabilities of the F# language per
se; instead you will learn how to harness the .NET Reflection APIs.
Reflection allows you to access the runtime metadata information about executing
code. Metadata can be raw type information, such as the methods a class has, or it can
come in the form of attributes, which are programmer-supplied code annotations.
.NET reflection is most commonly used for metaprogramming, which is writing pro-
grams that reason about themselves, or modify themselves, such as those that can load
plug-ins to add new functionality at runtime; in other words, they allow other devel-
opers to extend your application without their needing to have access to the source
code.
But before you can do full-on metaprogramming, you must first understand how to
annotate code to provide metadata.
Attributes
Attributes are a form of metadata you can attach to assemblies, methods, parameters,
and so on. These annotations can then be inspected at runtime using reflection, which
we will get to later in this chapter. The F# compiler will recognize attributes as well;
in fact, you have been using attributes for a while now to provide hints for how to
compile your code.
Recall from Chapter 5 that in order to mark a class as abstract, you need to provide the
[<Abstract>] attribute to the type declaration. When the F# compiler sees that
attribute, it will generate the code differently than if the attribute had not been present.
The same is true for the [<Struct>], [<ReferenceEquality>], and other attributes:
[<AbstractClass>]
type Animal =
abstract Speak : unit -> unit
abstract NumberOfLegs : int with get
287
In .NET programming, there are many common attributes that the F# compiler will
recognize as well. For example, the System.ObsoleteAttribute is used to mark depre-
cated methods and classes. When using code entities marked with [<Obsolete>], the
F# compiler will generate an error or warning based on usage.
Example 12-1 shows some F# code that has been refactored since originally written.
An [<Obsolete>] attribute has been added to help guide consumers of the class to up-
date their code.
As you saw earlier, to place an attribute, simply insert the attribute’s name surrounded
by [< >] above the class or method you want the attribute to apply to. When applying
an attribute, if the attribute’s name is suffixed with Attribute, you can omit the word
Attribute. For example, to apply an instance of System.ObsoleteAttribute, you just
need to write [<Obsolete>] if the System namespace has been opened.
Example 12-1. The Obsolete attribute
open System
type SafetyLevel =
| RecreationalUse = 1
| HaveMcCoyOnDuty = 3
| SetPhasersToKill = 4
Figure 12-1 shows the Visual Studio 2010 editor when trying to call a method or prop-
erty marked with [<Obsolete>].
Attribute targets
Most of the time putting the attribute directly above the type or method you want to
annotate will be sufficient. However, there will be times when the attribute’s target can
be ambiguous. To have the attribute apply to a specific construct, you can use an
attribute target, which is a keyword indicating what the attribute applies to.
For example, assembly-level attributes are global in the sense that they do not belong
to a particular class or method, but rather apply to the whole assembly. So an attribute
target is required to make clear that the assembly-level attribute doesn’t apply to the
next type or value declared.
The most common assembly-level attributes are those for setting the assembly’s version
and copyright information. Note in the example that character trigraphs \169 and
\174 are used to encode the © and " characters, respectively:
open System.Reflection
[<assembly:AssemblyDescription("RemoteCombobulator.exe")>]
[<assembly:AssemblyCompany("Initech Corporation")>]
[<assembly:AssemblyCopyright("\169 Initech Corporation. All rights reserved.")>]
[<assembly:AssemblyProduct("Initech \174 RemoteCombobulator")>]
do()
Attributes | 289
Example 12-2 shows the same assembly-level attributes from the previous code snippet,
but in a slightly cleaned-up form.
Example 12-2. Applying multiple attributes
open System.Reflection
[<
assembly:AssemblyDescription("RemoteCombobulator.exe");
assembly:AssemblyCompany("Initech Corporation");
assembly:AssemblyCopyright("\169 Initech Corporation. All rights reserved.");
assembly:AssemblyProduct("Initech \174 RemoteCombobulator")
>]
do()
type Widget =
| RedWidget
| GreenWidget
| BlueWidget
Type Reflection
So you have learned how to attribute code, but what if you wanted to inspect those
attributes? You can access attributes on code through type reflection.
Recall that everything in .NET inherits from System.Object, and therefore every value
in .NET has a GetType method, which returns an instance of System.Type. The Type class
describes everything there is to know about a type—including its attributes.
Accessing Types
The following FSI session defines a custom type named Sprocket and then uses two
different methods to access its type information. The first method used in the sample
is the generic typeof<_> type function, which returns the type of the supplied type
parameter. The second method used is to call the GetType method found on an instance
of Sprocket because it inherits from obj:
> // Simple sprocket type
type Sprocket() =
member this.Gears = 16
member this.SerialNumber = "WJM-0520";;
type Sprocket =
class
new : unit -> Sprocket
member Gears : int
member SerialNumber : string
end
Inspecting methods
Once you have an instance of Type, you can inspect the type’s methods and properties
by calling GetMethods and GetProperties, and so on.
Example 12-4 defines a function called describeType, which will print all methods,
properties, and fields for a type to the console.
The BindingFlags enum is passed in to filter the results. For example, Binding
Flags.DeclaredOnly is used so that the call to GetMethods doesn’t return inherited meth-
ods, such as GetHashCode and ToString.
Example 12-4. Inspecting and analyzing types
open System
open System.Reflection
let bindingFlags =
BindingFlags.Public ||| BindingFlags.NonPublic |||
BindingFlags.Instance ||| BindingFlags.Static |||
BindingFlags.DeclaredOnly
let methods =
ty.GetMethods(bindingFlags)
|> Array.fold (fun desc meth -> desc + sprintf " %s" meth.Name) ""
let props =
ty.GetProperties(bindingFlags)
|> Array.fold (fun desc prop -> desc + sprintf " %s" prop.Name) ""
let fields =
ty.GetFields(bindingFlags)
|> Array.fold (fun desc field -> desc + sprintf " %s" field.Name) ""
...snip...
Properties:
FirstChar Chars Length ArrayLength Capacity
Fields:
m_arrayLength m_stringLength m_firstChar Empty WhitespaceChars TrimHead
TrimTail TrimBoth
charPtrAlignConst alignConst
Now let’s use describeType on a custom class. First, define a type called Cog. Note the
private accessibility modifiers on the class’s field and property:
/// Unit of measure for milliliter
[<Measure>]
type ml
member this.Rotate() =
// Rotating loses a bit of oil on each turn...
this.m_oilLevel <- this.m_oilLevel - 0.01<ml>
Now, in an FSI session, call describeType on the type of Cog. Notice how all type in-
formation is printed out, including members marked with the private accessibility
modifiers.
Properties:
SerialNumber Gears
Fields:
m_oilLevel
val it : unit = ()
Inspecting attributes
Inspecting a type’s attributes is done in much the same way as inspecting its methods
and properties. The only difference, however, is that attributes can be put in many more
places.
Example 12-5 shows inspecting a type for the ClassDescription and MethodDescrip
tion attributes from earlier in this chapter. Note how the full list of custom attributes
on a type needs to be pared down to just the ClassDescription or MethodDescription
attributes.
Example 12-5. Inspecting type attributes
open System
methodDescriptions
|> Seq.iter(fun (methName, desc) -> printfn
"\t%15s - %s"
methName
(getDescription desc))
When run on the WidgetStack type from Example 12-3, the printDocumentation func-
tion has the following output:
> printDocumentation (typeof<WidgetStack>);;
Info for class: WidgetStack
Class Description:
Represents a stack of Widgets.
Method Descriptions:
Push - Pushes a new Widget onto the stack.
Peek - Access the top of the Widget stack.
Pop - Pops the top Widget off the stack.
ToString - (no description provided)
Equals - (no description provided)
GetHashCode - (no description provided)
GetType - (no description provided)
val it : unit = ()
type PlayingCard =
| Ace of Suit
| King of Suit
| Queen of Suit
| Jack of Suit
| ValueCard of int * Suit
| Joker
However, when it is analyzed using reflection by our inspectType function, you can see
all the work the F# compiler does behind the scenes to map the discriminated union
onto the object-oriented .NET runtime:
> describeType (typeof<PlayingCard>);;
Name: PlayingCard
Methods:
get_Joker get_IsJoker NewValueCard get_IsValueCard NewJack get_IsJack NewQueen
get_IsQueen NewKing get_IsKing NewAce get_IsAce get_Tag __DebugDisplay CompareTo
CompareTo CompareTo GetHashCode GetHashCode Equals Equals Equals
Properties:
Tag Joker IsJoker IsValueCard IsJack IsQueen IsKing IsAce
Fields:
_tag _unique_Joker
Fortunately, the F# library contains a few additional APIs for reflecting over functional
types in the Microsoft.FSharp.Reflection namespace.
Tuples
The underlying type used for tuples is System.Tuple<_>. To decompose a particular
instance of a tuple and get the type of its elements, you can use the FSharpType.GetTu
pleElements method, which will return the types of the tuple’s elements in order as an
array:
> let xenon = ("Xe", 54);;
Discriminated unions
You can reflect over discriminated union data tags by using the FSharpType.GetUnion
Cases method. This will return an array of the UnionCaseInfo type, which will describe
each data tag.
The following snippet reflects over the PlayingCard type and prints each data tag name
to the console:
> // Reflect the PlayingCard discriminated union type
FSharpType.GetUnionCases typeof<PlayingCard>
|> Array.iter (fun unionCase -> printfn "%s" unionCase.Name);;
Ace
King
Queen
Jack
ValueCard
Joker
val it : unit = ()
Records
You can use the FSharpType.GetRecordFields method to get all of the properties on a
record type:
> // Type definitions
[<Measure>]
type far // Degrees fahrenheit
...snip...
Instantiating Types
Dynamically instantiating types can be done using the Activator class. Example 12-6
defines a type PoliteWriter, and creates a new instance dynamically using
Activator.CreateInstance.
The parameters to PoliteWriter’s constructor are passed in as an obj array. The result
of CreateInstance is an obj, which will need to be cast as the type you want.
Example 12-6. Dynamic instantiation
> // Definte a polite text writer
open System.IO
type PoliteWriter =
class
new : stream:IO.TextWriter -> PoliteWriter
member WriteLine : msg:string -> unit
end
Now you have the ability to not only load types but also to create new instances of them
at runtime. This opens up powerful new capabilities for F# applications such as loading
plug-ins.
Dynamic Invocation
If you can go about dynamically loading and instantiating types, why stop there?
In .NET you can do dynamic invocation, which is a fancy term for late binding, which
is in turn a fancy way of saying “calling methods at runtime which you didn’t know
about at compile time.” Using reflection you can get an instance of a method or property
and call it.
Example 12-7 defines a class Book and dynamically gets an instance of the type’s
CurrentPage property. That property can then be updated dynamically without ma-
nipulating an instance of Book directly, but rather calling SetValue on the returned
PropertyInfo object.
override this.ToString() =
match m_currentPage with
| Some(pg) -> sprintf "%s by %s, opened to page %d" title author pg
| None -> sprintf "%s by %s, not currently opened" title author;;
Using late binding isn’t that common because normally if you are given an obj, you
know what types to expect, so you can use a dynamic cast (:?>) and then call the
expected method. Late binding is typically used in dynamic languages, in which meth-
ods and properties can be added to objects at runtime. So you need to look up whether
the given method or property even exists before calling it!
Using the question mark operator, we can clean up our code in Example 12-7. The
following snippet defines both a question mark and question mark setter for dynami-
cally accessing a class and updating a class property:
> // Get a property value. Notice that the return type is generic.
let (?) (thingey : obj) (propName: string) : 'a =
let propInfo = thingey.GetType().GetProperty(propName)
propInfo.GetValue(thingey, null) :?> 'a
In most applications, you will rarely encounter a type for which you know its available
properties but not its actual type. So the question mark operators are of somewhat
limited use. But consider situations where the property being checked contains addi-
tional information; for example, book?CurrentPageTextInSpanish. The question mark
operators add extra flexibility when pushing the limits of what F# code can do.
Using Reflection
So now that you know all about reflection, what do you do with it? In other words,
what is metaprogramming?
Next, we will look at two specific applications of .NET reflection: declarative pro-
gramming and writing plug-in systems.
[<Measure>]
type inches
type Container =
| Envelope
| Box
| Crate
type Dimensions =
{ Length : float<inches>; Width : float<inches>; Height : float<inches> }
[<AbstractClass>]
type ShippingItem() =
abstract Weight : float<lb>
abstract Dimension : Dimensions
// Will it blend?
type Blender() =
inherit ShippingItem()
Example 12-8 seems simple enough, but the code doesn’t scale very well. Imagine you
need to extend it so that you can add special handling instructions. For example, sup-
pose you need to account for the fact that some shipping items are fragile or flammable.
Using the full object-oriented approach, one approach would be to extend the
ShippingItem base class and add new properties:
[<AbstractClass>]
type ShippingItem() =
abstract Weight : float<lb>
abstract Dimension : Dimensions
abstract Fragile : bool
abstract Flammable : bool
type FragileAttribute() =
inherit System.Attribute()
type FlammableAttribute() =
inherit System.Attribute()
type LiveAnimalAttribute() =
inherit System.Attribute()
[<Fragile; Flamable>]
type Fireworks() =
inherit ShippingItem()
type ShippingRequirements =
| NeedInsurance of ShippingItem
| NeedSignature of ShippingItem
| NeedBubbleWrap of ShippingItem
Plug-in Architecture
Another use of reflection is to allow for a plug-in architecture, where other developers
can extend your application by creating new .NET assemblies that conform to a specific
Plug-in interfaces
Example 12-10 defines the code for DeliverySystem.Core.dll, which is a library that
future plug-ins will have to reference. It defines an interface IDeliveryMethod, which
future plug-ins must implement.
Example 12-10. DeliverySystem.Core.dll
// DeliverySystem.Core.dll
namespace DeliverySystem.Core
Next, let’s author a simple plug-in. Example 12-11 defines a new assembly, which
references DeliverySystem.Core.dll, and implements a new high-speed delivery system
called CarrierPigeon.
// References DeliverySystem.Core.dll
open DeliverySystem.Core
type CarrierPigeon() =
interface IDeliveryMethod with
Loading assemblies
The final piece to our plug-in system is loading other assemblies and any desired types
into the executing process.
You can dynamically load assemblies by using the Assembly.Load method, which will
return an instance of System.Assembly. Once you have an Assembly object, you can then
examine its types and reflect over them—inspecting and/or instantiating the types.
Example 12-12 defines a method loadAsm that opens an assembly by name and later
prints out the number of types in a set of common assemblies.
The 1,317 types defined in FSharp.Core certainly may seem like a lot,
but remember you don’t need to know them all.
Loading plug-ins
Now that we can load assemblies, let’s finish our plug-in demo. Example 12-13 defines
a method loadPlugins, which searches the current directory for .dll files, loads
the assemblies, gets their types, and then filters them for those that implement
IDeliveryMethod.
Finally, it uses the Activator class to instantiate external plug-ins.
Example 12-13. Loading plug-ins
// DeliverySystem.Application.dll
open System
open System.IO
open System.Reflection
open DeliverySystem.Core
let loadPlugins() =
Directory.GetFiles(Environment.CurrentDirectory, "*.dll")
// Load all of the assembly's types
|> Array.map (fun file -> Assembly.LoadFile(file))
|> Array.map(fun asm -> asm.GetTypes())
|> Array.concat
// Just get types that implement IDeliveryMethod
|> Array.filter (typeImplementsInterface typeof<IDeliveryMethod>)
// Instantiate each plugin
|> Array.map (fun plugin -> Activator.CreateInstance(plugin))
// Cast the obj to IDeliveryMethod
|> Array.map (fun plugin -> plugin :?> IDeliveryMethod)
[<EntryPoint>]
let main(_) =
plugins |> Array.iter (fun pi -> printfn "Loaded Plugin - %s" pi.MethodName)
311
Quotation Basics
Simply put, a quotation is an object representing the structure of some F# code. You
can get hold of a quotation by placing quotation markers <@ @> or <@@ @@> around an
expression:
> // Simple addition
<@ 1 + 1 @>;;
val it : Quotations.FSharpExpr<int> =
Call (None, Int32 op_Addition[Int32,Int32,Int32](Int32, Int32),
[Value (1), Value (1)])
{CustomAttributes = [NewTuple (Value ("DebugRange"),
NewTuple (Value ("stdin"), Value (7), Value (3), Value (7), Value (8)))];
Raw = ...;
Type = System.Int32;}
val it : Quotations.FSharpExpr =
Lambda (x,
Call (None,
System.String op_Addition[String,String,String]
(System.String, System.String),
[Value ("Hello, "), x]))
{CustomAttributes = [NewTuple (Value ("DebugRange"),
NewTuple (Value ("stdin"), Value (10), Value (4), Value (10),
Value (26)))];
Type = Microsoft.FSharp.Core.FSharpFunc`2[System.String,System.String];}
Decomposing Quotations
Once you have a quotation, deconstructing the AST is done using active patterns de-
fined in the F# library. So while an Expr<_> represents a complicated tree-like data
structure, you can use pattern matching to match against what the code represents with
// Literal value
| Int32(i) -> printfn "Integer with value %d" i
| Double(f) -> printfn "Floating-point with value %f" f
| String(s) -> printfn "String with value %s" s
// Calling a method
| Call(calledOnObject, methInfo, args)
-> let calledOn = match calledOnObject with
| Some(x) -> sprintf "%A" x
| None -> "(Called a static method)"
// Lambda expressions
| Lambda(var, lambdaBody) ->
printfn
"Lambda Expression - Introduced value %s with type %s"
var.Name var.Type.Name
printfn "Processing body of Lambda Expression..."
describeCode lambdaBody
Using this, we can call our describeCode method in an FSI session, which will produce
the following output:
> describeCode <@ 27 @>;;
Integer literal with value 27
val it : unit = ()
The F# library provides a full range of active patterns for discerning any form of
quotation expression. All available active patterns can be found in the
Microsoft.FSharp.Quotations.Patterns namespace.
Before continuing, let’s take a closer look at the active patterns used in Example 13-1.
Literal values
There are active patterns available for every type of primitive value type in F#, from
int to float to string and so on. Matching against these constant literals gives you an
opportunity to bind their value:
// Literal value
| Int32(i) -> printfn "Integer with value %d" i
| Double(f) -> printfn "Floating-point with value %f" f
| String(s) -> printfn "String with value %s" s
Function calls
Function calls can be matched against the Call active pattern. If it matches, the pattern
introduces three values: the instance of the object on which the method is called (or
None if the method is static), the MethodInfo containing reflection information for the
method, and the list of arguments passed to the method.
// Calling a method
| Call(calledOnObject, methInfo, args)
-> let calledOn = match calledOnObject with
| Some(x) -> sprintf "%A" x
| None -> "(Called a static method)"
Function values
Function values are matched using the Lambda active pattern. The two values bound in
the active pattern are of type Var and Expr:
// Lambda expressions
| Lambda(var, lambdaBody) ->
printfn
"Lambda Expression - Introduced Value %s of type %s"
var.Name var.Type.Name
printfn "Processing body of Lambda Expression..."
describeCode lambdaBody
The Expr is simply the body of the lambda expression. The Var represents the new value
introduced by the lambda expression (its parameter).
// Literal value
| Int32(i) -> printfn "Integer literal with value %d" i
| Double(f) -> printfn "Floating point literal with value %f" f
| String(s) -> printfn "String literal with value %s" s
// Calling a method
| Call(calledOnObject, methInfo, args)
-> let calledOn = match calledOnObject with
| Some(x) -> sprintf "%A" x
| None -> "(static method)"
// Lambda expressions
| Lambda(var, lambdaBody) ->
printfn
"Lambda Expression on value '%s' with type '%s'"
var.Name var.Type.Name
The following FSI session shows the processing of two quotation expressions.
describeCode2 can process the body of the invertNumberReflected function because it
has been decorated with the [<ReflectedDefinition>] attribute:
> // Define functions with and without ReflectedDefinition
let invertNumber x = -1 * x
[<ReflectedDefinition>]
let invertNumberReflected x = -1 * x;;
// Other
You are then free to add new pattern-match rules earlier in the match expression to
“intercept” the things you care about, as in earlier versions of the describeCode method.
Encoding functions in RPN can be difficult and error-prone. Instead, you can use quo-
tations to write F# code—letting compiler build up the expression tree preserving the
order of operations—and then transform the quotation of that code into RPN.
Example 13-5 defines a function called fsharpToRPN, which takes a quotation expression
and decomposes the expression building up a list of operations to express the equation
in RPN.
The example works by intercepting arithmetic operations and recursively generating
the list of RPN operations to compute the left- and righthand sides of the operator.
Then, it simply adds those operators to the list of RPN operations in the correct order.
Example 13-5. Converting F# code to RPN
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patterns
open Microsoft.FSharp.Quotations.DerivedPatterns
let syntheticQuotation =
Expr.Let(
new Var("x", typeof<int * int * int>),
Expr.NewTuple( [ Expr.Value(1); Expr.Value(2); Expr.Value(3) ] ),
Expr.NewTuple( [ Expr.GlobalVar("x").Raw; Expr.GlobalVar("x").Raw ] )
)
Evaluating Quotations
You can not only analyze quotations—but execute them, too. The F# PowerPack con-
tains an experimental library for converting F# quotation expressions to LINQ ex-
pression trees. LINQ expression trees are very similar to F# quotation expressions:
both are ways to represent compiler ASTs. While F# quotation expressions can express
more forms of code than expression trees alone, the main advantage is expression trees
can be compiled and executed.
To evaluate or compile F# quotations, you will need to download and add a reference
to FSharp.PowerPack.dll and FSharp.PowerPack.Linq.dll and open up the Micro
soft.FSharp.Linq.QuoationEvaluation namespace. That will add extension methods to
Expr and Expr<_> allowing you to evaluate and compile quotations via the Eval and
Compile methods.
The following example shows simple evaluation and compilation of F# quotation
expressions:
> // Reference required libraries
#r "System.Core.dll"
#r "FSharp.PowerPack.dll"
#r "FSharp.PowerPack.Linq.dll"
val x : Expr<int> =
Call (None, Int32 op_Addition[Int32,Int32,Int32](Int32, Int32),
[Value (1),
Call (None, Int32 op_Multiply[Int32,Int32,Int32](Int32, Int32),
[Value (2), Value (3)])])
> x.Eval();;
val it : int = 7
> // Compile a function value expression
let toUpperQuotation = <@ (fun (x : string) -> x.ToUpper()) @>
let toUpperFunc = toUpperQuotation.Compile() ();;
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patterns
open Microsoft.FSharp.Quotations.DerivedPatterns
// Addition
// [d/dx] f(x) + g(x) = f'(x) + g'(x)
| SpecificCall <@ (+) @> (_, _, [f; g]) ->
let f' = generateDerivative f
let g' = generateDerivative g
Expr.Call(addMi, [f'; g'])
// Subtraction
// [d/dx] f(x) - g(x) = f'(x) - g'(x)
| SpecificCall <@ (-) @> (_, _, [f; g]) ->
let f' = generateDerivative f
let g' = generateDerivative g
Expr.Call(subMi, [f'; g'])
// Product Rule
// [d/dx] f(x) * g(x) = (f'(x) * g(x)) + (f(x) * g'(x))
| SpecificCall <@ (*) @> (_, _, [f; g]) ->
let f' = generateDerivative f
let g' = generateDerivative g
Expr.Call(addMi,
[ Expr.Call(mulMi, [f'; g]);
Expr.Call(mulMi, [f; g']) ]
)
// Quotient Rule
// [d/dx] f(x) / g(x) = ((f '(x) * g(x)) - (f(x) * g'(x))) / (g^2(x))
| SpecificCall <@ (/) @> (_, _, [f; g]) ->
let f' = generateDerivative f
let g' = generateDerivative g
let numerator =
Expr.Call(subMi,
[ Expr.Call(mulMi, [f'; g])
Expr.Call(mulMi, [f; g'])]
)
let denominator = Expr.Call(mulMi, [g; g])
// Value
// [d/dx] x = 1
| Var(x) ->
Expr.Value(1.0, typeof<double>)
// Constant
// [d/dx] C = 0.0
| Double(_) ->
Expr.Value(0.0, typeof<double>)
let f' =
let quote : Expr =
generateDerivative <@ (fun x -> 1.5*x*x*x + 3.0*x*x + −80.0*x + 5.0) @>
With function f and its derivative f', we can quickly plot their values on a graph,
resulting in Figure 13-1:
open System.IO
let generatePlot() =
use csvFile = new StreamWriter("Plot.csv")
csvFile.WriteLine("x, f(x), f'(x)")
csvFile.Close()
The .NET ecosystem has incredible breadth—by enabling you to run .NET code on
various platforms, like on the Internet via Silverlight, or on mobile devices with the
Compact Framework. It also has incredible depth—by having a wealth of powerful
libraries, from visualization, to communications, to databases, and so on.
This appendix will provide a quick overview of some existing .NET libraries so you can
transition from the sample applications in this book to real-world problem solving. The
APIs covered will be divided into three main areas: visualization, data processing, and
storing data. We’ll end with a quick look at the F# library, including the F# PowerPack.
Visualization
F# is a great tool for processing raw data, but visualizing that data doesn’t need to be
constrained to just the command line.
There are two main visualization APIs available for .NET: Windows Forms (Win-
Forms) and Windows Presentation Foundation (WPF). WinForms is the older of the
two and is an object-oriented wrapper on top of core Windows APIs. With WinForms
it is easy to create a functional UI with buttons and common controls, but it can be
difficult to create a rich and dynamic interface. WPF on the other hand is a much more
design-centric library that allows for sophisticated interfaces at the cost of added com-
plexity and a steeper learning curve.
329
Windows Forms
WinForms is conceptually very simple. Each window on the screen is an instance of a
Form, which contains a collection of UI elements of type Control. As users interact with
the Form’s controls, they will raise events, which the programmer can listen to. For
example, a Button control has a Click event, which will be fired whenever the button
is clicked:
#r "System.Windows.Forms.dll"
#r "System.Drawing.dll"
open System.Windows.Forms
// Create a form
let form = new Form()
// Create a button
let btn = new Button(Text = "Click Me")
btn.Click.AddHandler(fun _ _ ->
MessageBox.Show("Hello, World")
|> ignore)
Example A-1 creates a form that displays a progress bar while counting the number of
words in the complete works of Shakespeare. The form contains two controls, a
ProgressBar and a Label (a control that displays text). As files are processed asynchro-
nously, the two form controls will be updated to indicate progress.
Note the assembly references to System.Windows.Forms.dll and System.Drawing.dll;
these are required for WinForms development.
Example A-1. Using Windows Forms
#r "System.Windows.Forms.dll"
#r "System.Drawing.dll"
open System.IO
open System.Windows.Forms
form.Controls.Add(progress)
form.Controls.Add(wordCountText)
form.Show()
for i in 0 .. filesToProcess.Length - 1 do
totalWords := !totalWords + (countWords filesToProcess.[i])
} |> Async.Start
When the code from Example A-1 is executed, a form similar to Figure A-1 will be
displayed.
Visualization | 331
WPF was designed around a couple of key tenets:
Enable rich media
Make it easy to embed rich media like video and animation into an application.
Whereas WinForms applications typically rely on bland operating system controls,
the hallmarks of a WPF application are smooth corners, translucency, and clear
text. In addition, WPF applications will benefit from hardware acceleration where
possible.
Declarative programming model
Another problem with WinForms applications is that the design of the UI and the
code that powers it are inextricably linked. It is nearly impossible to change the
interface of a WinForms application without needing to rewrite most of the pro-
gram code. In WPF, the UI layout is separate from the code, and therefore much
easier to refactor.
Getting a simple WPF app up and running doesn’t take much more work than Win-
Forms. Example A-2 does the prototypical “Hello, World”–style application using
WPF.
Example A-2. Hello, World in WPF
#r "WindowsBase.dll"
#r "PresentationCore.dll"
#r "PresentationFramework.dll"
open System
open System.Windows
open System.Windows.Controls
Figure A-2 shows our “Hello, World” application in action. Notice how smooth the
text looks. This is because WPF is vector-based, meaning that displays created with
WPF can be zoomed without pixelation, making the overall experience much cleaner
and more visually appealing.
open System
open System.Windows
open System.Windows.Controls
open System.Windows.Markup
<StackPanel>
<TextBlock>Who do you want to say hello to?</TextBlock>
<TextBox Name='NameTextBox'> [name goes here] </TextBox>
<Button Name='SayHelloButton'>Say Hello!</Button>
</StackPanel>
Visualization | 333
</Window>
" // End string-based XAML
The example enabled us to express the UI layout in XAML, but the program code and
interface are still too tightly coupled. Because the program code gets a reference to the
TextBox and Button controls by their Name property, the layout has to have a button and
text box with specific names or else the application will stop working. Ideally, there
would be a way to write code so that it didn’t depend on the specific type of interface
elements.
Binding
The designers of WPF wanted to make progress and enable a true separation of the UI
and the code powering it. To achieve this, XAML supports binding, or linking live
program objects with UI elements.
Let’s rewrite our “Say Hello” application by first focusing on what we want the code
to model, and grafting an interface on top of it later.
To power our “Say Hello” application, we want a class that has an introductory mes-
sage, a Name property the user can set, and a command to greet the user. Example A-4
defines this simple Greeter class as well as a GreetUserCommand, which displays a message
box. This is all the code required to power our WPF application. Note the lack of any
reference to UI elements.
Example A-4. Greeter for the SayHello application
// Greeter.fs
namespace GreetersNamespace
open System
open System.Windows
open System.Windows.Input
[<CLIEvent>]
member this.CanExecuteChanged with get() = m_canExecuteChangedEvent.Publish
Now that we have a model for how the code should execute, we want to define some
XAML to use that model.
First, we need to let our XAML know about the namespace where our code lives. The
following snippet defines a new XML namespace, g, to be the GreetersNamespace in the
current assembly (named WPFinFSharp):
<Window
xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:g='clr-namespace:GreetersNamespace;assembly=WPFinFSharp'
Title='Greeter' Height='220' Width='450'>
Then, we add a resource to the window of type Greeter called TheGreeter. With this
resource in the XAML code, an instance of Greeter will be created behind the scenes
and stored in a resource dictionary. This will enable future UI elements to bind to it:
<!-- Add an instance of 'Greeter' to the Window's resources -->
<Window.Resources>
<g:Greeter x:Key='TheGreeter' />
</Window.Resources>
Visualization | 335
Now, we can refer to that live object using WPF binding. The following XAML asso-
ciates the live Greeter object with the StackPanel. It then adds a TextBlock, which will
display the Introduction property of resource TheGreeter:
<StackPanel DataContext='{Binding Source={StaticResource TheGreeter}}'>
Likewise, we can bind the TextBox’s value to TheGreeter’s Name property. So whenever
the textbox is updated, the Name property is updated accordingly:
<TextBox Margin='7.5' FontSize='36.0'
Text='{Binding Name}' />
Finally, we bind clicking the button to our GreetCommand property, which is an instance
of GreetUserCommand. When activated, this will carry out greeting the user by calling the
Execute method of the command:
<Button
Margin='7.5' FontSize='36.0'
Content='Say Hello' Command='{Binding GreetCommand}'
CommandParameter='{Binding Name}' />
Note that the code references the Greeters namespace from the cur-
rently executing assembly, WPFinFSharp. You’ll need to update that with
the name of the application you build, which may be different, for ex-
ample, Application1.
open System
open System.Windows
open System.Windows.Input
open System.Windows.Markup
module XamlCode =
let xamlUI = "
<Window
xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:g='clr-namespace:GreetersNamespace;assembly=WPFinFSharp'
Title='Greeter' Height='240' Width='450'>
<!-- Bind the TextBox's data to the Greeter's Name property -->
<TextBox Margin='7.5' FontSize='36.0'
Text='{Binding Name}' />
<!-- When you click the button, execute the Greeter's GreetCommand,
passing in the Name property as its parameter -->
<Button
Margin='7.5' FontSize='36.0'
Content='Say Hello' Command='{Binding GreetCommand}'
CommandParameter='{Binding Name}' />
</StackPanel>
</Window>
"
[<EntryPoint; STAThread>]
let main(args) =
let getWindow() =
let o = XamlReader.Parse(XamlCode.xamlUI)
o :?> Window
result
When the “Say Hello” application is compiled and executed, the result looks like
Figure A-3.
Visualization | 337
Figure A-3. Data-bound XAML
It is impossible to show the full extent of WPF data binding in a single example. But
you now have a glimpse of how WPF’s support for data binding allows you to cleanly
separate presentation layer from code and write rich media applications.
There is far more to learn about WPF. If you are interested in doing
serious WPF development, I recommend WPF Unleashed by Adam Na-
than (Sams).
Data Processing
Once you have a fancy user interface set up for your application, the next step is
doing something. There are plenty of great data-processing libraries in .NET for what-
ever your application needs.
Regular Expressions
Manipulating text is one of the most common tasks you will encounter. Because strings
in .NET are immutable, it can be difficult to search and modify them. Fortunately .NET
has the full support for regular expressions.
Regular expressions are a formal language for searching text for particular words or
sequences of characters. You can use regular expressions in .NET to simplify processing
textual data to do things like:
Metacharacters
Regular expressions are built on their own language of metacharacters. The metachar-
acters and syntax for .NET regular expressions are defined in Table A-1.
Table A-1. Regular expression metacharacters
Character Description
Most characters Characters other than . $ { [ ( | ) ] } * + ? are matched verbatim.
. Matches any character except for \n.
[aeiou0-9] Matches any of a series of characters, including those in a range.
[^aeiou0-9] Matches any character except those specified.
\w Matches any word character [a-zA-Z_0-9].
\W Matches any non-word character [^a-zA-Z_0-9].
\d Matches any decimal digit [0-9].
\D Matches any non-decimal digit [^0-9].
\s Matches any whitespace character.
\S Matches any non-whitespace character.
\ Used to escape metacharacters, such as matching . $ { [ ( | ) ] } * + ?.
+ Match the previous character one or more times.
* Match the previous character zero or more times.
? Match the previous character zero or one times.
Even for regular expression pros, the syntax can seem cryptic. Let’s look at this regex
piece by piece.
First, a phone number can start with an optional three-digit area code. This is done by
writing a regular expression to look for parentheses around three digits. Note that the
parentheses need to be escaped:
\(\d\d\d\)
To indicate that the optional area code can only be matched zero or one times, it is
enclosed in parentheses and suffixed with a question mark. The parentheses around
\(\d\d\d\) treat the sequence of five characters as a single entity, for which the question
mark will apply:
(\(\d\d\d\))?
Next, there can be zero or more whitespace characters \s* followed by three digits
\d\d\d, then optional whitespace on either side of a dash separating the first three digits
of a phone number from the rest \s*-\s*, and finally four sequential digits \d\d\d\d.
Example A-6 defines a function isValidPhoneNumber for validating input. The regular
expression itself is processed using the static Regex.IsMatch method.
Example A-6. Validating input with regular expressions
> // Matching a phone number
open System.Text.RegularExpressions
1. e4 e5 2. Nf3 Nc6 3. Bb5 {This opening is called the Ruy Lopez.} 3... a6
4. Ba4 Nf6 5. O-O Be7 6. Re1 b5 7. Bb3 d6 8. c3 O-O 9. h3 Nb8 10. d4 Nbd7
11. c4 c6 12. cxb5 axb5 13. Nc3 Bb7 14. Bg5 b4 15. Nb1 h6 16. Bh4 c5 17. dxe5
Nxe4 18. Bxe7 Qxe7 19. exd6 Qf6 20. Nbd2 Nxd6 21. Nc4 Nxc4 22. Bxc4 Nb6
23. Ne5 Rae8 24. Bxf7+ Rxf7 25. Nxf7 Rxe1+ 26. Qxe1 Kxf7 27. Qe3 Qg5 28. Qxg5
hxg5 29. b3 Ke6 30. a3 Kd6 31. axb4 cxb4 32. Ra5 Nd5 33. f3 Bc8 34. Kf2 Bf5
35. Ra7 g6 36. Ra6+ Kc5 37. Ke1 Nf4 38. g3 Nxh3 39. Kd2 Kb5 40. Rd6 Kc5 41. Ra6
Nf2 42. g4 Bd3 43. Re6 1/2-1/2
In order to access the game move data, first all comments and metadata must be re-
moved. Using Regex.Replace this task is very straightforward. In the game format all
comments and metadata are enclosed between [ ] or { }. So with two regular expres-
sions, any text that matches any sequence of characters between [ ] or { } can be
replaced with the empty string, effectively stripping out all comments and metadata:
// Remove comments and markup from the PGN file
let removeMarkup text =
let tagPairs = new Regex(@"\[(^\])+\]")
let noTagPairs = tagPairs.Replace(text, "")
Using the removeMarkup and getMoves methods in conjunction will get you access to the
meat of the PGN file. While there certainly is need for more format-specific code to be
written, Example A-7 shows how using regular expressions can quickly transform a
chunk of textual data into a form that can be more easily processed.
Example A-7. PGN file parser
> // Starting to parse a PGN file
open System.Text.RegularExpressions
1. e4 e5 2. Nf3 Nc6 3. Bb5 {This opening is called the Ruy Lopez.} 3... a6
4. Ba4 Nf6 5. O-O Be7 6. Re1 b5 7. Bb3 d6 8. c3 O-O 9. h3 Nb8 10. d4 Nbd7
11. c4 c6 12. cxb5 axb5 13. Nc3 Bb7 14. Bg5 b4 15. Nb1 h6 16. Bh4 c5 17. dxe5
Nxe4 18. Bxe7 Qxe7 19. exd6 Qf6 20. Nbd2 Nxd6 21. Nc4 Nxc4 22. Bxc4 Nb6
23. Ne5 Rae8 24. Bxf7+ Rxf7 25. Nxf7 Rxe1+ 26. Qxe1 Kxf7 27. Qe3 Qg5 28. Qxg5
hxg5 29. b3 Ke6 30. a3 Kd6 31. axb4 cxb4 32. Ra5 Nd5 33. f3 Bc8 34. Kf2 Bf5
35. Ra7 g6 36. Ra6+ Kc5 37. Ke1 Nf4 38. g3 Nxh3 39. Kd2 Kb5 40. Rd6 Kc5 41. Ra6
Nf2 42. g4 Bd3 43. Re6 1/2-1/2
"
// Get the list of moves, each prefixed with a number and one or three dots
let getMoves text =
let factRegex = new Regex(@"\d+\.+", RegexOptions.Multiline)
factRegex.Split(text)
> printGameMoves();;
e4 e5
Nf3 Nc6
Bb5
a6
Ba4 Nf6
O-O Be7
Re1 b5
Bb3 d6
c3 O-O
h3 Nb8
d4 Nbd7
c4 c6
...
Capture groups
Regular expressions can be used for more than just validating input and manipulating
text. You can also use regular expressions to extract textual data that matches a given
format.
If you want to use a metacharacter like ?, +, or * to apply to a sequence of characters,
you can group them in parentheses. For example, "(foo)+" matches the phrase "foo"
one or more times. Without the parentheses, "foo+" the regular expression would
match the letters f, o, and then another o one or more times.
Grouping can be more useful than adding expressiveness to regular expression syntax.
If you use the Regex.Match method, a Match object will be returned that you can use to
refer back to the string captured in each captured group.
Example A-8 defines a regular expression to parse out word analogies found on
standardized tests. Each part of the regular expression associated with a word is en-
closed in parentheses, and therefore is its own group.
We’ve already seen a great example of when to use regular expression group captures,
and that is in conjunction with active patterns.
Example A-9 is from Chapter 7. Notice how the partial active pattern returns a tuple
of three strings, which are the result of three captures in the given regular expression.
List.tl is used to skip the first group’s value—the fully matched string—and instead
returns each individual group capture.
The example uses the (|Match3|_|) partial active pattern to match different ways to
specify a date, binding the year, month, and day to values in the pattern match rule.
Example A-9. Regular expression captures via active patterns
let (|RegexMatch3|_|) (pattern : string) (input : string) =
let result = Regex.Match(input, pattern)
if result.Success then
match (List.tl [ for g in result.Groups -> g.Value ]) with
| fst :: snd :: trd :: []
-> Some (fst, snd, trd)
| [] -> failwith "Match succeeded, but no groups found.\n \
Use '(.*)' to capture groups"
| _ -> failwith "Match succeeded, but did not find exactly three groups."
else
None
Writing XML
Writing XML documents is as simple as constructing a hierarchy of XElement objects.
Example A-10 defines a Book type that knows how to convert itself into an XML docu-
ment, via the ToXml method.
In order to work with XML, you must first add a reference to both
System.Xml.dll and System.Xml.Linq.dll.
open System.Xml.Linq
type Book =
| ComicBook of string * int
| Novel of string * string
| TechBook of string * string
member this.ToXml() =
match this with
| ComicBook(series, issue) ->
When used in an FSI session, you can see how easy it is to construct XML documents.
Saving XML documents is as simple as calling the Save method on an XElement object:
> // Convert a list of Book union cases to an XML document
[
ComicBook("IronMan", 1)
ComicBook("IronMan", 2)
TechBook("Machine Learning", "Mitchell")
TechBook("Effective C++", "Meyers")
Novel("World War Z", "Max Brooks")
ComicBook("IronMan", 3)
]
|> bookshelfToXml
|> (printfn "%A");;
<Bookshelf>
<ComicBook>
<Series>IronMan</Series>
<Issue>1</Issue>
</ComicBook>
<ComicBook>
<Series>IronMan</Series>
<Issue>2</Issue>
</ComicBook>
<TechBook>
<Author>Machine Learning</Author>
<Title>Mitchell</Title>
</TechBook>
<TechBook>
<Author>Effective C++</Author>
<Title>Meyers</Title>
</TechBook>
<Novel>
<Author>World War Z</Author>
Reading XML
To open XML documents, use the static methods XElement.Parse and XElement.Load.
Parse takes a string containing structured XML data and returns an instance of
XElement; Load will take a file path to a valid XML file:
> // Load an existing XML document from disk
do
let shelf = XElement.Load("LordOfTheRings.xml")
printfn "Loaded Document:\n%A" shelf;;
Loaded Document:
<Bookshelf>
<Novel>
<Author>The Two Towers</Author>
<Title>Tolkien</Title>
</Novel>
<Novel>
<Author>Fellowship of the Ring</Author>
<Title>Tolkien</Title>
</Novel>
<Novel>
<Author>The Return of the King</Author>
<Title>Tolkien</Title>
</Novel>
</Bookshelf>
val it : unit = ()
Parsed Document:
<Bookshelf>
<TechBook>
<Author>Steve McConnell</Author>
<Title>CodeComplete</Title>
Storing Data
Once you’ve computed the values you need, you will probably want to save that data
to disk or in a database. There are numerous ways to store data in .NET, each with
varying degrees of performance and ease of use.
File IO
Most .NET IO facilities are based around the abstract System.IO.Stream class, which
defines a primitive interface between the stream and the backing store. (Backing store
is just a fancy term for “where the data comes from.”) Classes inherit from Stream and
add specialized ways to read or write from a particular backing store. For example,
MemoryStream is used to read or write data from memory, NetworkStream is used to read
or write data from a network connection, FileStream is used to read or write data from
a file, and so on.
The simplest way to read or write data from disk is to use the File.ReadAllLines and
File.WriteAllLines methods. WriteAllLines takes a file path and a string array of tex-
tual data to be written to the file, while ReadAllLines takes a file path and returns the
string array of the file’s lines:
> // Solving the fizz buzz problem
open System.IO
let computeFileContents() =
[| 1 .. 100 |]
|> Array.map(function
| x when x % 3 = 0 && x % 5 = 0 -> "FizzBuzz"
| x when x % 3 = 0 -> "Fizz"
| x when x % 5 = 0 -> "Buzz"
| _ -> ".");;
Data Serialization
While the ability to read and write data to files is quite useful, you’re still out of luck
if you have no way to convert your classes to a form that can be easily written to disk.
For that, you will need to use serialization. Serialization is the process of converting an
object into a binary format, which can then be persisted on disk or transferred across
a network.
You can write your own mechanisms for serialization, but built into the .NET frame-
work is support to do this for you. To opt into the default serialization provided
by .NET simply add the [<Serializable>] attribute to the type.
Consider the following types defining a baseball team from a discriminated union,
record, and class; each is marked with the [<Serializable>] attribute:
open System
open System.Collections.Generic
// Discrimianted Union
[<Serializable>]
type Position =
// Battery
| Pitcher | Catcher
// Infield
| ShortStop | FirstBase | SecondBase | ThirdBase
// Outfield
| LeftField | CenterField | RightField
// Record
[<Serializable>]
type BaseballPlayer =
{
Name : string
// Class
[<Serializable>]
type BaseballTeam(name : string ) =
mstream.ToArray()
()
;;
Deserialization
Deserializing data is done in much the same way it is serialized, but the Deserialize
method is called on the BinaryFormatter. Deserialize returns an obj, which will need
to be cast as another type.
Example A-12 provides the complement of saveTeam, called loadTeam. The method
opens up a FileStream object, which the BinaryFormatter will read from and deserialize
a BaseballTeam type.
Example A-12. Deserialization
> // Deserialization
let loadTeam(filePath) =
The F# Libraries
Like most modern languages, F# comes with its own standard library. The F# library,
however, comes in two parts:
FSharp.Core.dll
FSharp.Core.dll is implicitly referenced by every single F# application and is the
F# library. It contains the definitions for common types such as list, Async,
option, and so on.
The F# PowerPack
The F# PowerPack is a set of experimental extensions and additional functionality
meant to augment the F# library: for example, modules enabling F# applications
to be source-level-compatible with the OCaml programming language.
FSharp.Core.dll
Learning more about FSharp.Core.dll will enable you to get the most out of your F#
projects. We’ve covered a lot of functionality in the F# library throughout the book so
far, but there are several gems tucked away under the Microsoft.FSharp namespace,
the most useful of which are a couple of purely functional (immutable) data structures.
Chapter 4 introduced mutable collection types as an easier-to-use alternative to list
and seq. However, if you want to strive for pure functional programming, you can use
functional collection types instead.
The Set<_> and Map<_,_> types behave much like the HashSet<_> and Dictionary<_,_>
types from Chapter 4, except that you cannot modify any instance. So when you add
a new element to the collection by using the Add method, rather than returning unit
reader.ReadToEnd()
let uniqueWords =
Array.fold
(fun (acc : Set<string>) (word : string) -> Set.add word acc)
Set.empty
words
uniqueWords;;
Example A-14 uses the Map<_,_> type to associate each word with the number of times
it is used in the book. Then, the Map<_,_> is converted to a sequence of key-value tuples,
sorted, and the top 20 most frequently occurring words are printed to the screen.
Just as the Set<_> was built up in the previous example, the Map<_,_> is also produced
during a fold operation.
Example A-14. Using the Map type
> // The functional Map type
let wordUsage (text : string) =
let words = text.Split([| ' ' |], StringSplitOptions.RemoveEmptyEntries)
let wordFrequency =
Array.fold
(fun (acc : Map<string, int>) (word : string) ->
if Map.contains word acc then
let timesUsed = acc.[word]
Map.add word (timesUsed + 1) acc
else
Map.add word 1 acc)
Map.empty
words
wordFrequency
The F# PowerPack
When F# was still a research project by Microsoft Research, the original F#
library was quite large. Not only did it contain all the functionality found in
FSharp.Core.dll, but it also contained many experimental library extensions. When
Microsoft made the decision to integrate F# into Visual Studio 2010, it meant putting
just the core set of functionality into FSharp.Core.dll and leaving the rest for the F#
PowerPack (to either be used just by language power users or stabilized and eventually
put into the proper F# library).
Inside the F# PowerPack, you’ll find tools and utilities such as:
• Functions and modules found in the OCaml library
• Plotting and graphing libraries
• Tools for generating parsers
• Libraries for enabling LINQ-like support in F#
• An F# CodeDOM, or library for generating F# source code
The F# PowerPack does not ship with Visual Studio 2010; instead you must download
it from CodePlex.
While most F# PowerPack functionality is specialized and targeted for only select de-
velopers, there are two features you might be interested in using.
SI units of measure
In Chapter 7, you learned how to eliminate dimensional analysis bugs by adding units
of measure. However, rather than define standard units such as seconds and meters for
every F# application you write, the F# PowerPack contains built-in units of measure
from the French Système International d’Unités (known to folks in the United States as
the metric system).
val g : float<m/s>
This book presents F# as a great language for helping you to be more productive and
one that can also seamlessly integrate with the existing .NET stack. While it is true that
F# is a great language, the word seamlessly may need to be qualified.
The dominant languages used in .NET today are C# and Visual Basic .NET, and while
in F# you can write and call into object-oriented assemblies to interact with C# and
VB.NET code, some concepts may get lost in translation.
This appendix is focused on how to ease interop with F#. By the end of this appendix,
you’ll be able to identify problem areas when doing multilanguage programming. In
addition, you’ll be able to make your F# interoperate with unmanaged (native) code
using Platform Invoke and COM.
Although F# allows you to work with both C# and Visual Basic .NET,
in this appendix, I’m just going to refer to C#, for convenience. When-
ever you see the term C#, you can replace it with the phrase C# or Visual
Basic .NET, depending on your preference.
.NET Interop
The key to interoperation between .NET languages is to make sure that the types and
methods in the public surface area of a class library are available to all languages con-
suming the code. Intuitively, if a library exposes constructs that are not native to the
consuming language, things can look a little strange.
C# Interoperating with F#
When writing F# code you should refrain from exposing functional types such as dis-
criminated unions, records, and function values directly. (Instead use them only for
internal types and values, and expose regular class types for public-facing methods.)
357
Discriminated unions
Discriminated unions are types that define a series of possible data tags, and an instance
of the discriminated union may only hold the value of one data tag at a time. To enforce
this property, the F# compiler relies on inheritance and polymorphism behind the
scenes.
Example B-1 shows a simple discriminated union in F# for describing the employees
of a company.
Example B-1. Discriminated union exposed from F#
// HumanResources.fs - F# code declaring a discrimianted union
namespace Initech.HumanResources
type Employee =
| Manager of PersonalInfo * Employee list
| Worker of PersonalInfo
| Intern
Discriminated unions seem simple in F# when you have pattern matching and syntax
support; however, when consumed from C# they are quite a pain to use.
Example B-2 shows the same F# code for the Initech.HumanResources.Employee dis-
criminated union as it would look in C# (and therefore the API a C# developer would
have to program against). In the example, interfaces and compiler-generated methods
have been omitted.
You can see how treacherous navigating the type is in C#, but it should also give you
an appreciation for how much legwork the F# compiler is doing to make pattern
matching so elegant.
Example B-2. F# discriminated unions when viewed from C#
using System;
using Microsoft.FSharp.Collections;
namespace Initech.HumanResources
{
[Serializable, CompilationMapping(SourceConstructFlags.SumType)]
public abstract class Employee
{
// Fields
internal static readonly Employee _unique_Intern = new _Intern();
// Methods
internal Employee()
{
}
[CompilationMapping(SourceConstructFlags.UnionCase, 0)]
[CompilationMapping(SourceConstructFlags.UnionCase, 1)]
public static Employee NewWorker(PersonalInfo item)
{
return new Worker(item);
}
// Properties
public static Employee Intern
{
[CompilationMapping(SourceConstructFlags.UnionCase, 2)]
get
{
return _unique_Intern;
}
}
// Nested Types
[Serializable]
internal class _Intern : Employee
{
// Methods
internal _Intern()
{
}
}
[Serializable]
public class Manager : Employee
{
// Fields
internal readonly PersonalInfo item1;
internal readonly FSharpList<Employee> item2;
// Methods
internal Manager(PersonalInfo item1, FSharpList<Employee> item2)
{
this.item1 = item1;
this.item2 = item2;
}
// Properties
[CompilationMapping(SourceConstructFlags.Field, 0, 0)]
public PersonalInfo Item1
{
get
{
return this.item1;
}
}
[CompilationMapping(SourceConstructFlags.Field, 0, 1)]
public FSharpList<Employee> Item2
{
get
{
return this.item2;
}
}
}
// Methods
internal Worker(PersonalInfo item)
{
this.item = item;
}
// Properties
[CompilationMapping(SourceConstructFlags.Field, 1, 0)]
public PersonalInfo Item
{
get
{
return this.item;
}
}
}
}
}
Function values
Function values represent a similar problem when consuming F# code from C#. While
C# supports lambda expressions just like F#, the way the C# compiler handles them
is very different.
In C# the method for representing function values is to use delegates, and in C# 3.0
two generic delegate types were introduced to help aid C# developers when doing
functional programming: System.Action<_> and System.Func<_,_>.
The Action<_> type represents a function that does not return a value. The Func<_,_>
type, on the other hand, does return a value. In both cases, the generic type parameters
are the types of the function’s parameters.
The following snippet shows F# code for creating Action<_> and Func<_,_> function
values:
> // Using the System.Action type
#r "System.Core.dll"
open System
let fsAction = new Action<string>(fun arg -> printfn "Hello, %s" arg);;
> fsFunc.Invoke("4");;
val it : int = 16
Function values created in F# are not of type Action<_> or Func<_,_>, but instead of
type FSharpFunc<_,_>. In F#, function values may be curried, so passing in the first
parameter of a function returns a new function with one less parameter.
Example B-3 shows the type MathUtilities, which has a method named GetAdder that
returns an F# function value taking three string parameters.
Example B-3. Function values in F#
namespace Initech.Math
open System
type MathUtilities =
static member GetAdder() =
(fun x y z -> Int32.Parse(x) + Int32.Parse(y) + Int32.Parse(z))
However, to declare that same function type in C#, you must write:
FSharpFunc <string, FSharpFunc <string, FSharpFunc <string, int>>>
Just imagine what the function signature would look like if it took more parameters!
Curried function values are an elegant way to simplify code in functional languages,
but be wary of exposing them to languages that don’t support currying natively.
F# Interoperating with C#
Consuming C# code from F# is typically no problem because F# contains most all of
the language features found in C# and VB.NET. However, there are a few things you
can do in C# that you cannot do easily in F#; so if you have control over the C#
libraries, you will consume from F#, keep the following points in mind.
In F#, to pass a parameter to a function accepting ref arguments, you have two options:
you can either pass in a ref<'a> value or use the F# address-of operator & on a mutable
value.
The following FSI session shows calling into the CSharpFeatures.ByrefParams method
using both a ref value and the address-of operator. Notice that after the function exe-
cutes, both parameters have been modified:
> // Declare ref / out parameter values
let x = ref 3
let mutable y = 4;;
In F#, out parameters have the special property that they are not required to be passed
to the function, and if they are omitted, the out parameters are returned with the
To expose a method parameter from F# as a ref parameter, simply give the parameter
type byref<'a>. You can then treat that parameter as a local, mutable variable. To
expose an out parameter, give the parameter type byref<'a> as well but also attribute
it with System.Runtime.InteropServices.OutAttribute. The following snippet defines
an F# function that takes a ref and an out parameter, respectively:
open System.Runtime.InteropServices
[<AllowNullLiteral>]
type Sprocket() =
override this.ToString() = "A Sprocket";;
type Widget =
class
new : unit -> Widget
override ToString : unit -> string
end
type Sprocket =
class
new : unit -> Sprocket
override ToString : unit -> string
end
stdin(12,18): error FS0043: The type 'Widget' does not have 'null' as a proper value
> // Works
let x : Sprocket = null;;
Unmanaged Interop
While the .NET platform offers a wealth of functionality and is a great platform for
writing new applications, there are many times when you will want to interoperate with
legacy code: for example, calling into frameworks written in C or C++.
F# supports all the same mechanisms as C# for interoperating with unmanaged code.
Platform Invoke
Platform invoke, also referred to as P/Invoke, is how managed applications can call into
unmanaged libraries, such as Win32 API functions. When you have a non-.NET library
you wish to call into, P/Invoke is how you do it.
For more advanced interop scenarios, you can customize how data gets marshalled by
providing hints to the F# compiler.
Example B-5 declares two structs, SequentialPoint and ExplicitRect, for use with the
Win32 API function PtInRect. The function PtInRect returns whether or not a given
point exists inside of a rectangle. However, because the Win32 function has no knowl-
edge of the .NET type system, it simply expects structs with a specific layout to be
passed on the stack.
The StructLayout attribute determines how the struct’s data gets laid out in memory,
to ensure that it lines up with the way data is expected to be received on the unmanaged
half of the P/Invoke call.
For example, the PtInRect function expects the rectangle to be made up of four 4-byte
integers in left, top, right, bottom order. In the example, the ExplicitRect doesn’t de-
clare its fields in that order. Instead, it uses the FieldOffset attributes so that when the
F# compiler generates code for the ExplicitRect type, its fields are laid out in the
expected order.
new (x, y) = { X = x; Y = y }
val X : int
val Y : int
[<FieldOffset(12)>]
val mutable bottom : int
[<FieldOffset(0)>]
val mutable left : int
[<FieldOffset(8)>]
val mutable right : int
[<FieldOffset(4)>]
val mutable top : int
There are many other ways to customize the marshaling of data between
managed and unmanaged code. For more information, refer to MSDN
documentation of the System.Runtime.InteropServices namespace.
COM Interop
The .NET world where multiple languages use the same runtime to interoperate seam-
lessly isn’t a terribly new idea; in fact, many of .NET’s core architectural principles are
rooted in another technology called Component Object Model or COM. COM is a
1990s-era technology to support binary interoperability between different programs,
so a C++ application written by one developer could interact with a Visual Basic ap-
plication written by another.
Most large, unmanaged applications expose COM interfaces as their way of loading
add-ins, enabling programmatic access, and so on. For example, rather than writing
your own web browser you could just host the Internet Explorer COM control inside
your application.
With the RCW in place, you can reference the library like any other .NET library and
write applications that interact with iTunes. Example B-6 shows an FSI session where
the RCW for calling into the iTunes APIs is used to print out a list of top-rated albums
for the current music library.
This is done by creating an instance of the iTunesAppClass, and accessing the Library
Source property. These all correspond to COM interfaces defined in the iTunes API,
but with the generated RCW it looks just like any other .NET assembly. Moreover, if
you add a reference to an RCW within Visual Studio, you will get full IntelliSense
support.
Example B-6. COM interop
> // Reference required libraries
#r "System.Core.dll"
#r @"C:\Program Files\iTunes\NETiTunesLibrary.dll";;
> // Search my iTunes music library to sort albums by average song rating
// Return type is seq< albumName * avgRating * tracksInAlbum>
let albumsByAverageRating : seq<string * float * seq<IITTrack>> =
iTunesLibrary.Tracks.OfType<IITTrack>()
|> Seq.groupBy(fun track -> track.Album)
|> Seq.map(fun (albumName, tracks) ->
// Get average track rating
let trackRatings = tracks |> Seq.map (fun track -> float track.Rating)
albumName, (Seq.average trackRatings), tracks);;
> // Return whether or not the majority of tracks in the album have been rated
let mostTracksRated tracks =
let rated, notRated =
Seq.fold
(fun (rated, notRated) (track : IITTrack) ->
if track.Rating <> 0 then
(rated + 1, notRated)
else
(rated, notRated + 1))
(0, 0)
tracks
if rated > notRated then
true
else
false
> printTopAlbums();;
Album [Aloha From Hawaii ] given an average rating of 100.00
Album [Sample This - EP ] given an average rating of 95.00
Album [Dark Passion Play ] given an average rating of 83.08
Album [When I Pretend to Fall] given an average rating of 80.00
Album [Weezer (Red Album)] given an average rating of 78.30
Album [The Best Of The Alan Parsons Project] given an average rating of 76.36
Album [Karmacode ] given an average rating of 64.00
val it : unit = ()
A \\ (backslash), 19
.. (double dot/period), list range, 32
& (ampersand) F# Interactive window output
& (pattern matching 'and'), 67, 175 --^^, error, 23
&& (Boolean 'and'), 20 -> (right arrow) value reporting, 22
&&& (bitwise 'and'), 18 > command line prompt, 10
' (apostrophe), generic parameters, 25, 125 # (number sign) F# script-specific directives,
* (asterisk), 95 232
multiplication operator, 15 operators, arithmetic
regular expressions, 339, 343 % modulus, 15
tuple type separator, 30 * multiplication, 15
@ (at sign), verbatim string, 20 ** power, 15
brackets/parens, 244, 341 + plus, 15
( ) pass to higher order functions, 56, 61 - subtraction, 15
( )overloading an operator, 206 / division, 15
( : ) type annotation, 24 operators, comparison and equality
()unit, representation of void, 29 < less than, 21
(* *) multiline comments, 8 <= less than or equal to, 21
(| |) active pattern, 170 <> not equal to, 21
(| |_|) partial active pattern, 171 = equal to operator, 21
.[ ] indexer, 19, 93, 205, 207 > greater than, 21
<@ @> or <@@ @@> expression >= greater than or equal to, 21
quotation markers, 312, 316 operators, other
[ ] list comprehension, 33 ! get ref cell value, 91
[ ] lists, 32 -> right arrow, 34, 50, 63
[< >] attribute, 288 . dot, 300
[| |] array values, 93 :: cons list, 32, 68, 184
{ } define expression/record, 76, 81, 152 := set ref cell value, 91
character escape sequences :> static cast, 141
\" (double quote), 19 :? pattern type test, 143
\' (single quote), 19 :?> dynamic cast, 142, 300
\b (backspace), 19 <- left arrow, 90, 128
\n (newline), 19 << backward composition, 62
\r (carriage return), 19 <<< bitwise 'left shift', 18
\t (horizontal tab), 19 <| pipe-backward, 61
We’d like to hear your suggestions for improving our indexes. Send email to [email protected].
371
>> forward composition, 60 functions, 98, 353
>>> bitwise 'right shift', 18 arrays
? question mark, 300 advantages/disadvantages of, 93
?<- question mark setter, 300 creating, 96
@ list append, 32, 185 equality of, 97
^^^ bitwise 'exclusive or', 18 indexing of, 93
|> pipe-forward, 58, 196, 250 jagged, 99
|| Boolean 'or', 20 multidimensional, 99
||| bitwise 'or', 18 rectangular, 99
% (percent), printf format specifiers, 41, 52 slicing of, 95
%% (percent signs), expression hole, 322 suffix character to create, 20
regular expression metacharacters summing of, 261
* asterisk, 339, 343 assemblies, 145, 305
+ plus, 339, 343 assembly code, alternatives to, 319
. dot (period), 339 assembly-level attributes, 289
? question mark, 339, 343 Async
\ (backslash) escape characters, 339 cancellation, 271
; (semicolon) exception handling, 270
; array values separator, 93 operations, 273
;; F# end of code snippet, 9 starting, 269
/ (slash) Async Library, 269, 275
// single line comment, 8 Async<'T>, 269, 278
/// hover comment, 8 asynchronous programming, 257
`` ``(tickmarks), value name extended asynchronous programming model (APM),
characters, 6 265
~ (tilde), unary operator overload, 206 asynchronous workflows, 248, 268
_ (underscore), wildcard, 63, 126 attributes
| (vertical pipe), separator, 67 accessing through reflection, 291
abstracting data access, 204 declarative programming, 302
accessibility modifiers, 134 multiple, 289
accessing types, 291 overview, 287
accumulator pattern, 193 targets of, 289
Activator class, 308 AttributeUsage attribute, 290
active patterns, 168–178, 312–319, 344 AutoOpen attribute, 183
combining, 175
multi-case, 173
nesting, 176
B
backward composition operator <<, 62
parameterized, 172
banana clips (| |), 170
partial, 170
base class, 137, 138
and regular expression captures, 344
base keyword, 139
single-case, 169
beeps, generating, 235
AddHandler method, 219
BeginInvoke method, 217
aggregate operators, 36, 83, 99
BeginOperation, 265, 274
aliasing, 89
BigInt type, 17
APM (asynchronous programming model),
binary numerical primitive, 15
265
binary recursion, 193
arithmetic operators, 15
binary trees, 193
array comprehensions, 93
BinaryFormatter, 350, 351
Array module, 277
372 | Index
Bind computation expression method, 244, CancellationToken type, 272
248, 249 capture groups, 343
binding in XAML, 334 case sensitivity, 6
bitwise operations, 18 casting, 141
bool type, 20 ceil function, 16
Boolean values, 20 character trigraphs, 289
box function, 220 characters, 19
Byref parameters, 363 checked arithmetic, 16, 181
byref<obj> objects, 237 class hierarchy, 137
byte numerical primitive, 14–17, 350 classes
abstract, 139, 140
adding indexers to, 207
C adding slices to, 209
C#, how F# differs from, 9 converting modules to, 178
(see also troubleshooting: warnings/notes derived, 153
regarding) generic, 125
behavior of equality on arrays, 97 methods and properties, 127–133
cannot partially implement interface, 149 overview, 122
default constructors, 123 preferred way to create, 125
delegates do not have BeginInvoke, versus records, 76
EndInvoke methods, 217 sealed, 140
does not have break and continue keywords, CLI (Common Language Infrastructure), xv,
107 145
does not have event keyword, 218 CLIEvent attribute, 225
does not have protected visibility, 135 closures, 53, 90, 198
does not have try-catch-finally expression, CLR (Common Language Runtime)
111 restrictions, 90, 97, 187
extension methods cannot be consumed, code passing, 250
155 code, eliminating redundant, 197
failure indicated by None, not -1, 99 color console output, 234
for loop more powerful than foreach loop, COM (Component Object Model), 367
107 Combine method, 217
indexers require dot notation, 94 combining active patterns, 175
must explicitly implement base interface, Command Pattern, 116
150 comments, coding, 8
no native out or ref parameters, 363 Common Language Infrastructure (CLI), xv,
types cannot be null, 364 145
use of arbitrary symbolic operators, 207 compare function, 21
!x does not mean “not”, 91 comparison operators, 21
C#, how F# differs from. compile order, 11
does not have Main method, 42 compiler directives, 230
does not have return keyword, 22 SOURCE DIRECTORY, 231, 235
FSI improved code testing, 9 SOURCE FILE, 231
switch statement and pattern matching, 62 Component Object Model (COM), 367
void compared to unit, 28 comprehensions
C#, interoperability with, 357 array, 93
calculator example, 254 list, 33
call stacks, 188 computation expressions, 244
calls, tail, 190 overview, 241
cancellation, 280
Index | 373
computed derivative, 326 describeType function, 292
concurrent data structures, 283 deserialization, 351
concurrent queue, 283 design patterns, 116, 199
cons operator, 32, 68, 184, 185 Dictionary type, 102, 199
console dictionary, concurrent, 284
.Beep, 235 directives, compiler, 230
color output, 234 SOURCE DIRECTORY, 235
printf, printfn, sprintf, 41 SOURCE FILE, 231
.Writeline, 133 directives, F# script-specific, 231
constructors, 122, 129 directories
continuation passing styles, 194 adding new, 233
continuations, 243 walking, 235
control flow, 27 discriminated unions
conversions, 17, 157, 166 adding overloaded operators to, 206
core types, 29 defined, 70
cores, multiple, 258 versus enum, 158
cos function, 16 equality of, 121
cprintfn function, 234 generic, 126
creating events, 218 and interoperability, 358
CSV files, processing, 204 methods and properties of, 75
currying, 51, 59, 130, 196 and multi-case active patterns, 173
custom attributes, 290 pattern matching against, 73
custom logic, 66 reflecting, 297
custom types, 112, 195, 291 for tree structures, 72
custom workflows, 242, 244 division operator, 15
do bindings, 124, 289
do! keyword, 268
D download and install, xvi
data downto keyword, 106
associating with union case, 71 dynamic cast, 142, 143
pattern matching, 68 dynamic instantiation, 298
serialization, 349 dynamic invocation, 299
storing, 348 dynamic typing, 23
using ref cells to mutate, 91 dynamically built lists, 184
data encapsulation, 198
data processing libraries, 338–344
data races, 261, 283 E
deadlocks, 264 eager evaluation, 79
decimal numerical primitives, 14–17 elif (within if then expression), 28
decomposing quotations, 312, 318 else (within if then expression), 27
decr function, 91 empty list, 32
dedents, significance of, 7 encapsulation, 198
default constructor, 160, 213 encryption, 93
default values, 87 end keyword, 150, 159
defined block, 244 EndInvoke method, 217
delegate constraint, 213 EndOperation, 265, 274
DelegateEvent<_;gt type, 220 EntryPoint attribute, 45
delegates, 214–217, 219, 259, 361 enumerable for loops, 107
derivatives, 324 enumeration constraint, 213
derived classes, 137, 153 enumerations, 156–158
374 | Index
equality file extensions, 136, 230
of arrays, 97 file IO, 348
customize meaning of, 120 File methods, 348
definitions of, 22 files, opening, 233
generated, 120 FileStream, 248
object, 119 filesUnder function, 236
equality operators, 21 float type, 29, 167
escape sequences, 19, 339 float, float32 numerical primitives, 14–17
evaluating Boolean expressions, 21 floor function, 16
evaluation, eager versus lazy, 79 fold function, 186
Event class, 218 foldback function, 186
event-driven programming, 215, 330 for loops, 33, 54, 106, 276
events, 215, 218–225 format specifiers, 41, 117
Excel, Microsoft, 238 forward composition operator, 60
exceptions Friedl, Jeffrey E. F., 345
defining custom, 112 .fs, .fsi file extension, 136
DivideByZeroException, 242 FSharp.PowerPack.dll,
handling, 109 FSharp.PowerPack.Linq.dll, 269,
handling with PFX, 281 323
Microsoft.FSharp.Core.FailureException, FSharpFunc<_,_> type, 362
108, 132 FSharpList type, 184
Microsoft.FSharp.Core.MatchFailureExcep FSharpType methods, 296
tion, 64 FSharpValue methods, 299
NullReferenceException, 89 FSI (F# Interactive), 8, 22, 230
Option.get called on None, 40 FSLex, 356
reraising, 111 fst function, 30
StackOverflowException, 187, 188 .fsx file extension, 230
System.FormatException, 17, 171 FSYacc, 356
System.IndexOutOfRangeException, 94 fun keyword, 50
System.InvalidCastException, 142 Func<_,_> type, 274
System.OutOfMemoryException, 80 function calls, 314
System.OverflowException, 182 function composition, 57
exp function, 16 function keyword, 70
explicit class construction, 123, 138 function values, 50, 202, 315, 361
Expr<_> type, 312, 322 functional programming
expression holes, 322 data structures, 352
expressions (see computation expressions) defined, xv
ExprShape module, 318 importance of lists in, 183
eXtensible Application Markup Language overview, 47, 163
(XAML), 333 patterns, 199
extension methods, 154 primitives, 116
pure versus impure, 50
functions
F anonymous, 50
F# developer center, 311 currying, 51
F# Interactive (FSI), 8, 230 generic, 24
F# PowerPack, 322, 355 must return a value, 29
F# signature files, 135 nested, 198
failwith, failwith functions, 108 passing as parameters, 196
fields, 75, 92, 122
Index | 375
recursive, 53 infix notation, 56
returning functions, 52 inheritance, 136
treated as data, 50 initializing enum array, 156
types, 29, 52 initializing of values, 87
installation, xvi
G instantiation, 298
instruction pointer, 188
garbage collection, 146
int core type, 29
general directives, 231
int, int16, int32, int64 numerical primitives,
generated equality, 120
14–17
generic classes, 125
Int32.TryParse function, 39
generic functions, 24, 25
integers, 14
generic types, 211, 292
intentional shadowing, 181
get keyword, 128
interface keyword, 148, 150
GetHashCode function, 292
interfaces, 148–153
GetMethods function, 292
interfaces, plug-in, 306
GetProperties function, 292
intermediate results, 30
GetSlice method, 209
internal accessibility modifier, 134
GetType method, 291
interoperability, 357
Invoke method, 215
H IO operations, asynchronous, 248
handlers, event, 219 IO.Stream type, 273, 348
handling exceptions, 109 IObservable<_> interface, 221
hash codes, 117 it (unnamed expression), 9
HashSet<_> type, 104, 285 Item property, 207
heap, 86, 91 iter implementation, 193
Hello, World examples, 3, 5, 332, 333 iTunes RCW example, 368
hexadecimal numerical primitive, 15
higher order functions, 50 J
jagged arrays, 99
I JIT (just-in-time), 145
#I directive, 233 just-in-time (JIT), 145
IAsyncResult interface, 265
IComparer interface, 152
IConsumable interface, 148
K
keyboard shortcuts, 10, 11
IDisposable interface, 147
IEEE32, IEEE64 standards, 15
IEnumerable<'a>, 80 L
IEvent<_> interface, 225 lambda expressions, 50, 259
if then expressions, 27, 63 lambda syntax for pattern matching, 70
ignore function, 29 late binding, 299
immutability, 6, 49 launching a new process, 236
imperative programming, xv, 49, 85 lazy evaluation, 79–81, 203
implementations, 202 let bindings, 14, 31, 69, 124, 174
implicit class construction, 124, 137 let keyword, 6, 14
impure functional programming languages, 50 let! keyword, 244, 248, 268
incr function, 91 libraries
indents, significance of, 6, 43 F# PowerPack, 352, 355
infinite loop example, 188 FSharp.Core.dll, 352
376 | Index
list, 34 leaks in, 146
XML, 345 Message property, 108
lightweight exceptions, 112 metacharacters, 339
linearize function, 195 metaprogramming, 287, 301
LINQ expression trees, 322 method
list comprehensions, 33 overloading, 133, 137
List module, 34 overriding, 138
List<'T> methods, 101 methods
lists adding to a class, 129
append operator, 32, 185 extension, 154
declaring, 32 inspecting, 292
fold functions, 37 static, 131
iteration function, 39 methods and properties, 34
map function, 36 (see also properties)
pattern matching against structure of, 68 Array module, 98, 277
ranges, 32 Async module, 269
sorting using IComparer<'>, 152 Computation expression, 246
structure of in F#, 184 concurrent structures, 283
System.Collections.Generic, 101–104 Dictionary type, 103
types, 29, 101 for discriminated unions, 75
versus sequence, 80 F# extension, 273, 323
Literal attribute, 65 File module, 348
literal values, 314 FSharpType, 297, 299
#load directive, 233 HashSet type, 104
loading assemblies, 306–307 List module, 34
loading plug-ins, 308 Observable module, 221
local variables, 86 Option module, 40
lock function, 262 Parallel module, 276
log, log10 functions, 16 quoting, 316
logic, custom, 66 for records, 78
loops, 54, 105 Regex class, 340–344
Seq module, 82
System.Object, 117
M Thread object, 259
machine independence, 146 XElement module, 345
managed and unmanaged code, 145, 365 methods, defined, 127
managing source files, 11 metric system, 355
Map<_,_>, 352 Microsoft Intermediate Language (MSIL), 145
marshalling, 366 Microsoft Office, automating, 237
Mastering Regular Expressions (Friedl), 345 Microsoft.FSharp.Control, 273
match keyword, 63 Microsoft.FSharp.Core, 36, 181, 356
matching, pattern, 62 Microsoft.FSharp.Linq, 323
math functions, 16, 48 Microsoft.FSharp.Quotations, 312
Measure attribute, 165, 167 Microsoft.FSharp.Reflection, 296
members, 122 mock object, 154
memoization, 199 module keyword, 43
memory modules, 43
and CLR array limits, 97 controlling usage of, 182
in .NET, 86, 146 converting to classes, 178
lazy programming and, 203
Index | 377
extending, 155 Windows Forms (WinForms), 224, 329,
intentional shadowing using, 181 330
vs. namespaces, 43, 178 Windows Presentation Foundation (WPF),
scope of, 25, 182 329, 331
when not recommended, 45 XML libraries, 345
modulus operator, 15 .NET and COM (Nathan), 370
Mono platform, xvi, 3 new keyword, 215
monoids, 249 newlines, 6
mouse events, 223, 224 non integer index, 208
MSIL (Microsoft Intermediate Language), 145 None value, 39
multi-case active patterns, 173 null, 40, 88, 364
multidimensional arrays, 99 numeric primitive conversion routines, 17
multiple attributes, 289 numeric primitives, 14
multiple processors/cores, 258 numeric values, comparing, 21
multiple source files, managing, 11, 43
multiplication operator, 15
mutability O
collection types, 101 obj, 116, 141, 291
record field, 92 object expressions, 151
variable, 49, 90 object-oriented programming
mutable function values, 202 advantages of, 115
mutable keyword, 90, 92 defined, xv
mutual recursion, 55 disadvantages of, 116
overview, 115
Observable module, 221
N Obsolete attribute, 288
named patterns, 68 octal numerical primitive, 15
namespaces, 43 one-dimensional slice, 209
naming opaque types, 165
.NET interfaces, 150 operator overloading, 205
self-identifiers, 127 operator precedence, 61
Nathan, Adam, 338, 370 option type, 29, 39, 68, 99, 171
nesting optional single-case active patterns, 171
of active patterns, 176 order of
of functions, 25, 198 compilation, 11
of modules, 43 operators, 61
.NET type inference, 58
classes versus F# records, 76 Out parameters, 363
Common Language Infrastructure (CLI), outscoping, 65
145, 365 overflow in arithmetic operators, 16
equality in, 22 overloading, 24, 133, 205, 279
events, 225 override keyword, 138
interoperability, 8, 146, 357, 367 overview, 108
IO facilities, 348
memory in, 86, 146
platform, 145–147 P
Reflection APIs, 287, 296 P/Invoke, 365
regular expressions, 338–344 Parallel Extensions to the .NET Framework
thread pool, 260 (PFX), 276, 281
parallel for loops, 276
378 | Index
parallel programming, 257, 276 Process.Start method, 236
parameterized active patterns, 172 processes versus threads, 258
parsing XML with active patterns, 176 processors, multiple, 258
partial active patterns, 344 program startup, 44
partial function application (currying), 51 programming
partial-case active patterns, 170 asynchronous, 257, 265
Pascal Case, 130 declarative, 302, 332
passing functions as parameters, 196 with functions, 48, 196
pattern matching, 62 imperative, 85
(see also active patterns) lazy, 203
against arrays, 97 parallel, 257
against discriminated unions, 73 pure and impure, 50, 85
against types, 143 properties, 34, 122
alternate lambda syntax, 70 (see also methods and properties)
of data structure, 68 defined, 127
enumerations in, 157 defining class, 128
and exception handlers, 109 inspecting, 292
for loops and, 107 setting, 129
grouping patterns, 67 property getter, setter, 128
limitations of, 168 public accessibility modifier, 134
match failure, 64 pure functional programming, 352
matching literals, 65
named patterns, 64
outside of match expressions, 69 Q
overview, 62 question mark (?)
on records, 77 in regular expressions, 339, 343
when guards in, 66 operator, 300
PFX (Parallel Extensions to the .NET quotation holes, 322
Framework), 276, 281 quotations
PGN (Portable Game Notation), 341 decomposing, 312
PGN file parser example, 342 evaluating, 322
pipe-backward operator, 61 generating, 321
pipe-forward operator, 58, 60, 196, 250 generating derivatives, 324
placeholders, 60 overview, 311
Platform invoke, 365
plug-in architecture, 305
plus operator, 15
R
#r directive, 232
pointers, 87, 188
race condition, 261
polymorphism, 136, 141
raise function, 108
Portable Game Notation (PGN), 341
raising events, 219
power operator, 15
range syntax, 32
pown function, 16
RCW (runtime callable wrapper), 368
prefix notation, 56
Read, Evaluate, Print, Loop (REPL), 9
primary constructor, 124
read-write properties, 128, 208
primitive types, 13, 279, 314
rec keyword, 53
print functions, 28, 39, 41, 52, 117, 234
recipes, F# script, 234–239
private accessibility modifier, 134
records, 75
proactive type, 214, 221
cloning, 76
process, launching a new, 236
equality of, 121
Index | 379
generic, 126 scope, 25, 52, 65, 182
mutable, 92 screen, writing to, 41
pattern matching on, 77 scripting
reflecting, 297 advantages of F#, 229
versus structs, 161 defined, 229
types, 206 editing, 230
rectangular arrays, 99 recipes, 234
recursion Sealed attribute, 140, 161
in class methods, 130 sealed class, 140
in discriminated unions, 71 security issues
in discriminated unions, 74 CLR restrictions, 90
mutual, 55 at runtime, 294
in sequences, 81 spoofing, 141
tail, 187 self identifier, 123
recursive functions, 53, 201 self-identifier, 127
redundancy, avoiding, 197, 241 Seq class, 204, 235
ref function, 91 Seq module, 82
ref type (ref cell), 91, 242 seq type, 79, 169
reference constraint, 213 sequence expressions, 81, 241
reference types, 87, 89, 119 sequences versus lists, 80
ReferenceEquality attribute, 122 Serializable attribute, 349
referential equality, 22, 119 set keyword, 128
ReflectedDefinition attribute, 316 Set type, 352
reflection shadowing, 26, 181
declarative programming, 302 short circuit evaluation, 21
dynamic instantiation, 298 SI units of measure, 355
overview, 287 side effects, 39, 49, 85
plug-in architecture, 305 sign function, 16
type, 291 signature files, 135
regular expressions (Regex), 338–344 sin function, 16
Remove method, 217 single-case active patterns, 169
RemoveHandler method, 219 slashes (/), 8
reordering files in FSI, 11 slices, 95, 100, 209
REPL (Read, Evaluate, Print, Loop), 9 snd function, 30
RequireQualifiedAccess attribute, 182 Some('a) value, 39
reraise function, 111 sound, producing, 235
Result property, 279 SOURCE DIRECTORY directive, 231, 235
Return computation expression method, 246 spaces, 6
return escape character (\r), 19 spawning new processes, 236
reverse polish notation (RPN), 319 spawning threads, 259
rich media, 332 speeding up computation, 257
ROT13, 93 spoofing, preventing, 141
rounding workflows, 249 sprintf function, 42
RPN (reverse polish notation), 319 sqrt function, 16
runtime, 145, 146 stack, 86, 188
runtime callable wrapper (RCW), 368 Stacktrace property, 108
state workflows, 250–255
static keyword/methods, 131
S static typing, 23
sbyte numerical primitive, 14–17
380 | Index
static upcast, 141 FS0191, 90, 161, 183
statically typed, xv FS0366, 151
string manipulation, 341 FS0809, 131
Struct attribute, 159 FS0945, 141
struct constraint, 213 troubleshooting: warnings/notes regarding, 51,
struct keyword, 159 127
structs, 159–162 (see also C#, how F# differs from)
sub sequences, 81 .NET reflection, 294
subscribing to events, 219 active patterns and match statements, 173
subtraction operator, 15 arbitrary symbolic operators in C# and
subtype constraint, 213 VB.NET, 207
suffixes for numeric primitives, 14, 19 assembly-level attributes, 289
sumArray function, 261 constraints on serialization, 349
symbolic operators, 55 creating async primitives, 275
synchronization primitives, 279 curried functions, 52
syntactic transforms, 244 exceptions and performance, 109
System.Collections.Generic lists, 101–104 explicit type annotations for method
System.Object, 116–118, 143 signature, 252
F# PowerPack not in Visual Studio 2010,
323
T hash codes and equality, 118
tab character, 7, 19, 20 large lambdas, 51
tail calls, 190 modules in large scale projects, 45
tail recursion no CLR support for units of measure, 168
accumulator pattern, 193 overuse of #load, 233
continuations, 194 passing functions and code readability, 197
overview, 187, 190 persisting sensitive information in memory,
tan function, 16 294
targets, attribute, 289 pipe-forward operator, 59
Task object, 278 presentation layer code, 329
then keyword, 123 proper semantics when overloading
Thread, 271 operators, 205
Thread methods, 259 timely removal of event handlers, 219
threads, 258–264 Visual Studio, 323, 329
.NET thread pool, 260 while loop with initial false predicate will
overview, 258 not execute, 106
spawning, 259 wildcard matching against discriminated
thread hopping, 248 union, 75
ToString method, 292 !x does not mean “not”, 91
tree structures, 72 truth table example, 20
trigraphs, character, 289 try-catch expressions, 109
troubleshooting: warnings and errors, 64 try-finally expressions, 110
(see also exceptions) Tuple class, 30
FS0001, 23, 28, 31, 41, 165, 167 tuple type, 29, 30, 68, 120, 296
FS0025, 64, 74 two-dimensional slice, 210
FS0026, 65 type annotation, 24
FS0039, 25, 55, 149, 155, 247 type function, 87
FS0043, 89, 365 type inference, 23, 41
FS0058, 7 coding order of, 58
FS0072, 59
Index | 381
and pipe-forward, 59 usage
for records, 77 memory, 97, 203
and recursion, 55 module, 182
and units of measure, 167 use keyword, 147
type instantiation, 299
type keyword, 70
type reflection, 291 V
typedefof function, 292 val (FSI output), 9
typeof function, 291, 292 val bindings, 124, 160
types val keyword, 123
Async<string>, 269 value captures, 65
Boolean, 20 value equality, 22
CancellationToken, 272 value types, 87, 119
concurrent, 283, 284 values
core, 29 accessibility modifiers on module, 135
custom iterator, 195 case sensitivity in, 6
defined, 13 changing, 89
FSharpList, 184 default, 87
Func<_,_>, 274 in scope, 52
generic, 211, 292 initializing of, 87
Lazy, 79 mutable, 89
numeric, 14–18 naming syntax, 6
opaque, 165 using fields to name, 75
option, 39 versus variables, 49
in pattern matching, 63 variables, 49, 60, 86
primitive, 13, 314 VB.NET, how F# differs from, 9
proactive, 214, 221 (see also troubleshooting: warnings/notes
record, 75 regarding)
ref, 91 delegates do not have BeginInvoke,
reference, 87 EndInvoke methods, 217
string, 19, 234 does not have event keyword, 218
unit, 28 FSI improved code testing, 9
value, 87 indexers require dot notation, 94
use of arbitrary symbolic operators, 207
Visual Studio
U and F# PowerPack, 323, 355
Unicode, 19 debugger, 188, 189, 191
uninitialized state, 87 download and install, xvi
union cases, 70 features, 4, 257
unit core type, 29 online documentation, 3
unit, uint16, uint32, uint64 numerical RCW in, 368
primitive, 14–17 script editing in, 230, 239
units of measure, 163–168 sending code to FSI, 10
unmanaged code, 145, 146, 365 warnings and errors in, 288
URLs visualization APIs, 329
CodePlex (www.codeplex.com), 323, 355 void, representation of, 29
F# development center (http://fsharp.net),
327
F# PowerPack (www.codeplex.com/ W
FSharpPowerPack), 355 walking a directory structure, 235
382 | Index
warnings/notes regarding
casting F# objects, 149
rearranging order of source files, 11
web scraper example, 178
WebRequest class, 273
webscripting example, 250
when guards, 66, 169, 178
while loops, 54, 105
whitespace, 6
wildcards, 63, 69
Win32 API functions, 365
Windows Forms (WinForms), 224
Windows Presentation Foundation (WPF),
329, 331
Windows shell for script files, 230
WinForms (Windows Forms), 329, 330
with get and with set methods, 128
with keyword, 63, 76, 109
Word, Microsoft, 237
workflows (see computation expressions)
WPF (Windows Presentation Foundation),
329, 331
WPF Unleashed (Nathan), 338
write-only properties, 128
X
XAML, 333
XML, 176, 345
XML documentation comment, 8
Y
yield keyword, 33, 241
yield! keyword, 81, 235
Z
zero-based array indexes, 93
Index | 383
About the Author
Chris Smith works at Microsoft on the F# team. His role as a software design engineer
in the testing group gives him a unique mastery of the F# language. Chris has a Master’s
degree in computer science from the University of Washington, and a burning passion
for any tasty drink that comes with an umbrella. You can find him online at Chris
Smith’s Completely Unique View at http://blogs.msdn.com/chrsmith/.
Colophon
The image on the cover of Programming F# is a bullfinch (Pyrrhula pyrrhula). Members
of the Fringillidae family, bullfinches are small passerine birds that can be found
throughout central and northern Europe, from the Atlantic coast of western Europe
and Morocco to the Pacific coasts of Russia and Japan. They primarily inhabit wood-
land areas, orchards, and farmlands, and are somewhat notorious for the damage they
do by eating the buds of fruit trees and flowering shrubs in spring. Bullfinches are
skittish, wary birds and are rarely seen on the ground.
The common bullfinch grows to about six inches long. It has a thick neck and a short,
stubby bill. Both sexes are primarily black and white, though males have rose-colored
undersides while females have duller, brownish breasts. The females make nests from
small twigs, moss, and roots, and lay four or five eggs twice a year. Both parents share
feeding responsibilities once the young have hatched.
Between 1968 and 1991, there was a significant decline in the bullfinch population.
While the specific cause is not known, one theory is that the trend of deforestation and
overtrimming hedges on agricultural land compromised many nesting sites and re-
moved the birds’ primary food sources. Increased use of herbicides is also a probable
culprit, as well as the fact that trapping and killing bullfinches was not regulated until
relatively recently. However, bullfinch numbers are now abundant over most of their
range and, with the exception of some local populations and subspecies, the birds are
not considered threatened.
The cover image is from Cassell’s Natural History. The cover font is Adobe ITC Gara-
mond. The text font is Linotype Birka; the heading font is Adobe Myriad Condensed;
and the code font is LucasFont’s TheSansMonoCondensed.