Fundamentals - The Book of F#: Breaking Free with Managed Functional Programming (2014)

The Book of F#: Breaking Free with Managed Functional Programming (2014)

Chapter 3. Fundamentals

In the previous chapter, you learned how F# Interactive can enhance your workflow through rapid feedback and task automation. Now we’ll put that knowledge to work as we explore some basic language features. The concepts introduced in this chapter apply regardless of whether you’re programming primarily in an imperative, object-oriented, or functional style.

Most of this chapter focuses on how F# handles concepts central to the .NET Framework, like the core data types, enumerations, flow control, generics, and exception handling. You’ll also learn how F# can help you write more predictable code through controlling side effects, default immutability, type inference, and option types. Regardless of the subject, though, you should start seeing how F# distinguishes itself as a compelling alternative to C# and Visual Basic.

Immutability and Side Effects

If you’re coming to F# from a primarily object-oriented background, the feature you may find the most challenging to adjust to is default immutability. This is a radical departure from traditional .NET languages that place few restrictions on what can change and when. Programs written in languages without default immutability can be unpredictable because system state (program data) can change at almost any time. We refer to these changes as side effects.

Some side effects, like writing to the console, are relatively benign, but what about when they affect shared resources? What if invoking a function changes a value that’s used elsewhere? Will a function always yield the same result regardless of when it’s called? Consider this C# example that references a public field for some multiplication:

//C#

using System;

using System.Linq;

class Example

{

public static int multiplier = 1;

private static void ① Multiply(int value)

{

var result = value * multiplier;

Console.WriteLine("{0} x {1} = {2}", value, ②multiplier++, result);

}

static void Main()

{

var range = Enumerable.Range(1, 100);

foreach(var i in range)

{

Multiply(i);

}

}

}

// First 10 results

// 1 x 1 = 1

// 2 x 2 = 4

// 3 x 3 = 9

// 4 x 4 = 16

// 5 x 5 = 25

// 6 x 6 = 36

// 7 x 7 = 49

// 8 x 8 = 64

// 9 x 9 = 81

// 10 x 10 = 100

In this example, the Multiply method ① has a side effect where the multiplier is incremented ②. As long as nothing changes anywhere else in the program it’s somewhat predictable, but as soon as you change the order of calls to the Multiply method, introduce another call to theMultiply method, or change the multiplier field through some other mechanism, all future results are brought into question.

To further complicate the issue, consider what happens when multiple calls to Multiply are made in parallel, in this revision of the Main method:

//C#

static void Main()

{

var range = Enumerable.Range(1, 100);

System.Threading.Tasks.Parallel.ForEach(range, i => Multiply(i));

}

// First 10 results

// 1 x 1 = 1

// 6 x 3 = 18

// 7 x 4 = 28

// 5 x 2 = 10

// 10 x 6 = 60

// 11 x 7 = 77

// 12 x 8 = 96

// 13 x 9 = 117

// 14 x 10 = 140

// 15 x 11 = 165

There’s no guarantee as to which operation will execute first when running in parallel, so running this 10 times is likely to give you 10 different results. The unpredictability that comes from using mutable values is why global state (values accessible from anywhere within your application) is generally considered harmful. Properly managing global state requires discipline that can be increasingly difficult to enforce as teams and projects grow.

Functional Purity

Functional languages like F# are often described in terms of their mathematical purity. In purely functional languages like Haskell, programs are composed entirely of deterministic expressions that always return a value, and side effects are expressly forbidden except in certain specific circumstances. In contrast, F# is an impure functional language. As such, it takes an important step toward improving predictability by making values immutable by default.

That’s not to say that F# can’t use variables in the traditional sense; it just means that in order to change a value, you must explicitly allow it and should restrict the value’s scope as much as possible. By keeping the scope narrow, you can code in a primarily functional style but switch to a more imperative or object-oriented style in isolated fragments as appropriate.

By managing side effects through default immutability, F# code is more naturally suited for execution in parallel and concurrent environments. In many cases, carefully controlling what can change reduces, if not eliminates, the need to lock shared resources and ensures that multipleprocesses don’t attempt to make potentially conflicting or behavior-altering changes to the overall system state. This added safety is increasingly important as software development evolves to take advantage of the multiprocessor or multicore systems that are so ubiquitous in modern computing.

Bindings

Bindings are F#’s primary way of identifying values or executable code. There are three types of bindings—let, use, and do—and each has a specific purpose.

let Bindings

let bindings simply associate names with values. They are the most common and versatile binding type. (I briefly introduced let bindings in Chapter 2.) You create a let binding with the let keyword. For example, to bind an integer value you would use something like this:

let intValue = 1

Similarly, to bind a string you could use:

let strValue = "hello"

But let bindings aren’t restricted to simple assignments. You can also use them to identify functions or other expressions:

let add a b = a + b

let sum = add 1 2

Literals

Although the let bindings we’ve seen so far are immutable, they can’t be considered constant values in the traditional .NET sense. Bindings are more like readonly variables in C# (ReadOnly in Visual Basic) than they are constants, in that their values are resolved at run time rather than replaced inline at compile time. You can define a true .NET constant value, called a literal in F#, by decorating a binding with the Literal attribute. (F# follows the same convention as other .NET languages by making the Attribute suffix optional, so in this example both Literal andLiteralAttribute are acceptable.)

[<Literal>]

let FahrenheitBoilingPoint = 212

This causes the compiler to treat the definition the same as a const in C# (Const in Visual Basic), meaning that the value will be compiled inline wherever it is used. As such, bindings decorated as Literal must be a full constructed value type, string, or null.

Mutable Bindings

If you try to change the value of a default binding with the assignment operator (<-), the compiler will tell you that you can’t.

let name = "Dave"

name <- "Nadia"

// Error – immutable binding

To make a binding mutable, simply include the mutable keyword in its definition. Once a mutable binding is defined, you can change its value at will.

let mutable name = "Dave"

name <- "Nadia"

// OK – mutable binding

There is, of course, a caveat: Mutable bindings don’t play nicely with closures (inline functions that can access bindings visible within the scope where they’re defined).

// Horrible, invalid code

let addSomeNumbers nums =

let ① mutable sum = 0

let add = ② (fun num -> sum <- sum + num)

Array.iter (fun num -> add num) [| 1..10 |]

In this example, the mutable binding, sum①, is captured by the add closure ②. If you try to compile this code, the compiler politely informs you of the error and instructs you to either eliminate the mutation or use another mutable construct, a reference cell, instead.

Reference Cells

Reference cells are like mutable bindings in that their values can be changed at run time, but they work much differently. A reasonable way to think of reference cells is that they are to pointers what mutable bindings are to traditional variables. That said, reference cells aren’t really pointers either because they’re concrete types that encapsulate a mutable value rather than pointing to a particular resource or memory address.

You create a new reference cell like a typical let binding except that you include the ref operator before the bound value.

let cell = ref 0

Accessing and changing a reference cell’s value requires a different syntax than a standard binding because we need to affect the encapsulated value rather than the reference cell itself.

① cell := 100

printf "%i" ②! cell

As you can see at ①, the := operator is used to change the reference cell’s value, and at ② the ! operator is used to return the cell’s value.

use Bindings

F# provides a binding mechanism for types that implement the IDisposable interface in a way that’s similar to C#’s using statement. In F#, when you want the compiler to insert a call to an IDisposable object’s Dispose method, you can create a use binding with the use keyword.

Like the using statement, which delimits the block where the IDisposable object is in scope, objects created through use bindings are disposed of when their enclosing block terminates; that is, if a use binding is created at the top level of a function, the object will be disposed of immediately after the function returns. Similarly, if a use binding is created within a nested construct, like a loop, the object will be disposed of when the iteration completes.

The following example shows this principle in action:

open System

let ① createDisposable name =

printfn "creating: %s" name

②{ new IDisposable with

member x.Dispose() =

printfn "disposing: %s" name

}

let ③testDisposable() =

use root = createDisposable "outer"

for i in [1..2] do

use nested = createDisposable (sprintf "inner %i" i)

printfn "completing iteration %i" i

printfn "leaving function"

In this example, the createDisposable function ① writes a message to the console telling you that a disposable object is being created. It then returns an object that prints a message when it’s disposed of ②. The testDisposable function ③ repeatedly invokes the createDisposablefunction both inside and outside of a simple for loop and writes out messages telling you when each block is terminating.

Invoking the testDisposable function produces the following output that shows when each object is created and disposed of in relation to its containing block.

creating: outer

creating: inner 1

completing iteration 1

disposing: inner 1

creating: inner 2

completing iteration 2

disposing: inner 2

leaving function

disposing: outer

A simple and more practical example of a use binding is writing some text to a file, like this:

open System.IO

① let writeToFile filename buffer =

②use fs = ③new FileStream(filename, FileMode.CreateNew, FileAccess.Write)

fs.Write(buffer, 0, buffer.Length)

Notice at ① that a let binding is used for the writeToFile function (functions are data in F#) and that at ② a use binding is used in conjunction with the new keyword ③ to create the FileStream. (The new keyword is optional in F#, but by convention it’s included whenever anIDisposable object is created to indicate that the object should be disposed of. If you create a use binding without the new keyword, the compiler will issue a warning.)

use bindings can’t be used directly within a module, primarily because modules are essentially static classes that never go out of scope. If you try to define a use binding directly within a module, you’ll receive a compiler warning along with a note that the binding will be treated as a letbinding instead, like this:

warning FS0524: 'use' bindings are not permitted in modules and are

treated as 'let' bindings

using Function

For more control over an IDisposable, turn to the using function. Although not a binding in its own right, using offers functionality that’s a bit more like C#’s using statement: Give it an IDisposable and a function that accepts the instance, and using automatically calls Disposewhen it completes, as shown here:

open System.Drawing

using (Image.FromFile(@"C:\Windows\Web\Screen\img100.jpg"))

(fun img -> printfn "%i x %i" img.Width img.Height)

In some ways using is more powerful than its C# counterpart because, like every expression in F#, it returns a value. Consider this revision of the previous example:

open System.Drawing

① let w, h = using (Image.FromFile(@"C:\Windows\Web\Screen\img100.jpg"))

(fun img -> ②(img.Width, img.Height))

③ printfn "Dimensions: %i x %i" w h

Instead of writing the dimensions to the console within the function passed to the using function at ②, we return them as a tuple (a simple type containing multiple data items) and bind each component value to meaningful names as shown at ①, before writing them to the console at ③. Even in this simple example you can begin to see how F#’s composable, expressive syntax leads to more understandable solutions by eliminating most of the plumbing code (code you have to write to satisfy the compiler), allowing you to focus on the problem itself.

Replicating the using Function in C#

I like F#’s using function so much that I’ve created a couple of static helper methods for use in my C# projects.

// C#

public static class IDisposableHelper

{

public static TResult Using<TResource, TResult>

(TResource resource, Func<TResource, TResult> action)

where TResource : IDisposable

{

using(resource) return action(resource);

}

public static void Using<TResource>

(TResource resource, Action<TResource> action)

where TResource : IDisposable

{

using(resource) action(resource);

}

}

They’re not exactly pretty, but they get the job done.

Now here’s the C# version of the preceding examples using my helper functions.

// C#

// using System.Drawing

IDisposableHelper.Using(

Image.FromFile(@"C:\Windows\Web\Screen\img100.jpg"),

img => Console.WriteLine("Dimensions: {0} x {1}", img.Width, img.Height)

);

var dims =

IDisposableHelper.Using(

Image.FromFile(@"C:\Windows\Web\Screen\img100.jpg"),

img => Tuple.Create(img.Width, img.Height)

);

Console.WriteLine("Dimensions: {0} x {1}", dims.Item1, dims.Item2);

Although the code looks and behaves like the F# version, I find the F# version much cleaner, especially with its syntactic support for tuples.

do Bindings

The final type of binding is the do binding, defined with the do keyword. Unlike the other binding types, do bindings don’t attach values to a name; they’re used whenever you need to execute some code outside the context of a function or value definition.

do bindings are commonly used within looping constructs, sequence expressions, class constructors, and module initialization. We’ll look at each scenario in turn as we encounter them in later chapters.

Identifier Naming

We’ve seen quite a few identifiers already, but we haven’t really looked at what makes something a valid identifier. Like any programming language, F# has naming rules.

Identifiers in F# are pretty typical of most programming languages. In general, F# identifiers must start with an underscore (_), an uppercase letter, or a lowercase letter, followed by any combination thereof. Numbers are also valid characters in identifiers so long as they are not the first character. For example, the following are valid identifiers.

let myIdentifier = ""

let _myIdentifier1 = ""

The most interesting thing about identifiers in F# is that there’s an alternative quoted identifier format with fewer restrictions. By enclosing an identifier in double backtick characters ("), you can use virtually any string as a valid F# identifier, like so.

let "This is a valid F# identifier" = ""

It’s usually best to use quoted identifiers sparingly, but they can be incredibly useful in certain situations. For example, they’re often used for naming unit tests. By using quoted identifiers for test names, you can focus on describing the test rather than arguing over naming conventions. If you’re using a test framework (like NUnit), the full quoted name in the test list clarifies what is being tested.

Core Data Types

As a .NET language, F# supports the full range of Common Language Infrastructure (CLI) types. Each of the core primitives and even some more complex types, like System.String, are exposed as type abbreviations (convenient aliases for existing types). Many of these even have additional syntax support to enhance type inference (the compiler’s ability to automatically determine data types) or otherwise simplify working with them.

Boolean Values and Operators

The bool type abbreviation exposes the standard System.Boolean structure. Just as in other languages, bool can have one of two values: true and false.

The F# language includes a few operators for comparing Boolean values, as listed in Table 3-1.

Table 3-1. Boolean Operators

Operator

Description

not

Negation

||

OR

&&

AND

The OR and AND operators are short-circuited so they immediately return when the expression on the left satisfies the overall condition. In the case of the OR operator, if the expression on the left is true, there is no need to evaluate the expression on the right. Similarly, the AND operator will evaluate the expression on the right only when the expression on the left is true.

Numeric Types

F# offers the same selection of numeric types as in other .NET languages. Table 3-2 lists commonly used numeric types along with their corresponding .NET type, value range, and suffix.

Table 3-2. Common Numeric Types

Type Abbreviation

.NET Type

Range

Suffix

byte

System.Byte

0 to 255

uy

sbyte, int8

System.SByte

–128 to 127

y

int16

System.Int16

–32,768 to 32,767

s

uint16

System.UInt16

0 to 65,535

us

int, int32

System.Int32

–231 to 231–1

uint, uint32

System.UInt32

0 to 232–1

u, ul

int64

System.Int64

–263 to 263–1

L

uint64

System.UInt64

0 to 264–1

UL

decimal

System.Decimal

–296–1 to 296–1

M

float, double

System.Double

64-bit double precision number precise to approximately 15 digits

float32, single

System.Single

32-bit single precision number precise to approximately 7 digits

F, f

bigint

System.Numerics.BigInteger

No defined upper or lower bounds

I

nativeint

System.IntPtr

32-bit platform-specific integer

n

unativeint

System.UIntPtr

32-bit unsigned platform-specific integer

un

In general, the suffixes are used more frequently in F# than in other .NET languages because they provide the compiler with all of the information it needs to correctly infer the type.

Numeric Operators

As you might expect, F# includes a number of built-in operators for working with the numeric types. Table 3-3 lists commonly used arithmetic, comparison, and bitwise operations.

Table 3-3. Numeric Operators

Operator

Description

+

Unary positive (does not change the sign of the expression)

Unchecked addition

-

Unary negation (changes the sign of the expression)

Unchecked subtraction

*

Unchecked multiplication

/

Unchecked division

%

Unchecked modulus

**

Unchecked exponent (valid only for floating-point types)

=

Equality

>

Greater than

<

Less than

>=

Greater than or equal

<=

Less than or equal

<>

Not equal

&&&

Bitwise AND

|||

Bitwise OR

^^^

Bitwise exclusive OR

~~~

Bitwise negation

<<<

Bitwise left shift

>>>

Bitwise right shift

It’s important to note that although most of the operators in Table 3-3 work with any numeric type, the bitwise operators work only against the integral types. Also, because of the way floating-point numbers are represented in memory you should avoid using the equality operators with them directly or you may see incorrect results, as shown here:

> let x = 0.33333

let y = 1.0 / 3.0

x = y;;

val x : float = 0.33333

val y : float = 0.3333333333

val it : bool = false

Instead of using the equality operator (=), you can calculate the difference between the two floating-point values and verify that the difference is within a threshold. I generally prefer to define this type of operation as a function for reusability.

> open System

let approximatelyEqual (x : float) (y : float) (threshold : float) =

Math.Abs(x - y) <= Math.Abs(threshold)

approximatelyEqual 0.33333 (1.0 / 3.0) 0.001;;

val approximatelyEqual : x:float -> y:float -> threshold:float -> bool

val it : bool = true

Numeric Conversion Functions

When you’re working with numeric data types in F#, there are no implicit type conversions. This is largely because type conversions are considered side effects, and computation problems arising from implicit type conversions are often difficult to locate.

To work with different numeric types in the same expression, you’ll need to explicitly convert them using the appropriate built-in conversion functions. Each conversion function has the same name as the target type abbreviation, which makes them really easy to remember. For instance, to convert an integer value to a float you’d call the float function, as shown in this example.

let marchHighTemps = [ 33.0; 30.0; 33.0; 38.0; 36.0; 31.0; 35.0;

42.0; 53.0; 65.0; 59.0; 42.0; 31.0; 41.0;

49.0; 45.0; 37.0; 42.0; 40.0; 32.0; 33.0;

42.0; 48.0; 36.0; 34.0; 38.0; 41.0; 46.0;

54.0; 57.0; 59.0 ]

let totalMarchHighTemps = List.sum marchHighTemps

let average = totalMarchHighTemps / float marchHighTemps.Length

Characters

As a .NET language, F# carries on the tradition of using 16-bit Unicode for character data. Individual characters are represented by System.Char and exposed to F# via the char type abbreviation. You can bind most individual Unicode characters to an identifier by wrapping them in single quotes, while the remaining characters are represented with escaped character codes, as shown here.

> let letterA = 'a'

let copyrightSign = '\u00A9';;

val letterA : char = 'a'

val copyrightSign : char = '©'

In addition to the Unicode character escape code, F# has a few other escape sequences for some common characters, as listed in Table 3-4.

Table 3-4. Common Escape Sequences

Character

Sequence

Backspace

\b

Newline

\n

Carriage return

\r

Tab

\t

Backslash

\\

Quotation mark

\"

Apostrophe

\'

Strings

Strings are sequential collections of char and are represented by the string type abbreviation. There are three types of strings in F#: string literals, verbatim strings, and triple-quoted strings.

String Literals

The most common string definition is the string literal, which is enclosed in quotation marks as follows.

> let myString = "hello world!";;

val myString : string = "hello world!"

String literals can contain the same characters and escape sequences described in Table 3-4. Newlines within the string literal are retained unless they’re preceded by a backslash (\) character. If a backslash is present, the newline character will be removed.

Verbatim Strings

Verbatim strings are much like string literals except that they are preceded by the @ character and ignore escape sequences. You can embed quotation marks within the string, but they must be written as "", like this:

> let verbatimString = @"Hello, my name is ""Dave""";;

val verbatimString : string = "Hello, my name is "Dave""

Not parsing escape sequences makes verbatim strings a good choice for representing system paths containing backslashes, provided that you don’t have them stored in a configuration setting somewhere. (You’re not hard-coding your paths, right?)

Triple-Quoted Strings

As the name implies, triple-quoted strings are enclosed in triple quotation marks like ""Klaatu barada nikto!"". Triple-quoted strings are like verbatim strings in that they ignore all escape sequences, but they also ignore double quotes. This type of string is most useful when you’re working with formatted character data that naturally contains embedded quotes, like XML documents. For example:

> let tripleQuoted = """<person name="Dave" age="33" />""";;

val tripleQuoted : string = "<person name="Dave" age="33" />"

String Concatenation

When you want to combine multiple strings, you can concatenate them in a variety of ways. First, there’s the traditional Concat method on the System.String class. This method is exactly what you’d expect from other .NET languages.

> System.String.Concat("abc", "123");;

val it : string = "abc123"

WARNING

Be careful when using String.Concat to not accidentally use the concat extension method defined in FSharp.Core. The concat extension method has more in common with the String.Join method than it does with String.Concat.

You can also use the operators + and ^ to make the code a bit cleaner. For example:

> "abc" + "123";;

val it : string = "abc123"

The + operator is preferred, particularly in cross-language scenarios, because it’s defined on System.String. The ^ operator is provided for ML compatibility, and the compiler will issue a warning if you use it in your code.

Type Inference

I’ve been very careful not to explicitly state any data types in examples so far in order to illustrate one of F#’s most interesting features: type inference. Type inference means that the compiler can often deduce data types based on individual values and usage. In fact, F#’s type inference capabilities are so powerful that they often give newcomers to F# the impression that the language is dynamically typed when it’s actually statically typed.

F# certainly isn’t the first .NET language to include type inference. C# supports type inference through the var keyword, as does Visual Basic when Option Infer is enabled. However, while the type inference in C# and Visual Basic helps avoid some explicit type declarations, it works only in very limited situations. Furthermore, while both C# and Visual Basic can infer data types for individual values, they still generally require you to explicitly specify types in multiple places. In contrast, F#’s top-down evaluation takes type inference to levels never before seen in .NET.

F#’s type inference capabilities permeate the entire language. You’ve seen examples of type inference previously, ranging from simple values to function parameters and return types, but this feature even enters into F#’s object-oriented features.

At the risk of jumping too far ahead, let’s examine how much F#’s type inference helps with a simple class definition, beginning with an example in C#.

// C#

using System;

public class Person

{

public Person(Guid id, string name, int age)

{

Id = id;

Name = name;

Age = age;

}

public Guid Id { get; private set; }

public string Name { get; private set; }

public int Age { get; private set; }

}

Even in this simple example, C# requires no fewer than six explicit type declarations. If you wanted to take it a step further and define readonly backing variables rather than using auto-implemented properties with private setters, you’d take the number of type declarations up to nine!

Now let’s look at an equivalent class in F#.

type Person (id : System.Guid, name : string, age : int) =

member x.Id = id

member x.Name = name

member x.Age = age

Yes, those two definitions are indeed the same class! Not convinced? Figure 3-1 shows how each class looks in the compiled assemblies according to the decompiler, ILSpy.

Comparison of compiled F# and C# classes

Figure 3-1. Comparison of compiled F# and C# classes

NOTE

There is a subtle difference between the two classes that isn’t pictured. The C# class sets the property values via the private setters, whereas the F# class foregoes the private setters and relies exclusively on the backing variables.

As you can see in the decompiled code, the classes are virtually identical. Ignoring the other syntactic differences between the two languages (object-oriented programming is covered in Chapter 4), you can see F#’s type inference in action throughout this example. In F# we needed to specify the data types for each member only once in the constructor, and in many cases the compiler can infer it there, too. Even each property’s data type is automatically inferred from that one definition!

In cases where the compiler can’t infer the type, you can add a type annotation to tell it what the type should be. You can use type annotations anywhere you introduce a new value. For example, you can include a type annotation in a let binding like this:

let i : int = 42;;

You can also annotate each part of a function definition. A function that adds two integers might be annotated like this:

let add (a : int) (b : int) : int = a + b

In this example, no type inference is performed because the definitions explicitly specify the type.

Nullability

If project structure differences and immutability aren’t enough to make your head spin, F# has yet another trick for you: null is almost never used! You can’t create null values directly with F# without resorting to a library function, and types defined in F# allow null as a valid value only if they’re decorated with the AllowNullLiteral attribute. If not for the need to interoperate with .NET assemblies written in languages that lack the same restrictions, null probably wouldn’t be included in the language at all.

By placing such tight restrictions around nulls, the F# language designers have greatly reduced the possibility of encountering stray null references, particularly when you’re working entirely within F#. This means that you get to spend less time checking to see if every reference type instance is null before doing anything with it.

That said, null is still a valid keyword in F#, and you will find that you do need to use it from time to time, particularly as you work with assemblies written in other .NET languages. Usually, you’ll pass null as a parameter to a library function or verify that the return value of a library function isn’t null.

Options

Although F# strives to eradicate nulls from your software, there are times when something legitimately doesn’t have a value. Without nulls this could seem like a problem, but the language has you covered.

Rather than simply allowing null to be valid for every reference type, F# takes an opt-in approach via the Option<'T> type. This type is a generic discriminated union with two values: Some('T) and None. In some ways options are like nullable types, but their explicit nature makes it obvious that a meaningful value might not be present. (We’ll cover discriminated unions in Chapter 5. Generics are covered later in this chapter.)

Options are so important in F# that they have syntactic support in type annotations through the option keyword, as shown here:

> let middleName : string option = None;;

val middleName : string option = None

> let middleName = Some("William");;

val middleName : string option = Some "William"

Options are also how the compiler represents optional parameters for constructors or methods. You make a parameter optional by prefixing it with a question mark (?). Optional parameters are allowed only at the end of the parameter list, as shown here:

type Container() =

member x.Fill ①?stopAtPercent =

printfn "%s" <| match (②defaultArg stopAtPercent 0.5) with

| 1.0 -> "Filled it up"

| stopAt -> sprintf "Filled to %s" (stopAt.ToString("P2"))

let bottle = Container()

In the preceding example, ① is the optional stopAtPercent parameter. The function needs to account for the cases when stopAtPercent is None. One common way to provide a default value in these cases is with the defaultArg function ②. This function is kind of like C#’s null coalescing operator (??) except that it works with options instead of nulls. The defaultArg function accepts an option as its first argument and returns its value when it is Some<_>; otherwise, it returns the second argument.

Unit Type

Expressions must always evaluate to a value, but sometimes they’re evaluated solely for a side effect, such as writing to a log or updating a database. In these cases, turn to the unit type. The unit type, represented by () (an empty pair of parentheses), is a concrete type with a single value that signifies that no particular value is present, so the result of any expression that returns unit can safely be ignored. (In some ways, unit is like a manifestation of the void return type in C# in that it should be returned whenever a function doesn’t really return anything, but it’s also used syntactically to signify parameterless functions.)

Whenever an expression returns a value other than unit, F# expects you to do something with it. The compiler doesn’t care whether you bind the value to an identifier or pass it as an argument to a function; it just wants you to use it. When you don’t do something with the return value, the compiler warns that the expression should have type unit because it may actually indicate a program error (the warning is displayed only in compiled code and doesn’t appear in FSI). For example:

let add a b = a + b

// Compiler warning

add 2 3

If you don’t want to do anything with the return value, you can pass the result to the ignore function, which accepts a single, unconstrained generic argument and returns unit.

let add a b = a + b

// No warning

add 2 3 |> ignore

In this example, the add function’s result is sent to the ignore function via the forward pipelining operator (|>). This operator evaluates the expression on the left and sends the result as the last argument to the expression on the right. We’ll look at the forward pipelining operator in detail inPipelining.

Enumerations

Enumerations help you write more readable code by letting you assign descriptive labels to integral values. F# enumerations compile to the same CLI type as in other .NET languages, so all of the capabilities and restrictions that apply in C# or Visual Basic apply in F# too.

The basic syntax of an enumeration in F# is:

type enum-name =

| value1 = integer-literal1

| value2 = integer-literal2

-- snip --

However, unlike in C# and Visual Basic, F# doesn’t automatically generate a value for each label in an enumeration, so you need to explicitly provide one. For example, if your program represents each day of the week as an integer, you might define a DayOfWeek enumeration like this:

type DayOfWeek =

| Sunday = 0

| Monday = 1

| Tuesday = 2

| Wednesday = 3

| Thursday = 4

| Friday = 5

| Saturday = 6

Should you want to base your enumerations on an integral type other than int, simply include the appropriate suffix for the data type in the label definition. For example, you could easily change the preceding DayOfWeek sample to use sbyte as its underlying type by changing the suffix on each value:

type DayOfWeekByte =

| Sunday = 0y

| Monday = 1y

| Tuesday = 2y

-- snip --

Flags Enumerations

The enumerations we’ve seen so far represent only single values. However, it’s common for each label to represent a value by position in a bit mask so that multiple items can be combined.

For example, consider the case of the RegexOptions enumeration from the System.Text.RegularExpressions namespace. This enumeration allows you to control how the regular expression engine processes the pattern by combining multiple values with the logical or operator, like this:

open System.Text.RegularExpressions

let re = new Regex("^(Didactic Code)$",

RegexOptions.Compiled ||| RegexOptions.IgnoreCase)

To achieve this same result in your own enumerations, include the Flags attribute and use values that are powers of two.

open System

[<Flags>]

type DayOfWeek =

| None = 0

| Sunday = 1

| Monday = 2

| Tuesday = 4

| Wednesday = 8

| Thursday = 16

| Friday = 32

| Saturday = 64

NOTE

The Flags attribute isn’t required, but it’s good practice to include it to show other developers how the enumeration should be used.

You can now represent the days in a weekend by combining the Saturday and Sunday values as we did previously.

let weekend = DayOfWeek.Saturday ||| DayOfWeek.Sunday

If you know that several values will be commonly combined, consider including those combinations in your enumeration definition. F# doesn’t allow referencing the other values in the definition by name, but you can still provide the appropriate corresponding integral value. In the case ofDayOfWeek you could provide Weekdays and WeekendDays with the values 62 and 65, respectively.

open System

[<Flags>]

type DayOfWeek =

-- snip --

| Weekdays = 62

| WeekendDays = 65

It’s easy to determine whether a particular enumeration value has a particular flag set with the HasFlag method of System.Enum.

> DayOfWeek.Weekdays.HasFlag DayOfWeek.Monday;;

val it : bool = true

> DayOfWeek.Weekdays.HasFlag DayOfWeek.Thursday;;

val it : bool = true

> DayOfWeek.Weekdays.HasFlag DayOfWeek.Sunday;;

val it : bool = false

Reconstructing Enumeration Values

Using named labels for integral values is a great way to avoid magic numbers (numbers without any apparent meaning) in your code, but what if you save off the underlying value (say, to a database) and later want to reconstruct the original enumeration value from it? The built-in enumfunction allows you to do just that for integer (int32) values.

> enum<DayOfWeek> 16;;

val it : DayOfWeek = Thursday

When the enumeration’s underlying type is something other than int32, use the EnumOfValue function from the Microsoft.FSharp.Core.LanguagePrimitives module namespace instead.

> open Microsoft.FSharp.Core.LanguagePrimitives

EnumOfValue<sbyte, DayOfWeek> 16y;;

val it : DayOfWeek = Thursday

NOTE

Enumeration types aren’t constrained to the values identified by labels, so when using these functions be sure to create only enumeration values that you’ve accounted for in your code.

Flow Control

Despite its emphasis on functional programming, F# fully supports several imperative constructs for looping and branching. These are particularly useful in combination with other constructs like sequence expressions (particularly the looping constructs), but they’re certainly useful in other contexts as well.

Looping

Recursion is the preferred looping mechanism in functional programming, but F# also includes a few approaches typically found in imperative languages. These looping structures are similar to those of other languages.

NOTE

F# doesn’t provide mechanisms (like break or continue) for premature termination, so take extra care when using loops.

while Loops

The simplest iterative structure is the while...do loop. As you might expect, this construct evaluates a Boolean expression and iterates as long as that condition is true. while loops are useful when you need to iterate an unknown number of times, but because they inherently rely on a state change, they can’t be used in pure functional programming. The body of the loop can be any expression that returns unit.

One scenario in which while loops are helpful is responding to user input. In the following example, the echoUserInput function uses a while loop to echo whatever the user enters at the console until it encounters the word quit.

let echoUserInput (getInput : unit -> string) =

let mutable input = getInput()

while not (input.ToUpper().Equals("QUIT")) do

printfn "You entered: %s" input

input <- getInput()

echoUserInput (fun () -> printfn "Type something and press enter"

System.Console.ReadLine())

for Loops

When you know how many iterations you need to perform, you can turn to one of the for loop variations: simple or enumerable. Simple for loops are pretty restrictive in that they can iterate only over a range of integers and always return unit. Attempting to return something other thanunit will result in a compilation error.

Simple for loops are useful when you know how many times you need to iterate. Here, the numbers 0 through 100 are printed in the body of a simple for loop:

for i = 0 to 100 do printfn "%i" i

By replacing the to keyword with the downto keyword, you can make a simple for loop count down instead.

for i = 100 downto 0 do printfn "%A" i

The more powerful variation of the for loop is the enumerable for loop. In some ways, the enumerable for loop is similar to C#’s foreach loop in that it operates over any sequence (collection types implementing IEnumerable<'T>). For instance, the enumerable for loop makes it easy to iterate over a range of integers, like this:

for i in [0..10] do

printfn "%A" i

In reality, though, the enumerable for loop is a fancy syntactic shortcut for applying F#’s powerful pattern-matching capabilities over a sequence. With pattern matching, you can extract values from more complex types and even perform some rudimentary filtering right in the loop definition! No LINQ required!

① type MpaaRating =

| G

| PG

| PG13

| R

| NC17

② type Movie = { Title : string; Year : int; Rating : MpaaRating option }

③ let movies = [ { Title = "The Last Witch Hunter"; Year = 2014; Rating = None }

{ Title = "Riddick"; Year = 2013; Rating = Some(R) }

{ Title = "Fast Five"; Year = 2011; Rating = Some(PG13) }

{ Title = "Babylon A.D."; Year = 2008; Rating = Some(PG13) } ]

④ for { Title = t; Year = y; Rating = Some(r) } in movies do

printfn "%s (%i) - %A" t y r

At ① we see a discriminated union representing the rating scale, at ② a record type representing a movie with an optional rating, at ③ an F# list, and finally at ④ the for...in loop with a pattern match to find all movies that have been rated. The compiler will highlight the pattern match and warn you about not having a covering case, but that’s okay because we’re using it as a filter.

NOTE

Don’t worry about all this discussion of pattern matching, discriminated unions, record types, and other functional concepts yet. We’ll explore each in detail in Chapter 5 and Chapter 7.

Branching

F# offers only a single imperative construct for branching: the if...then...else expression, as shown next. This expression evaluates a Boolean expression in the if part. When that expression evaluates to true, the then branch is executed; otherwise, the else branch is executed (if one is present).

let isEven number =

if number % 2 = 0 then

printfn "%i is even" number

else

printfn "%i is odd" number

You can chain multiple if...then...else expressions together with the elif keyword (a shortcut for else if), as shown next. This has the same effect as nesting them, though the result is much more readable.

let isEven number =

if number = 0 then

printfn "zero"

elif number % 2 = 0 then

printfn "%i is even" number

else

printfn "%i is odd" number

Because the if...then...else expression returns a value, constructs like C#’s conditional operator (?:) aren’t necessary. You should know, though, that because the expression returns a value it behaves a bit differently depending on how it’s being used. When only the if branch is specified, its expression must evaluate to unit, but when both the if and else branches are specified, their expressions must both evaluate to the same type.

In each example so far, the result of the if...then...else expression has been unit, but what happens if you change the function to use sprintf instead of printfn like this?

let isEven number =

if number = 0 then

sprintf "zero"

elif number % 2 = 0 then

sprintf "%i is even" number

else

sprintf "%i is odd" number

Instead of printing the message to the console, the isEven function actually returns the message as a string. You can see this by invoking the function in FSI like so:

> isEven 0;;

val it : string = "zero"

> isEven 1;;

val it : string = "1 is odd"

> isEven 2;;

val it : string = "2 is even"

Generics

Don Syme, the designer and architect of F#, was heavily involved in the research and development of what eventually became generics in the .NET Framework. With a heritage like that, it’s no surprise that generics in F# are incredibly robust, in some ways even more powerful than in other .NET languages.

Generics allow you to define functions, classes, methods, interfaces, and structures that can work directly with any data type. Without generics, the only way to write type-safe code that works with multiple data types is to write a separate implementation for each type. However, this approach is limiting, because any new type that relies on that code will need its own implementation. Generics abstract away this complexity by generating these implementations for you based on the type parameters you’ve supplied in your code.

To show how useful generics really are, consider how one of the original .NET collection types, the ArrayList, compares to its cousin, the generic list. The ArrayList class is a collection type that has been around since the earliest days of .NET and well before generics were available in the framework. In order for it to hold data of any type, it needed to treat every element as System.Object. As a result, code written with ArrayList almost always involved excessive type conversions of elements in the list. Worse, there was no way to enforce consistency between elements, so although a developer might believe that every element in the list was a string, it could very well also contain integers, floats, or instances of any other data type. This type of code was highly error prone and often had a negative impact on performance.

The generic List<'T> class, on the other hand, can be instantiated to work with any specific data type. It removes all ambiguity about what its elements are and typically eliminates the type conversions (subclassing notwithstanding), which leads to more reliable and efficient code.

Since their beginning, generics have played a starring role in virtually every innovation in .NET development, including LINQ and the Task Parallel Library. In some ways, they play an even greater part in F# development than in traditional .NET development because of their role in the type inference process and concepts like statically resolved type parameters (discussed in Statically Resolved Type Parameters).

In F#, most generic type parameters are named with a leading apostrophe. For example, 'a, 'A, and 'TInput are all valid type parameter names. By convention, F# uses sequential lowercase identifiers for inferred type parameters, whereas user-defined type parameters begin with an uppercase character.

Automatic Generalization

F#’s type inference feature favors generic types whenever possible through a process called automatic generalization. Here it is in action:

> let toTriple a b c = (a, b, c);;

val toTriple : a:'a -> b:'b -> c:'c -> 'a * 'b * 'c

In this example the toTriple function converts its three parameters into a three-item tuple (sometimes called a triple). We’ll explore the arrows and tuples in detail in Chapter 5; for now just recognize that the compiler automatically generalized each of the three parameters to the types 'a,'b, and 'c, respectively.

Whether the compiler can automatically generalize a parameter depends largely on how and where it’s used. Automatic generalization is attempted only with immutable values on complete function definitions with explicit parameters.

Explicit Generalization

If the compiler can’t automatically generalize a parameter, or you want more control over it, you can explicitly generalize a parameter with a type annotation. This is especially useful when you want to constrain the types allowed. You could rewrite the previous toTriple example with explicit type parameters as follows:

> let toTriple (a : 'A) (b : 'B) (c : 'C) = (a, b, c);;

val toTriple : a:'A -> b:'B -> c:'C -> 'A * 'B * 'C

When type parameters are unconstrained, you’re fairly limited in what you can do with them. Generally, you can use them only with other unconstrained generic types or functions, and good luck invoking any methods beyond those defined on System.Object. To do something that depends on some aspect of the type, such as calling an interface method, you’ll need to add a constraint.

If you’re familiar with generic constraints in C# or Visual Basic, you may have been frustrated by the lack of things you can actually constrain. In those languages you can constrain type parameters only to reference types, value types, types with a default constructor, types that derive from a particular class, and types that derive from a particular interface. F# supports each of these but also adds a few other constraints.

NOTE

Most constraint types apply to standard type parameters, but a few apply only to an F#-specific form of type parameters called statically resolved type parameters. In the following examples, you’ll see these constraints defined in inline functions with a type parameter that uses a caret (^) instead of an apostrophe. Statically resolved type parameters are described later in this section.

You apply constraints by following the generic type annotation with when and the constraint. You can specify multiple constraints by combining them with and.

§ Subtype constraints. A subtype constraint limits the acceptable types to the constraint type itself or any type that derives from that type. When the constraint type is an interface, the provided type needs to implement that interface.

let myFunc (stream : 'T when 'T :> System.IO.Stream) = ()

Nullness constraints A nullness constraint limits the acceptable types to those where null is a valid value.

let inline myFunc (a : ^T when ^T : null) = ()

§ Member constraints. A member constraint ensures that the supplied type includes a member with a specific signature. You can constrain the types based on either instance or static members.

§ // instance member

§ let inline myFunc

§ (a : ^T when ^T : (member ReadLine : unit -> string)) = ()

§

§ // static member

§ let inline myFunc

(a : ^T when ^T : (static member Parse : string -> ^T)) = ()

§ Default constructor constraints. A default constructor constraint ensures that the supplied type has a default constructor.

let myFunc (stream : 'T when 'T : (new : unit -> 'T)) = ()

§ Value type constraints. A value type constraint restricts the supplied type to any .NET value types except System.Nullable<_>.

let myFunc (stream : 'T when 'T : struct) = ()

§ Reference type constraints. A reference type constraint ensures that the supplied type is a .NET reference type.

let myFunc (stream : 'T when 'T : not struct) = ()

§ Enumeration constraints. An enumeration constraint limits the supplied types to enumerations with a specific underlying type.

let myFunc (stream : 'T when 'T : enum<int32>) = ()

§ Delegate constraints. A delegate constraint restricts the provided types to delegate types with a particular set of arguments and return type. Delegate constraints are intended primarily for use with traditional .NET event handlers.

§ open Systemlet

myFunc (stream : 'T when 'T : delegate<obj * EventArgs, unit>) = ()

§ Unmanaged constraints. Unmanaged constraints restrict the provided type to unmanaged types like some of the numeric primitives and enumeration types.

let myFunc (stream : 'T when 'T : unmanaged) = ()

§ Equality constraints. An equality constraint restricts the provided type to types that support equality. This constraint is considered weak because it’s satisfied by nearly every CLI type.

let myFunc (stream : 'T when 'T : equality) = ()

§ Comparison constraints. Comparison constraints are satisfied only by types that implement System.IComparable, arrays, nativeint, and unativeint unless the type has the NoEquality attribute.

let myFunc (stream : 'T when 'T : comparison) = ()

Flexible Types

Although not strictly generic constructs, flexible types are a syntactic shortcut for subtype constraints. They’re particularly useful with the function arguments of a higher-order function where automatic type conversion normally doesn’t automatically occur.

You can specify a flexible type by prefixing a type name with a # character within a type annotation.

let myFunc (stream : #System.IO.Stream) = ()

Wildcard Pattern

When you want to use a generic type as a parameter but want the compiler to infer the type, you can use the Wildcard pattern in place of a named type parameter. The Wildcard pattern is represented with an underscore.

let printList (l : List<_>) = l |> List.iter (fun i -> printfn "%O" i)

The preceding function will print each element in an F# list with its ToString function regardless of what type is contained in the list.

Statically Resolved Type Parameters

F# has two classifications of generics. The first (which we’ve focused on almost exclusively so far) is standard generics, the same generics as in other .NET languages. The second, called statically resolved type parameters, is specific to F# and identified by a caret (^) instead of an apostrophe. Statically resolved type parameters force the compiler to resolve the types at compile time rather than run time. The implication is that the compiler generates a version of the generic type for each resolved type rather than a single version.

Statically resolved type parameters are primarily intended for use with inline functions and are especially well suited for custom operators, as shown here.

let inline (!**) x = x ** 2.0

When this operator is compiled, it uses static resolution with a constraint to ensure that any types that use it include the Pow function in their definition based on the use of the ** operator.

val inline ( !** ) :

x: ^a -> ^a when ^a : (static member Pow : ^a * float -> ^a)

When Things Go Wrong

Despite your best efforts and the extra safety that F# provides, things can and will go wrong. Proper error handling is a critical piece of any program. F# builds upon the standard .NET exception mechanisms with additional syntactic support that allows you to throw (or raise in F# parlance) and handle exceptions with ease. (For convenience, the standard exception type, System.Exception, is abbreviated as exn.)

Handling Exceptions

Error conditions are always a possibility, so it’s important to know how to handle them properly when they arise. F# provides two constructs for error handling: try...with and try...finally. These constructs are strictly independent of each other; that is, there is notry...with...finally construct in F#. If you need both a with and a finally block, you’ll generally nest a try...with block within a try...finally block, although the nesting order doesn’t really matter.

try. . .with Expressions

In a try...with construct, the expressions contained within the try block are evaluated and if any raise an exception, F# pattern matching is used to locate an appropriate handler in the with block.

Input/output-related operations, like reading from a file, are great examples of where you’d use the exception-handling constructs because you’re at the mercy of external factors like network availability issues or file permissions. In this example, we attempt to read a text file and write its contents to the console but do so in a try block.

open System.IO

try

use file = File.OpenText "somefile.txt"

file.ReadToEnd() |> printfn "%s"

with

| ①:? FileNotFoundException -> printfn "File not found"

| ②_ -> printfn "Error loading file"

If an exception is raised, execution passes to the with block, where the system attempts to find a handler first using ①, a Type-Test pattern (a pattern that matches a specific data type). In this case, the Wildcard pattern ② (a general-purpose pattern that matches everything) is used as a general exception handler. If a suitable match isn’t found, the exception bubbles up the call stack until a handler is found or the application fails.

Without delving too much into the specifics of pattern matching, we can look at a few ways to unlock the potential of the with block. As it stands now, the handler for FileNotFoundException isn’t very helpful because it doesn’t give any information about which file wasn’t found. You can capture the exception for use in the handler by including an identifier with the as keyword in the pattern.

try

-- snip --

with

| :? FileNotFoundException as①ex ->

②printfn "% was not found" ex.FileName

| _ -> printfn "Error loading file"

Now that the ex identifier is defined ①, you can include the filename in the printed message ②.

You can even combine cases when two or more exception types should use the same handler.

try

-- snip --

with

| :? FileNotFoundException as ex ->

printfn "%s was not found" ex.FileName

| :? PathTooLongException

| :? ArgumentNullException

| :? ArgumentException ->

printfn "Invalid filename"

| _ -> printfn "Error loading file"

Sometimes you may want to partially handle an exception at one level but still allow it to traverse up the call stack to another handler. You could raise the exception normally with the raise function, but in doing so you’d lose the call stack information embedded in the exception and later handlers would recognize your handler as the source of the error. To preserve the stack trace, reraise the exception with a function that’s valid only within a with block: reraise.

try

-- snip --

with

| :? FileNotFoundException as ex ->

printfn "%s was not found" ex.FileName

| _ ->

printfn "Error loading file"

reraise()

Unlike in C# and Visual Basic, F#’s try...with construct is an expression, so it returns a value. All of the examples so far have returned unit. This opens up more possibilities as to how you can use the construct, but it also means that each exception case must have the same return type as the try block.

A common practice is to have the try...with return an option type where the try block returns Some<_> and each exception case returns None. You can follow this pattern to return the contents of a text file.

open System

open System.Diagnostics

open System.IO

let fileContents =

try

use file = File.OpenText "somefile.txt"

①Some <| file.ReadToEnd()

with

| :? FileNotFoundException as ex ->

printfn "%s was not found" ex.FileName

②None

| _ ->

printfn "Error loading file"

reraise()

In this example, you can see at ① where an option is created with the contents of the text file and returned. Returning None from the FileNotFoundException handler is shown at ②.

try. . .finally Expressions

The try...finally construct is used to execute code that must run regardless of whether the code in the try block raises an exception.

Usually, try...finally is used to clean up any resources that might have been left open by the try block, as shown here:

try

use file = File.OpenText "somefile.txt"

Some <| file.ReadToEnd()

finally

printfn "cleaning up"

Raising Exceptions

An exception-handling mechanism isn’t much use if you’re stuck handling exceptions from library functions but can’t raise your own. You can raise an exception of any type with the raise function.

let filename = "x"

if not (File.Exists filename) then

raise <| FileNotFoundException("filename was null or empty")

In addition to raise, F# includes a sprinkling of additional functions for raising some of the more commonly used exceptions. The failwith and failwithf functions are convenient for general exceptions. Both raise a Microsoft.FSharp.Core.FailureException, but thefailwithf function allows you to use the F# format strings (discussed in String Formatting), as shown here.

// failwith

if not (File.Exists filename) then

failwith "File not found"

// failwithf

if not (String.IsNullOrEmpty filename) then

failwithf "%s could not be found" filename

Another common exception type that’s easily raised through a built-in function is System.ArgumentException. To conveniently raise it, use the invalidArg function.

if not (String.IsNullOrEmpty filename) then

invalidArg "filename" (sprintf "%s is not a valid file name" filename)

Custom Exceptions

It’s usually best to use predefined exception types like ArgumentException, FormatException, or even NullReferenceException, but if you must define your own exception types, you can define a new class that extends System.Exception. For example:

type MyException(message, category) =

inherit exn(message)

member x.Category = category

override x.ToString() = sprintf "[%s] %s" category message

You can raise your custom exception with the raise function and handle it in a try...with or try...finally block as with any other exception type. Here you can see the custom MyException exception raised and caught.

try

raise <| MyException("blah", "debug")

with

| :? MyException as ex -> printfn "My Exception: %s" <| ex.ToString()

| _ as ex -> printfn "General Exception: %s" <| ex.ToString()

There’s also a lightweight alternative to creating exception classes. In F# you can define a custom exception type and its associated data with the exception keyword. Exceptions created this way are still standard .NET exceptions that derive from System.Exception, but the syntax borrows from a few functional concepts (syntactic tuples and discriminated unions, in particular) to accomplish its magic.

exception RetryAttemptFailed of string * int

exception RetryCountExceeded of string

You raise these exceptions as you would any exception. However, handling them is streamlined because you can use the same pattern-matching syntax as discriminated unions (more on pattern matching in Chapter 7) to not only determine which handler to use but also to bind the associated data to useful identifiers.

A generalized retry function might raise different exception types that indicate whether it should keep trying or give up depending on how many times it has tried to execute some action.

let ①retry maxTries action =

let ②rec retryInternal attempt =

try

if not (action()) then

raise <| if attempt > maxTries then

③RetryCountExceeded("Maximum attempts exceeded.")

else

④RetryAttemptFailed(sprintf "Attempt %i failed." attempt, attempt)

with

| ⑤RetryAttemptFailed(msg, count) as ex -> Console.WriteLine(msg)

retryInternal (count + 1)

| ⑥RetryCountExceeded(msg) -> Console.WriteLine(msg)

reraise()

⑦retryInternal 1

retry 5 (fun() -> false)

In this example, the retry function ① accepts two parameters. The first indicates the maximum number of attempts and the second is a Boolean-returning function to invoke. All of the work is performed within retryInternal ②, a nested recursive function that calls itself and that invokes the supplied function. If the supplied function returns false, it raises either a RetryCountExceeded exception ③ or a RetryAttemptFailed exception ④. When RetryAttemptFailed is raised, the handler ⑤ writes the exception message to the console before calling theretryInternal function again with an incremented counter. If a RetryCountExceeded exception is raised, the handler ⑥ writes the exception message to the console and then reraises the exception for another handler to process. Of course, this process has to start somewhere, so we make the initial call to retryInternal ⑦ with 1 to indicate the first attempt.

This syntactic simplicity does come at a cost. Despite RetryAttemptFailed and RetryCountExceeded being standard exceptions, you’ll really want to keep them isolated to your F# assemblies because consuming them in other languages can be cumbersome. The associated data is defined as a syntactic tuple, so the individual values don’t get descriptive names in the compiled code; instead, the values are assigned “useful” generated names like Data0 and Data1. To confuse matters even more, the compiler has no way of knowing which, if any, of the associated data items should be treated as the exception’s Message property, so the default message (from the base exception class) is used.

String Formatting

You probably guessed that the tried and tested Console.Write, Console.WriteLine, and String.Format methods are perfectly acceptable in F#. When you need absolute control over formatting, you’ll have to use them. As capable as they are, though, they don’t take advantage of all that F# has to offer.

F# has its own string formatting capabilities that you can use with the printf, printfn, and sprintf functions, among others. Why did the language designers choose to build another formatting mechanism when .NET’s built-in mechanism is already so capable? Because F#’s native formatting capabilities tie into the compiler better than the traditional ones. For one, the tokens used within the F# format strings are generally easier to remember than the format strings in the core methods, but that’s not the primary advantage. What really distinguishes the F# formatting system is that it ties in to the F# type inference system! The compiler will verify that each token has a matching value and that each supplied value is the correct type for the corresponding token!

To simply format a string, you could use the sprintf function. For example, here’s how to quickly format a basic integer value.

> sprintf "%d" 123;;

val it : string = "123"

Of course, integers aren’t the only data type you can format in this manner. Table 3-5 shows a list of common format string tokens.

Table 3-5. Common Format Tokens

Token

Description

%A

Prints any value with F#’s default layout settings

%b

Formats a Boolean value as true or false

%c

Formats a character

%d, %i

Formats any integer

%e, %E

Formats a floating-point number with scientific notation

%f

Formats a floating-point number

%g, %G

Shortcut for %e or %f; the more concise one will be selected automatically.

%M

Formats a decimal value

%o

Octal

%O

Prints any value by calling its ToString method

%s

Formats a string

%x

Lowercase hexadecimal

%X

Uppercase hexadecimal

To ensure that the formatted text is at least a certain number of characters wide, you can include an optional width value after the %. (The default formatter won’t truncate your data unless the format token explicitly allows it.) For example:

> printfn "%5s" "ABC";;

ABC

> printfn "%5s" "ABCDEFGHI";;

ABCDEFGHI

You can combine several modifiers with the tokens for a little extra flexibility in formatting, as listed in Table 3-6.

Table 3-6. Numeric Format String Modifiers

Modifier

Effect

Example

Result

0

When used in conjunction with a width, pads any extra space with zeros

"%010d"

"0000000123"

-

Left-justifies the text within the available space

"%-10d"

"123 "

+

Prepends a positive sign if the number is positive

"%+d"

"+123"

(space)

Prepends a space if the number is positive

"% d"

" 123"

You can also combine several modifiers within a single token. For example, you could use the token %+010d to print a number front-padded with zeros and the plus (+) sign.

Type Abbreviations

Type abbreviations allow you to define a new name for an existing type just like the core data types are exposed to F#. It’s possible to do something similar in C# with the using directive, but F#’s type abbreviations allow you to use the name throughout your library (after its definition, of course) instead of only within a single file.

You define type abbreviations with the type keyword, an identifier, and the type. If you wanted to refer to System.IO.FileStream as fs, you’d use the following:

type fs = System.IO.FileStream

Comments

When you want to describe what a particular piece of code is doing, use comments. There are three ways to comment your code in F#: end-of-line comments, block comments, and XML documentation.

End-of-Line Comments

End-of-line (or single-line)comments begin with two slash characters (//). As their name implies, they include everything until the end of the line. These comments frequently appear on a line of their own but can also appear at the end of a line.

// This is an end-of-line comment

let x = 42 // Answer to the Ultimate Question of Life, The Universe, and Everything

Block Comments

Block comments are delimited with (* and *) and are typically used for comments that need to span multiple lines.

(* This is a block comment *)

(*

So is this

*)

You can also use block comments in the middle of a line of otherwise uncommented code.

let x (* : int *) = 42

Be careful with what you include in block comments because the compiler treats their content as strings, verbatim strings, and triple-quoted strings. If you happen to include a quotation mark (or three consecutive quotation marks), the compiler will insist that you’re beginning a string and will produce a syntax error if it doesn’t find the corresponding closing token.

(* "This is ok" *)

(* """This is not *)

XML Documentation

Like the other .NET languages, F# allows XML documentation comments with triple slashes (///). These comments are technically just a special case of end-of-line comments where the compiler retains the contents to build an XML document that can eventually serve as documentation.

A complete discussion of XML documentation comments is beyond the scope of this book, but keep in mind that comments are useful for documenting your API. At a minimum I recommend using them on all of your application’s public and internal types and members.

Your XML documentation comments will usually include a few elements like summary, param, and returns. summary elements briefly describe the documented code, param elements identify and describe individual function or constructor parameters, and returns elements describe a function’s return value.

You might document a function that calculates some circle measurements based on its radius like this:

/// <summary>

/// Given a radius, calculate the diameter, area, and circumference

/// of a circle

/// </summary>

/// <param name="radius">The circle's radius</param>

/// <returns>

/// A triple containing the diameter, area, and circumference

/// </returns>

let measureCircle radius =

let diameter = radius * 2.0

let area = Math.PI * (radius ** 2.0)

let circumference = 2.0 * Math.PI * radius

(diameter, area, circumference)

Even if you don’t intend to distribute the resulting XML file, XML documentation comments can help you by surfacing information about the documented types and members through IntelliSense. In Figure 3-2 you can see the summary from the preceding example included in the tool tip displayed when you hover the mouse over the measureCircle function in Visual Studio.

XML documentation in IntelliSense

Figure 3-2. XML documentation in IntelliSense

There’s a shortcut for XML documentation comments. When you’re writing only a summary, you can simply use the triple slashes and omit the tags. Here’s the summary in the previous example written using the shortcut:

/// Given a radius, calculate the diameter, area, and circumference

/// of a circle

let measureCircle radius =

-- snip --

As you can see, when your comment is too long for a single line, you can write it on multiple lines as long as each line begins with triple slashes.

Summary

In this chapter, we’ve explored some of the fundamental concepts of the F# language. You’ve seen the problems that can arise from mutable data and how F#’s default immutability, type inference capabilities, and explicit opt-in approach for valueless data can help you write more robust, fault-tolerant code. You’ve also learned how F# supports the core CLI types and other base capabilities of the .NET Framework like enumerations, generics, exception handling, and string formatting.

What really makes F# stand out as a viable language for your projects, though, is how it expands upon so many concepts even at this fundamental level. Constructs like use bindings that dispose of objects without requiring additional nesting levels, exception handlers that return values, and string-formatting functions that tie into the compiler can have an immediate, positive impact on your productivity.

In the next chapter, we’ll build upon these concepts with a look into F#’s object-oriented capabilities. We’ll see how the concepts introduced here can help you quickly develop complex libraries while keeping you focused on the problem rather than the compiler.