





















































In this article by Kevin Ashton, the author of the book F# 4.0 Programming Cookbook, you will learn the following recipes:
(For more resources related to this topic, see here.)
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.
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).
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
val filePath : string =
"C:DevelopmentPacktBookChapter1Alphabet.txt"
val tryGet : a:int -> string option
let badResult1, goodResult, badResult2 = tryGet 0, tryGet
5, tryGet 27
val goodResult : string option = Some "e"
val badResult2 : string option = None
val badResult1 : string option = None
printResult badResult1
printResult goodResult
No valid letter found
The letter found was e
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.
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
let goodDivideResult, badDivideResult = tryDivide 10 5,
tryDivide 1 0
val goodDivideResult : int option = Some 2
val badDivideResult : int option = None
let tryParse a f = let res,parsed = f a
if res then
Some parsed
else
None
val tryParse : a:'a -> f:('a -> bool * 'b) -> 'b option
let goodParseResult = tryParse "5" (Int32.TryParse)
let badParseResult = tryParse "A" (Int32.TryParse)
val goodParseResult : int option = Some 5
val badParseResult : int option = None
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.
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.
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
val divideWithRemainder :numerator:int -> denominator:int -
> (int * int) * (int * int)
val printResult : (int * int) * (int * int) -> unit
divideWithRemainder 10 4
|> printResult
10 divided by 4 = 2 (remainder: 2)
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
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.
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]
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)]
cats |> List.iter printCat
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.
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.
Further resources on this subject: