F# walkthrough: looking under the covers - F# Deep Dives (2015)

F# Deep Dives (2015)

Appendix. F# walkthrough: looking under the covers

Tomas Petricek

The purpose of this book is not to teach you F# from scratch. There are many other books that do that, including our own Real-World Functional Programming (Manning, 2009). Instead, this book is a collection of interesting uses of F# written by experts from the industry who are using F# to write production code and who often have unique experience that’s worth sharing.

Even after using F# for some time, many people, myself included, may be surprised by some aspect of F# or a combination of features that they haven’t seen before. In this appendix, we’ll walk through some of the F# basics, but we’ll also look at them from a more interesting perspective. You’ll learn how they fit together beautifully. As you’ll see, F# is remarkably simple.

Simplicity through compositionality

Most of the power of F# is achieved by composing simple ideas. For someone coming from a language such as C#, which was my background when learning F#, this isn’t obvious. For example, you expect that GenerateRange(0, 10) is calling a function taking multiple arguments, whereas in F#, the argument (0, 10) is a tuple and you’re just calling a function—the language doesn’t need functions of multiple arguments.

Why is this a good thing? Say you also had a function called NormalizeRange that takes a pair of numbers and returns a new pair, making sure the first one is the smaller one and the second one is the larger one. In C#, you’d probably have to define a type Range at this point. Alternatively, you can return Tuple<int, int>, although that’s not idiomatic. In C#, you can call the two functions as follows:

var corrected = NormalizeRange(10, 0)

var range = GenerateRange(corrected.Item1, corrected.Item2)

You call NormalizeRange with two arguments and get back a tuple. To call Generate-Range, you need to extract the first and second elements of the tuple using Item1 and Item2 properties. In F#, you don’t need that; GenerateRange takes a tuple as an argument, andNormalizeRange returns a tuple, so you can just write this:

let range = GenerateRange (NormalizeRange (10, 0))

Compositionality is one of the key principles in F#, and you’ll see a number of examples throughout this appendix that demonstrate its power. But F# is also a pragmatic language that aims to interoperate with .NET libraries and the outside world (through type providers), and so it also supports ordinary .NET members that take multiple arguments as in C#. Still, understanding the compositionality will demystify many F# constructs.

The fundamental building block from which programs are built in F# is an expression, so let’s begin by looking at expressions in the next section.

From expressions to functions

Languages like C++, Java, and C# distinguish between statements and expressions. A statement is a command that performs some action and typically changes the state of the memory (for example, assignment) or transfers the control flow (for example, a return statement). An expression is a computation that can be evaluated and gives a result.

Starting with expressions

In functional-first programming, whole programs are written as expressions that can be evaluated and yield a result, so expressions are all you need. F# isn’t purely functional, meaning you can easily do useful things like printing to the console, but even that is actually an expression. Consider the famous Hello world example in F#:

The snippet shows the result of entering the command in F# Interactive (FSI). In most editors, you can do that better by writing the expression (the first line without the > symbol) and pressing Alt-Enter or Ctrl-Enter.

When entered in FSI, the expression printfn "Hello world" is evaluated to a value. During the evaluation, the printfn function prints the message to the console, so this is shown on the next line . Then it continues evaluating and returns a result, which the FSI console prints on the last line .

The printfn function is useful for its side effects and not for its result. For such functions, F# provides a type called unit, which doesn’t represent any information. The only possible value of type unit is written as (), and you can also see this as a tuple containing zero elements. So, the line val it : unit = () means the expression has been evaluated to a value () of type unit, which FSI assigned to a temporary variable named it. If you have an expression that returns unit, you can sequentially compose it with other expressions using the semicolon operator, or by using the newline separator as shown in listing 1.

Listing 1. Sequencing expressions

Listing 1 shows the same expression written in two possible ways. First you write it using an explicit semicolon, and then you write it using indentation. In most cases, using indentation is the recommended way, but the first approach makes it more obvious what’s going on.

As you’ve just seen, the first expression (printfn "Thinking...") returns the only value of type unit. The sequencing operator ; ignores the value (because it doesn’t represent anything useful!) and evaluates the second expression (in this case, 42) and then returns the result of the second expression .

When you see the second version of the code , you could view it as a code block that consists of a print statement followed by a return statement, except that you don’t need the return keyword . But this isn’t how things work! Under the covers, the code is two expressions that are sequentially composed using ;.

Getting the indentation right

F# is indentation-sensitive, which means the whitespace determines the structure of code. This reduces the syntactic noise and makes your code more readable—indenting code properly is a good practice anyway, so why should the compiler ignore the information that the indentation gives you?

In most cases, indentation is easy to see. F# requires you to use spaces (instead of tabs) to avoid ambiguity. In some cases, getting the indentation right may be tricky. For example, when you’re typing code in FSI, the shell starts a line with >, but that isn’t added to the next line. For example:

> printfn "hi"

printfn "there"

Although the two lines don’t look aligned, the compiler sees them as aligned, because the first two characters of the first line are not your input. The best way to avoid the problem is to write most of your code in the text editor and then select and run it. In listings that show FSI sessions, we’ll add the spaces to the second (and later) lines, but note that you don’t need to type them!

The other tricky situation is when you’re selecting code in the editor and then sending it to FSI (using Alt+Enter or Ctrl+Enter). In that case, you need to select a full line. If your selection doesn’t include spaces at the beginning of the first line, but it does includes spaces on subsequent lines, the code sent to the compiler will be incorrectly indented.

The preceding sidebar clarifies how to properly indent code. This is important, because indentation affects the meaning of code, especially when it comes to writing more complex expressions. For example, the following listing demonstrates how to write a conditional expression.

Listing 2. Writing conditional expressions

You first define two values representing an incorrect range. If you incorrectly treat F# programs as C# programs without the return keyword, then you might try writing something like the first expression . If the range is wrong, you return “Wrong”; otherwise the evaluation continues and returns “Good”. As the compiler tells you , this doesn’t work.

If you write an if expression with only the then clause, then the type of the body has to be unit. This way, F# knows what to do when the condition doesn’t hold; it can do nothing and return the only valid unit value. This example tried returning a string, and the compiler said it expected a body of type unit (like printfn "Wrong").

You can also fix the code by using if as a conditional expression that returns the result of one of its two branches . Here, you return a string value “Wrong” or a string value “Good”, depending on the value of the expression lo > hi. FSI prints the result , but you could also assign it to a symbol using let (so the if expression behaves more like the conditional operator cond?e1:e2 in C#).

Exercise 1

The ; operator requires the first expression to return unit and returns the result of the second expression. See what happens when you write the following expression:

let res = ( 42; printf "Hello world" )

Exercise 2

If you’re going to apply for an F# job, you need to be able to solve the FizzBuzz problem with F#. That is, you must be able to write a function that iterates over numbers from 0 to 100. For each number, write “Fizz” if it’s divisible by 3 and “Buzz” if it’s divisible by 5. For other numbers, just output the number.

Some more syntax you’ll need is for i in 0 .. 100 do followed by a body (indented further than the for keyword); the expression a%b returns the remainder after integer division. You can also use the usual logical operators like >=, <=, =, <> (the last two test equality and inequality—in F# you don’t need double equals) as well as && and ||. It’s worth noting that && and || are short-circuiting, meaning they don’t evaluate the expression e2 in e1 && e2 or e1 || e2 when the result is clear after evaluating e1.

Exercise 3

As a brainteaser, try writing an expression that prints the message “Hello” when the variable trick is greater than 10, but do that without using the if expression. You can do this using only the F# features that we covered so far in the appendix, but you’ll need to use the sequencing of expressions and Boolean operators in a clever way. As a bonus, you can also implement FizzBuzz using the same trick!

As you can see, you can have a lot of fun by writing simple F# expressions. But if you’re writing a serious program, you need a way to reuse parts of your expressions. This is where functions come into the picture.

Wrapping expressions in functions

Functions are first-class values in F#, which means they can be treated as any other type. Even though languages like C# support functional constructs too, they still make a visible distinction between methods and delegates like Func<T1, T2>. In F#, a function type is written using the arrow symbol. For example, a function adding 1 to an integer has a type int -> int. Following is a comparison of functions and expressions, showing the important difference:

On the left , you use let as in the previous section and define a value helloResult, which is the result of evaluating an expression printfn "Hello...". As you can see, this prints the message when evaluating the value and returns a value of type unit . You wouldn’t normally write the code this way (because the value helloResult is useless), but it’s interesting to contrast this with the code on the right.

On the right , you add a parameter to the let declaration written as () after the name of the symbol. This makes it into a function of type unit -> unit . Declaring the function doesn’t print anything, but you can call it later:

The function takes a unit value as an argument, so if you want to call it, you need to create a unit value. As you already know, the only value of type unit is written as (), which explains what’s happening in the first case . Just out of curiosity, you can also evaluate the valuehelloResult . This represents the result of the previous evaluation and so prints the unit value.

One more interesting thing is the declaration of the function. You add a parameter (), which looks like you’re declaring an empty parameter list. This isn’t what’s going on! You could write the function differently:

let sayHello (u:unit) = printfn "Hello again!"

Here, you’re declaring a function that takes an argument u. The syntax (u:unit) is a type annotation that explicitly specifies the type of u to be unit. Of course, because unit doesn’t carry any information, you aren’t going to need u anywhere in the code. For this reason, the previous version used the notation (), which is a pattern specifying that the parameter should be a unit value and should be discarded. So, you can construct a unit value using the expression (), and you can also destruct a unit value using the pattern (). We’ll look at patterns in more detail in a moment, but let’s first finish the section on functions by exploring functions that take other functions as arguments.

Using functions as values

Even though you haven’t had to write a single type so far, F# is fully statically type checked. The language has a powerful type-inference algorithm that looks at the source code and infers the types of all expressions and functions. This becomes even more useful when you start writing functions that take other functions as arguments.

For example, suppose you want to write a function that will take a list of words and print them using the specified printer function. The function might print the words to a console, display them in some user interface, or even read them using a text-to-speech API. To do that, you can write the following:

> let sayEverything printer words =

for word in words do

printer word

;;

val sayEverything : words:seq<'a> -> printer:('a -> unit) -> unit

You haven’t specified any type annotations, so the type inference works out the type of the function based on the code you wrote. When doing this, F# looks for the most general type. In the previous example, this means two things:

· The type of words is inferred as seq<'a>, which is an F# alias for IEnumerable<'a>. This means the resulting function can be used on any collection.

· More interestingly, the compiler also figures out that the type of elements can be generalized and turned into a generic parameter that’s automatically named 'a.

How does the compiler know? You’re iterating over words using a for loop, so it must support iteration. But the only thing you do with individual word values is pass them to the printer function. This means the type of elements in the collection must be the same as the type accepted byprinter. Because you use printer as a function, it must be a function. Also, you use it in the body of a for loop, so it must return unit (because the body of a loop is evaluated, but its result is discarded before the next iteration). So, now you end up with a function that’s even more useful than you thought!

Note

Once you understand how type inference works, it becomes a surprisingly powerful tool. The inferred type of a function lets you do basic sanity checks of your code. For example, if the type inference inferred that your sayEverything function takes a collection seq<'a> and a function 'b -> unit, it would mean you did something wrong. You’re not passing values from the collection to the printer, because then the types would have to be the same. So, it can be an indication that you’re probably not calling the printer as you intended.

Now let’s see how you can call the function. As mentioned, you can not only use it to print strings, but also call it with a list of integers as an argument:

sayEverything (fun num -> printfn "%d" num) [1; 2; 3]

This sample uses the fun notation to create an anonymous function and use it as the printer. The body of the function spans as far as possible, so you need to wrap it in parentheses. The sayEverything function takes two arguments, which are separated by spaces. The second one is an immutable F# list containing 1, 2, and 3.

Looking under the covers

When I said that sayEverything takes two arguments, I was lying again. This is how it looks when you see it, and this is also the most useful way to think of the function, but you can read it differently too. When the compiler infers the type, it sees it with additional parentheses as follows:

sayEverything : ('a -> unit) -> (seq<'a> -> unit)

This is pretty subtle, but as I said in the introduction, this appendix isn’t an introduction to F#. Let’s see what the type means:

· sayEverything is a function that takes a function ('a -> unit) as an argument and returns a function (seq<'a> -> unit) as the result.

· The argument that needs to be specified when calling sayEverything is itself a function—that is, a printer of type ('a -> unit).

· The result is now a function that takes an argument of type seq<'a> and returns a unit value as the final result.

How can you then call this beast using sayEverything printer list? This expression can be seen as parenthesized too: (sayEverything printer) list. So, you first call sayEverything with a printer as an argument. What you get back is another function that already knowshow to print but doesn’t know what to print. You specify that in the second call by passing it a list.

Most of the time, you don’t need to worry about the fact that functions of multiple arguments return other functions as results. But there’s one situation where this becomes extremely useful. When you previously used (fun num -> printfn "%s" num) as the printer, you needed to create a function that takes a number and prints it. You did that by creating an anonymous function taking num and passing num to another function. There’s an easier way to do this:

sayEverything (printfn "%s") ["hello"; "word"]

In this example, you’re calling printfn with a single argument, "%s". The compiler understands this and infers that the call needs one more argument of type string. Rather than passing a variable as an argument, you use this expression, because it has the type you need: string -> unit. You can check this yourself in FSI by entering printfn "%s" without an argument and looking at the type of the result.

You might be wondering why you use sayEverything so that it takes the function as the first argument and the list as the second one. This isn’t an arbitrary choice. It means the function can be nicely called using the pipelining operator. To demonstrate this, let’s write a fancier printer that SHOUTS the words in uppercase:

["hello"; "word"] |> sayEverything (fun s ->

printfn "%s" (s.ToUpper()) )

The pipelining operator (|>) takes the argument on its left side (here a list of words) and passes it to the function on the right. On the right, you have a function seq<string> -> unit, which you get by passing a printer to sayEverything. As you can see, you use the fact thatsayEverything is a function that returns a function again. This is called partial function application.

Pipelining operator

In F#, you can define your own custom operators. Interestingly, the pipelining operator isn’t a built-in feature, but you can define it yourself using just 12 characters:

let (|>) x f = f x

The operator name needs to be wrapped in parentheses. It takes x and f as the left and right arguments and calls the function f with x as the argument.

Another interesting thing about the pipelining operator is that it works well with type inference. In F#, type inference propagates information from left to right. This means that in the previous example, the compiler looks at the list and sees that it contains strings. When you’re writing the function and type s followed by a dot, you’ll get an autocomplete list with all available members. But if you write the call without the pipeline, you’ll have to specify the type of s explicitly using a type annotation written as (s:string).

Exercise 4

The inferred type of functions in F# can tell you a lot about what the function does. Sometimes it’s more useful than the name of the function. To explore how the types work, try writing functions that have the following types:

mysteryFunction1 : ('a -> bool) -> seq<'a> -> int

mysteryFunction2 : bool -> 'a -> 'a -> 'a

mysteryFunction3 : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c

Make sure your solutions are correct by entering them in FSI and checking the inferred type. All three functions have useful implementations. Finding a useless implementation is much harder than finding the useful one!

So far, you’ve used basic expressions and primitive types. In any practical F# application, you’ll need to use more complex data types to represent your data. In the following section, we’ll look at functional types (and we’ll cover object-oriented types after that).

Constructing and destructing data

Similar to functions, the F# approach to representing data structures is “less is more.” There are two basic ways of constructing data structures. One is to group multiple values in a single value (for example, a person has a name and age), and the other is to combine multiple different choices (for example, a preferred contact is a phone number, email, or postal address). Let’s start by looking at the first option.

Representing composite values with tuples

The basic way to combine multiple values into a single one is to use a tuple. The following snippet creates a value person that represents a person with a name and an age:

> let person = "Alexander", 3;;

val person : string * int = ("Alexander", 3)

F# uses the standard .NET type Tuple<string, float>, so you can think of this code as syntactic sugar for calling the constructor. F# also uses a shorter type alias for the name, so the inferred type is printed as string * int. Tuples are immutable, so how can you do something useful with the tuple? The following function increments the age of a person by 1 and returns the new incremented value as the result.

Listing 3. Incrementing value in a tuple

The function in listing 4 takes a single parameter named person . To get values from the tuple, you use the let construct again. In its most general form, the let construct has this structure:

let <pat> = <expr> in <expr>

The in keyword is optional when you use indentation, so you wouldn’t typically see it in F# code. It means let has to be followed by some other expression whose result is returned. The interesting thing here is <pat>; this represents a pattern. You’ve already seen two patterns. A symbol like person is a pattern that assigns any given value to a variable (because variables are immutable, symbol is a better name in F#). The other pattern you’ve seen is (), which takes a unit value and ignores it. Now you’re using a pattern that takes a two-element tuple and assigns its two values to two symbols. You can also nest patterns and write the following:

let _, age = person

The pattern _ ignores any value, so this expression ignores the name and assigns the age of a person to a symbol, age. You can also write the following pattern:

let name, 3 = person

This is an incomplete pattern, meaning it won’t match all possible person values. In particular, it accepts only people of age 3! This isn’t a useful example, but it becomes useful when combined with match, which lets you choose between multiple patterns.

One more interesting thing about listing 4 is that the inferred type is again more general than you might’ve expected . The code increments the second element of the tuple, but it copies the first element and doesn’t restrict its type to string, which is how you happen to use the function in the following code:

> incAge person;;

val it : string * int = ("Alexander", 4)

> incAge ("Tomas", 29);;

val it : string * int = ("Tomas", 30)

In the first call, you pass to incAge an existing value that was created earlier. This works as expected, because person has a type string * int, which can be unified with the required type 'a * int. The function can also be called by creating a tuple explicitly as an argument. Thislooks as if you were calling a function with two arguments, but as you can now see, it’s just creating a tuple and calling a function with a single argument.

For the sake of completeness, it’s worth pointing out that the first version of incAge is unnecessarily long. You don’t need to take person as an argument to decompose it on the next line. Patterns like name, age can appear directly in the argument list of a function, although this time the pattern needs to be parenthesized:

> let incAgeShort (name, age) =

name, age + 1;;

val incAgeShort : name:'a * age:int -> 'a * int

Tuples are extremely useful when you’re working on a small scale, but if you have a larger number of composed fields, you’ll find it useful to name them. This is where records come into the picture.

Representing composite values with records

You can see records as the simplest possible kind of objects. They contain a number of fields that store data. In functional-first domain modeling, you’ll often start by focusing on the data structures and records provide one key component for that. You can use records to model composite data types before you need to start thinking about the operations that you’ll implement, because those can be written later as separate functions.

Unlike tuples, records need to be named and declared in advance. To make listing 5 a little more interesting, I added an overridden ToString member to the record.

Listing 4. Record with custom ToString method

The record you create here is a simple immutable type. When you see it from C#, it looks like an ordinary .NET class with a constructor (taking Name and Age) and two read-only properties. As you’ll see in the next section, records are just one small step away from the object-oriented programming features of F#. But there’s one nice feature of records that’s useful when you need to create modified values while keeping immutability: the with construct.

The following snippet shows a simple function that increments the age of a person, this time using the Person record:

> let incAgeRec person =

{ person with Age = person.Age + 1 }

;;

val incAgeRec : person:Person -> Person

The with construct is a special F# language feature that works only with records. It creates a record that’s exactly the same as person, but the value of Age is calculated using the expression person.Age + 1. As you can see, the inferred type is Person -> Person. For records, the F# compiler determines the type using the fields you’re accessing. This makes simple programming with records nicer, but it makes it hard to write programs that use records with field names that appear in multiple records. For public APIs of more complex systems, it’s usually better to use objects. Before moving to our next topic, let’s have a quick look at using the function you just wrote:

To create a record, you write field assignments in curly brackets . The compiler makes sure you specify a value for all fields of the record, which means you don’t run into problems with uninitialized fields. Then you can use it as an argument to your function and access its fields as well as its methods, including the overridden ToString .

Records are simple types, so we haven’t spent much time discussing them. The real power of records comes when you use them together with tuples and discriminated unions for domain modeling.

Representing choices with unions

Records let you define new types by combining values of several other simpler types. In OOP terms, they correspond to classes with multiple fields. Discriminated unions let you define new types by choosing between values of several other simpler types. In OOP, this is usually represented by class hierarchies.

Domain modeling

For example, suppose you want to represent information about customers. Every customer has a name (which they choose when they register) and a shipping address. For this part of the model, you need a record (to combine the two values). The shipping address can be empty (before the customer proceeds to checkout) or it can be local (with an address and postcode) or international (in which case, you track the country).

To model the address, you can use a discriminated union (to provide a choice between three possible values of the address). You can see the full definition next.

Listing 5. Domain model for customers with address

If you haven’t seen discriminated unions before, you can think of the type Address as a declaration of four separate classes (but on just four lines). The type name Address would be represented as an abstract base class, and the three type constructors would each correspond to a single inherited class with additional fields.

The great thing about F# type declarations is that they’re direct encoding of our informal description of the domain model. We said the address is empty, local, or international, and this is exactly what the code says. In F# 3.0, this is even nicer thanks to the ability to add names to the fields of each case .

Extensibility

Discriminated unions are similar to class hierarchies, but there’s an important difference. With class hierarchies, you can easily add a new derived class, but adding new functionality is hard (you need to add a virtual method and modify all classes). With discriminated unions, you can easily add new functions (using pattern matching), but adding new cases isn’t as easy (you have to modify all functions).

In functional-first programming with F#, choosing discriminated unions as your default option is a good idea for their simplicity. But as you’ll see later, you can also define and use interfaces and get the object-oriented extensibility model.

In F#, you’ll often start by defining the domain model (as you did in listing 6), because it lets you understand the big picture of your problem. Also, the definitions can often be explained even to nontechnical collaborators, who can verify and find holes in your understanding of the domain.

Once you have the domain model, the next step is to add some functionality. For example, let’s say you want to add a function that calculates shipping price based on the address. The code in listing 7 implements the following logic:

· Shipping within New York is free.

· Shipping to other parts of the United States costs $5.

· Shipping to Canada costs $15.

· Shipping to all other countries costs $25.

Listing 6. Calculating shipping price

As with the domain model, the encoding of the pricing logic using pattern matching directly follows the rules specified earlier. The match construct lets you write a number of cases (separated by |) that consist of a pattern followed by -> and an expression that will be executed if the pattern matches.

The key idea is that the patterns can be incomplete. You saw incomplete patterns earlier, and now you can see why they’re useful. The match construct tests the cases from top to bottom, looking for the first one that matches. For example, listing 7 first tests whether the postcode of a local address starts with NY before the case that handles all other local addresses.

The example also uses one new feature of F# 3.0. When pattern matching on a union case with named fields, you can use the names of the fields. For example, the pattern Local(postcode=p) matches a local address and extracts the value of the postcode into a new variable named p. This not only makes the code more readable, but also makes it more extensible—adding more fields to the case won’t break the code. In contrast, the pattern International("Canada") doesn’t use the name and relies on the structure (the name of the country is the first and only field).

Exercise 5

Suppose you want to distinguish between personal and business addresses. To do that, modify the Address type and add another case, called Business, storing the company name, street, and postcode.

After extending Address, look at shippingPrice and try calling it with a business address as an example (you should see a warning). The shipping price for businesses is $10, except for “Manning Publications,” for which the shipping should be free.

Exercise 6

If you want to turn this example into an online retail system, you must add order management. As an exercise, define an OrderStatus discriminated union that models the following three cases:

· Received —The order has been created and received. The case carries the date when the order has been received as System.DateTime.

· Dispatched —In this state, you’ll keep the date when the order has been dispatched (System.DateTime) and the shipping company used (as a string).

· Delivered —After the order is delivered, you’ll keep the name of the person who signed for the delivery (as a string).

Next, write a function that checks whether the order has been lost. An order has been lost if it was dispatched 14 or more days ago. It’s also considered lost if it was received but hasn’t been dispatched for more than 7 days. A delivered order is never lost.

If you look back at listing 7, you can see that the code can throw an exception when the parameter represents an address that hasn’t been set. In C#, you might represent a missing address with null, so your code could easily fail unexpectedly (because of a forgotten null check). F# types don’t have null as a valid value, so this isn’t going to happen:

The need for an explicit check using pattern matching makes the code more error-prone, but the fact that the shippingPrice function can still throw an exception suggests that your domain model isn’t entirely appropriate. In the next section, you’ll change it slightly to avoid this problem.

Improving the domain model with options

In the previous section, the NotSet value was a valid value of type Address. As a result, a function that takes a value of type Address needs to be able to handle the NotSet case—even though it doesn’t carry information about the shipping address, it is a valid address.

One of the key principles of domain modeling in F# is that invalid states shouldn’t be representable. Following this idea, valid addresses should be only local and international. Whether the customer has an address or not is a separate concern. The code in listing 8 is a modified version of the previous snippet. The Address type now has only Local and International cases. The fact that the customer may or may not have an address is expressed by making the Shipping field optional using the option type.

Listing 7. A better domain model

As mentioned already, the key change is that Address now represents only valid addresses. The Shipping property now has a type Address option . Here, you’re using the prefix notation rather than using the more common notation option<Address> (the prefix syntax is mainly used for built-in F# types).

In C#, you could set the field to null to represent the fact that the field is missing. If you did that, you could easily forget to add a null check and run into trouble. The option type makes this information explicit so the F# compiler can enforce that you always handle missing addresses correctly.

For example, say you want to call your new shippingPrice function on a Shipping property of a customer. To do that, you need to use pattern matching, handling both the Some case (the value is present) and the None case (the value is missing):

The type of customer.Shipping is Address option, whereas shippingPrice takes Address, and so the compiler forces you to use pattern matching. The first branch handles the case when the address is available, and it assigns the carried Address value to a new variable named addr that you can use later. The second is a catch-all pattern that handles all cases you haven’t covered before; you tell the user that they need to enter an address first.

There are certainly more things to be said about designing functional types, and they’re used extensively throughout this book. Although F# is functional-first (meaning functional style is the default), it also supports object-oriented concepts, emphasizing the good parts of the object-oriented paradigm.

It’s just elementary school mathematics

When creating domain models, you have several ways to model the same thing. But how do you know whether two ways of modeling a domain are equivalent or whether one can express different states than the other?

It turns out you can do a lot by using elementary school mathematics. To make this example simpler, define three type aliases—for name, phone number, and email address—all represented using a string:

type Name = string

type Phone = string

type Email = string

Say you want to model a choice of person with a name and a phone number and a person with a name and an email address. One way to do this is to write the following:

type Person1 =

| WithPhone of Name * Phone

| WithEmail of Name * Email

Another way to model this is to say that a person is a record (or tuple) that always contains a name together with either a phone number or an email. In F#, this could be expressed like this:

type Contact = Phone of Phone | Email of Email

type Person2 = Name * Contact

The interesting question now is, do the types Person1 and Person2 represent the same thing? In F#, the tuple type is written using *, and theoreticians call it the product type. F# discriminated unions require explicit type definitions, but for now, let’s write them using +, because theoreticians call them sum types. So for example, the Contact type could be written as Phone + Email. If you expand the definitions for Person1 and Person2 using this notation, you get this (in pseudocode):

type Person1 = (Name * Phone) + (Name * Email)

type Person2 = Name * (Phone + Email)

The choice of + and * for the type constructors is intentional. It turns out that many properties of addition and multiplication also work for tuples and discriminated unions. For example, the distributive property says that

a(b + c) = ab + ac

Now look at the Person2 and Person1 types. They’re exactly the two sides of the distributive property. Using basic mathematical reasoning, you can prove that the two types are the same. The correspondence goes further than this (for example, the unit type behaves as the number 1), so if you want to learn more, check out my blog post on “Reasoning about functional types” at http://tomasp.net/blog/types-and-math.aspx/.

Object-oriented programming: the good parts

Being a first-class .NET and Mono language, F# supports all the object-oriented features of the common language runtime (CLR). This means you can use libraries that require you to inherit from the base classes they provide, implement interfaces, override methods, expose .NET events, and so on.

But OOP is also useful on its own. It provides a nice way to encapsulate public functionality in libraries (be it for F# or for other CLR languages). Using interfaces, you also get a different extensibility model that’s complementary to the one provided by discriminated unions.

From functions to interfaces

Say you want to check whether customer information is valid before accepting an order. You may want to check that the address is set and that the name matches some basic sanity checks. It’s likely you’ll want to add more validation checks later on. In the simplest form, you can represent customer validator as a function:

type CustomerValidator = Customer -> bool

This defines an F# type alias, meaning when you write CustomerValidator in a type annotation or definition, the compiler will interpret it as a function from Customer to bool. Using this representation, you can check whether the customer is valid, but you don’t know what error message you should report. Using the object-oriented approach, you can use an interface with a Validate method and a Description property:

When used in a functional way, interface types are a surprisingly great fit for functional programming. As you can see here, interfaces are a natural progression from functions. A function can be called on some input. An interface groups multiple functions (or other values) together in a single type.

In this example, you have a method Validate and a property Description. The method is declared using the F# function type, but it will be compiled into an ordinary .NET method and can be easily implemented in C#. The property you’re defining here is read-only (which is the default in F#), but you can also require a setter by writing with get, set at the end of the declaration.

Implementing interfaces

Let’s stick to the idea that interface types are like function types. When you have a function type like Customer -> bool, you can implement it in two ways. The first way is to use an anonymous (lambda) function (such as fun c -> true). The second way is to write an ordinary named function using let binding that has the matching signature.

When implementing interfaces in F#, you have the same two options. You can use object expressions, which are like anonymous functions, and you can use named type definitions. For example, listing 8 shows an object expression implementing a validator that ensures that the address is provided. This approach is often the easiest way to implement interfaces in F#.

Listing 8. Validating addresses using object expressions

The object expression syntax consists of new IInterface with, followed by a number of member declarations, all wrapped in curly brackets. When you use this notation, the compiler generates a class implementing the interface behind the scenes. But you don’t need to know anything about the class, because the type of hasAddress is ICustomerValidator.

Inside the object expression, you can use the member keyword to implement members of the interface. The same keyword works for properties as well as methods . F# makes it easy to write read-only properties. If you needed a getter and a setter, you can use the notation member x.Foo with get() = ... and set(value) = ....

Before looking at how to use the validators, let’s create one more: a validator that ensures that the name of the person is longer than a specified length. But you want to be able to easily change the required length. For this reason, you’ll use a named type declaration. Listing 10 shows a typeNameLength that implements the ICustomerValidator interface and has an additional mutable property, RequiredLength, that specifies the length of the customer name.

Listing 9. Validating the name using object types

The easiest way to declare classes in F# is to use the implicit constructor syntax . This means the name of the type (here NameLength) is followed by a list of constructor parameters in parentheses. In this example, the constructor doesn’t take any parameters, but if it took some, their values would be available in all the members of the class (the compiler automatically generates private fields for them).

Next, you define a property, RequiredLength . F# 3.0 supports automatically implemented properties using the member val notation, meaning it generates a backing field for the property and adds a getter and a setter. F# avoids uninitialized values, so you need to provide a default value for the property.

Finally, the declaration includes an implementation of the ICustomerValidator interface . Here, you’re using explicit implementation (similar to explicit interface implementations in C#), which means a value of type NameLength can be treated as a value of the interface type, but it doesn’t automatically expose all the members of the interface.

Note

As of this writing, 3.0 was the most recent version of F# available, but the plans for future versions include support for implicit interface implementations. This means in versions after 3.0, you’ll most likely be able to implement interfaces implicitly by writing inherit ICustomerValidator and providing members of the right types in the type declaration.

Now that you have two sample validators, let’s briefly look at how you can do something useful with them. The FSI session in the next listing creates a list of ICustomerValidator values and then runs all of them on a given customer, printing the descriptions of failing validators.

Listing 10. Programming with objects interactively

The first code block creates a list of validators. When you create an instance of NameLength, you use the property-initialization notation to set the required name length to 5 . The resulting variable, v2, has a type NameLength. When creating the list, you want to get a list ofICustomerValidator values, so you need to cast the value to the interface using the :> operator . This process is called upcasting, and it means a type cast that always succeeds (like casting a class to an interface it implements). F# doesn’t, in general, automatically perform upcasts except for a few cases—such as when you’re calling a method with an object as an argument. It’s also good to know that if you want to write a cast that may fail, you’d need the downcast operator, :?>.

Once you have a list of validators, using it is easy. The last block of code iterates over all the validators using the for loop, checks whether they fail by calling the Validate method, and then, in case of failure, prints the description.

Exercise 7

First, implement a validator for the postcode field in local addresses. To be a valid US postcode, the string needs to start with two letters, followed by an optional space and a five-digit number. Implement the validator using object expression.

Next, suppose you want to provide more flexibility and allow the user to specify the postcode format using regular expressions. Implement the AddressRegexValidator class that has a mutable property FormatRegex and checks that the postcode of an address matches the specified regex.

Exercise 8

Recall the order status discriminated union that you defined in exercise 6. Let’s say that you now want to implement an extensible system for handling orders (this may not be the best approach, but it makes for a fun exercise!).

Your first task is to define an interface, IOrderShipper. Given an Address, the shipment handler decides whether it supports shipping to the specified address (USPS supports only local addresses, but FedEx can ship anywhere). Then it should provide a method that takes Address andOrderStatus as arguments and returns the new OrderStatus. Given a received order, it should return a dispatched order with the current date and the name of the company used.

As an example, implement a shipping handler for USPS and FedEx. Then write a function that processes an order, finding the first shipping handler that’s capable of handling the address and using it to get the new order status.

As mentioned earlier, F# supports most of the object-oriented features of the CLR, so we can’t cover all of them in a single chapter. Rather than trying to cover all possible features, let’s focus on one object-oriented feature that’s emphasized in F#: composition.

Composition over inheritance

Inheritance is an important part of OOP, but it suffers from a number of issues. One such difficulty is that by inheriting from another class, your class is inheriting all the functionality and all the complexity of the parent class. This means inheritance can only make your types more complex.

This is one of the reasons for the principle of composition over inheritance. This principle is encouraged in functional-first programming with F#. Moreover, F# provides a number of features that make it easy to compose other objects.

For example, let’s say you want to have a way of composing multiple validators into a single one. Given a sequence of validators, the new validator succeeds only when all of the given validators succeed. The description is a string with descriptions of all the composed validators. The next listing implements this idea as a function using object expressions.

Listing 11. Composing validators using object expressions

The listing defines a function that takes a value of type seq<ICustomValidator> as an argument (where seq<'T> is an alias for IEnumerable<'T>). It returns a single composed validator of type ICustomValidator.

To create the resulting validator, you use an object expression that creates a new implementation of the ICustomerValidator interface. The implementation is simple. The Description property concatenates all descriptions , and the Validate method uses Seq.forall to check that Validate methods of all composed validators return true.

Note

If you look just at the number of keywords available in object-oriented languages like C#, you can see that OOP is more complex than you might think. F# supports the majority of the OOP features required to interoperate with .NET, and it’s impossible to cover all of them in a few pages.

Even though interoperability with .NET is an important reason for using object-oriented features in F#, the language doesn’t support OOP just for interoperability. Object-oriented features are useful for structuring functional code, and we looked at some of the F# features that make it easy to use the good parts of object-oriented ideas.

Summary

This appendix serves as an F# introduction, but it’s not the usual F# tutorial. Rather than looking at the features one by one, I showed you interesting examples that can offer deep insight into how F# works and how to think about it.

The recurring theme in this chapter has been composition. Composition is perhaps the most important concept in F#. At the expression level, you’ve seen that F# programs are composed from simple expressions. Even seemingly imperative features like printing are expressions that return theunit value as the result.

At the next level, we looked at domain modeling using functional types. Again, the type system is based on composition: you can compose the domain model from primitive types using tuples, records, and discriminated unions. These give you two basic forms of composition. You can group multiple types using records and represent a choice using discriminated unions. Finally, we looked at some of the object-oriented features of F#. Again, the focus was on composition and using interfaces, which fits extremely well with the nature of F#.

About the author

Tomas Petricek is a long-time F# enthusiast and author of the book Real-World Functional Programming, which explains functional programming concepts using C# 3.0 while also teaching F#. He is a frequent F# speaker and does F# and functional training in London, New York, and elsewhere worldwide.

Tomas has been a Microsoft MVP since 2004, writes a programming blog at http://tomasp.net, and is also a Stack Overflow addict. He contributed to the development of F# during two internships at Microsoft Research in Cambridge. Before starting a PhD at the University of Cambridge, he studied in Prague and worked as an independent .NET consultant.