In this article by Kevin Ashton, the author of the book F# 4.0 Programming Cookbook, you will learn the following recipes:
- Working with optional values
- Working with tuples and pattern matching
(For more resources related to this topic, see here.)
Working with optional values
In 2009, Tony Hoare, the creator of the ALGOL W programming language, called the concept of null reference his "billion-dollar mistake". Thousands of man-hours are lost each year due to bugs caused by null references. Many of these errors are avoidable, although not all developers are aware of it.
The Option type, and working with it, is what this recipe is all about. This recipe covers a few different ways of using the option type, although all of the different methods shown can exist within the same script file.
Getting ready
Make sure that you have a text editor or IDE, and that the F# compiler and tools are installed on your system.
In Visual Studio versions 2012 and 2013, F# is installed by default. If you are running Visual Studio 2015, you will need to make sure that you select Visual F# tools during the installation phase.
The code in this example expects that you have a file named Alphabet.txt located in the same directory as your source code. This file consists of the letters a to z, each on a single line (with no lines before or after).
How to do it…
- Open a new F# Script file.
- In Visual Studio, select File | New | File
- Select F# Script File
- Enter the following code:
open System
open System.IO
let filePath =
Path.Combine(__SOURCE_DIRECTORY__,"Alphabet.txt")
let tryGet a = let lines = File.ReadAllLines(filePath)
if a < 1 || a > lines.Length then
None
else
Some(lines.[a-1])
let printResult res = let resultText = match res with
| None -> "No valid letter found."
| Some str -> sprintf "The letter found was %s" str
printfn "%s" resultText
- Select the code that you have entered and press ALT + ENTER (in Visual Studio, this will send the highlighted lines of code to the F# Interactive (FSI) evaluation session).
- The FSI session should display:
val filePath : string =
"C:DevelopmentPacktBookChapter1Alphabet.txt"
val tryGet : a:int -> string option
- To test this code, enter the following and send the result to FSI session:
let badResult1, goodResult, badResult2 = tryGet 0, tryGet
5, tryGet 27
- The FSI session should display:
val goodResult : string option = Some "e"
val badResult2 : string option = None
val badResult1 : string option = None
- Now, enter the following code and send it to the FSI session:
printResult badResult1
printResult goodResult
- The FSI session should display:
No valid letter found
The letter found was e
- Here, we show a different way of using the option type. In this case, it is used where there might not be a valid result from an operation (in the case of dividing by zero for instance). Enter the following code and send the result to the FSI session:
let inline tryDivide numerator denominator = if denominator
= LanguagePrimitives.GenericZero then
None
else
Some(numerator / denominator)
This method is defined as an inline method because that allows it take advantage of F#'s automatic generalization. If the inline keyword were to be omitted, the compiler would assume that this method only works on integer types. Notice also the use of LanguagePrimitives.GenericZero; this allows for this function to work on any type that defines a static get_Zero method.
- The FSI session should display:
val inline tryDivide : numerator: ^a -> denominator: ^b ->
^c option
when ( ^a or ^b) : (static member ( / ) : ^a * ^b ->
^c) and ^b : (static member get_Zero : -> ^b) and ^b :
equality
- To test this function, enter the following code and send it to the FSI session:
let goodDivideResult, badDivideResult = tryDivide 10 5,
tryDivide 1 0
- The FSI session should display:
val goodDivideResult : int option = Some 2
val badDivideResult : int option = None
- Here is another way of using the Option type – to handle the results of parse expression where the parse may or may not succeed. In this case, the function we are writing takes advantage of an F# feature where output parameters (parameters that are marked with the out keyword in C#) can be returned as part of a tuple that includes the function result. Enter the following code and send it to the interactive session:
let tryParse a f = let res,parsed = f a
if res then
Some parsed
else
None
- The FSI session should display:
val tryParse : a:'a -> f:('a -> bool * 'b) -> 'b option
- To test this function, enter the following code and send it to the FSI session:
let goodParseResult = tryParse "5" (Int32.TryParse)
let badParseResult = tryParse "A" (Int32.TryParse)
- The FSI session should display:
val goodParseResult : int option = Some 5
val badParseResult : int option = None
How it works…
This recipe shows three potential uses of the Option type. First, we show how the Option type can be used to return a safe result, where the expected value might not exist (this is particularly useful when working with databases or other IO operations, where the expected record might not be found).
Then, we show a way of using the Option type to deal with operations that could potentially throw an exception (in this case, avoiding division by zero).
Finally, we show a way of using the Option type to handle situations where it is valid for there to be no result, and to provide a strongly typed and explicit way of showing this.
Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at $19.99/month. Cancel anytime
Working with tuples and pattern matching
In F#, and in functional programming in general, a function is defined as taking one parameter and returning one result. F# makes it possible for developers to write functions that take more than a single parameter as input, and return more than a single value as the results. The way F# does this is by providing tuples. Tuples are heterogeneous collections of values.
This recipe will show you how to work with tuples. As with the previous recipe, this recipe shows multiple ways of working with tuples, as well as ways of pattern matching over them.
How to do it…
- Open a new F# script file.
- Enter the following code and send it to the FSI session:
let divideWithRemainder numerator denominator = let
divisionResult = numerator / denominator
let remainder = numerator % denominator
let inputs = (numerator, denominator)
let outputs = (divisionResult, remainder)
(inputs,outputs)
let printResult input = let inputs = fst input
let numerator, denominator = inputs
let outputs = snd input
let divisionResult, remainder = outputs
printfn "%d divided by %d = %d (remainder: %d)" numerator
denominator divisionResult remainder
- The FSI session should display:
val divideWithRemainder :numerator:int -> denominator:int -
> (int * int) * (int * int)
val printResult : (int * int) * (int * int) -> unit
- To test the previous code, enter the following and send to the FSI session:
divideWithRemainder 10 4
|> printResult
- The FSI session should display:
10 divided by 4 = 2 (remainder: 2)
- The printResult function could also be written in other ways with the same result. These ways are shown next. Send these to the interactive session to confirm that they display the same result.
let printResultPartialPattern (inputs, outputs) = let numerator,
denominator = inputs
let divisionResult, remainder = outputs
printfn "%d divided by %d = %d (remainder: %d)" numerator
denominator divisionResult remainder
let printResultFullPattern ((numerator, denominator),
(divisionResult, remainder)) = printfn "%d divided by %d = %d
(remainder: %d)" numerator denominator divisionResult remainder
- The FSI session should display:
val printResultPartialPattern : inputs:(int * int) *
outputs:(int * int) -> unit
val printResultFullPattern : (int * int) * (int * int) ->
unit
Notice how for all of the various printResult functions, the signature displayed in the F# Interactive Session window are of the same type, (int * int) * (int * int). Whenever you see the asterisk (*) in a function signature, it is there to show that the type is a tuple, with a possible range consisting of all the possible values of the type on the left multiplied by all the possible values of the type on the right of the asterisk. This is a good way of displaying how tuples form part of the theory of algebraic data types.
- Now, we will show another way of using tuples. Tuples can be given type aliases so that their structure and intent is clear. This also allows for the defining of functions that have a clear purpose with regards to either their inputs or their outputs. Enter the following code and send it to the FSI session:
type Gender = | Female | Male
type Cat = (string * int * Gender)
let printCat (cat:Cat) =
let name,age,gender = cat
let genderPronoun = match gender with | Female -> "She" |
Male -> "He"
printfn "%s is a cat. %s is %d years old." name
genderPronoun age
let cats: Cat list = ["Alice", 6, Female "Loki", 4, Male
"Perun", 4, Male]
- The FSI session should display:
type Gender =| Female | Male
type Cat = string * int * Gender
val printCat : string * int * Gender -> unit
val cats : Cat list = [("Alice", 6, Female); ("Loki", 4,
Male); ("Perun", 4, Male)]
- To test the preceding code, enter the following and send it to the FSI session:
cats |> List.iter printCat
- The FSI session should display:

How it works…
This recipe shows two potential uses for tuples. Tuples can be used to allow multiple inputs to a function, or to allow a function to return multiple outputs. The values contained within a tuple do not have to be of the same type.
First, we show a case for both returning multiple values from a function and providing multiple input values. The divideWithRemainder function accepts two inputsin curried form and returns a tupled output (which itself consists of two tuples). The tupled output from the divideWithRemainder function is then piped into the printResult function. Notice that the first printResult function accepts only a single input. The other ways of writing the printResult function are two different ways of taking advantage of F#'s pattern matching capabilities for simplifying the process.
Then, we show you how to work with tuples with more than two elements (the functions fst and snd do not work for tuples that consist of more than two elements). As with the first section, pattern matching is able to decompose the tuple into its constituent parts. We also show how you can provide a type alias for a tuple and get a strongly typed feedback of whether the tuple matches the expected type. If you were to leave out an element from the cats list (age or gender for example), the IDE and compilers would give you an error.
Summary
This article intends to introduce you to many of the features of F# that will be used throughout the book. We learned more about using the Option type, Tuples, and Active Patterns, for moving from different programming styles towards a more idiomatic F# functional style.
Resources for Article:
Further resources on this subject: