Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds

Thinking Functionally

Save for later
  • 600 min read
  • 2016-11-01 00:00:00

article-image

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…

  1. Open a new F# Script file.
    • In Visual Studio, select File | New | File
    • Select F# Script File
  2. 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
  3. 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).
  4. The FSI session should display:
    val filePath : string = 
    "C:DevelopmentPacktBookChapter1Alphabet.txt"
    val tryGet : a:int -> string option
  5. To test this code, enter the following and send the result to FSI session:
    let badResult1, goodResult, badResult2 = tryGet 0, tryGet 
    5, tryGet 27
    
  6. The FSI session should display:
    val goodResult : string option = Some "e"
    val badResult2 : string option = None
    val badResult1 : string option = None
  7. Now, enter the following code and send it to the FSI session:
    printResult badResult1
    printResult goodResult
  8. The FSI session should display:
    No valid letter found
    The letter found was e
  9. 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.

  10. 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
  11. To test this function, enter the following code and send it to the FSI session:
    let goodDivideResult, badDivideResult = tryDivide 10 5, 
    tryDivide 1 0
    
  12. The FSI session should display:
    val goodDivideResult : int option = Some 2
    val badDivideResult : int option = None
    
  13. 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
    
  14. The FSI session should display:
    val tryParse : a:'a -> f:('a -> bool * 'b) -> 'b option
  15. 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)
    
  16. 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…

  1. Open a new F# script file.
  2. 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
  3. The FSI session should display:
    val divideWithRemainder :numerator:int -> denominator:int -
    > (int * int) * (int * int)
    val printResult : (int * int) * (int * int) -> unit
  4. To test the previous code, enter the following and send to the FSI session:
    divideWithRemainder 10 4
    |> printResult
  5. The FSI session should display:
    10 divided by 4 = 2 (remainder: 2)
  6. 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
    
  7. 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.

  8. 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]
  9. 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)]
  10. To test the preceding code, enter the following and send it to the FSI session:
    cats |> List.iter printCat
  11. The FSI session should display:

    thinking-functionally-img-0

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:


Modal Close icon
Modal Close icon