Introduction to Functional Programming in F# – Part 3

Introduction

So far in this series we have covered a lot of the fundamental functional programming concepts. In this post we will investigate null handling and exceptions.

Null Handling

Most of the time, you will not have to deal with null in your F# code as it has a built-in type called Option that you will use instead. It looks very similar to this:

type Option<'T> =
    | Some of 'T
    | None

It is a discriminated union with two choices. The ' is the F# way of showing the type is a generic. Any type can be made optional.

Create a new file in your folder called option.fsx. Add 'open System' without the quote marks to the top of the file.

Don't forget to highlight and run the code examples in this post in F# Interactive (FSI).

We'll create a function to try to parse a string as a DateTime.

let tryParseDateTime (input:string) =
    let (success, result) = DateTime.TryParse input
    if success then Some result
    else None

You can also pattern match directly:

let tryParseDateTime (input:string) =
    match DateTime.TryParse input with
    | true, result -> Some result
    | _ -> None

I prefer the previous version because it reads more easily to my eyes.

Run the following examples:

let isDate = tryParseDateTime "2019-08-01"
let isNotDate = tryParseDateTime "Hello"

You will see that the valid date returns Some of the valid date and the non-date string will return None.

Another way that the Option type can be used is for optional data like a person's middle name as not everyone has one:

type PersonName = {
    FirstName : string
    MiddleName : string option // or Option
    LastName : string
}

If the person doesn't have a middle name, you set it to None and if they do you set it to Some "name".

let person = { FirstName = "Ian"; MiddleName = None; LastName 
= "Russell"}
let person' = { person with MiddleName = Some "????" }

Notice that we have used the copy-and-update record expression we met in the last post.

Sadly, there is one area where nulls can sneak into your codebase; through interop with code/libraries using other .Net languages.

Interop With .NET

If you are interacting with code written in C#, there is a chance that you will have some null issues. In addition to the Option type, F# also offers the Option module that contains some very useful helper functions to make life easier.

Let's create a null for both a .Net Reference type and a .Net Nullable primitive:

let nullObj:string = null
let nullPri = Nullable()

Run the code in FSI to prove that they are both null.

To convert from .Net to an F# Option type, we can use the Option.ofObj and Option.ofNullable functions:

let fromNullObj = Option.ofObj nullObj
let fromNullPri = Option.ofNullable nullPri

To convert from an Option type to .Net types, we can use the Option.toObj and Option.toNullable functions.

let toNullObj = Option.toObj fromNullObj
let toNullPri = Option.toNullable fromNullPri

Run the code in FSI to show that this works correctly:

What happens if you want to convert from an Option type to something that doesn't support null but instead expects a placeholder value? You could use pattern matching as Option is a discriminated union or you can use another function in the Option module:

let result = Option.defaultValue "------" fromNullObj

If the Option value is Some, then the value inside the Some is returned otherwise the default is returned.

If you use this a lot, you may find that using Partial Application might make the task more pleasurable by reducing the amount of code you may need to write. We create a function that takes the default but not the Option value:

let unknown = Option.defaultValue "????" // (string option 
-> string)

let result = unknown fromNullObj

As you can see, handling of null and optional values is handled very nicely in F#. You should never see a NullReferenceException. :)

Handling Exceptions

Create a new file called result.fsx in your folder.

WE will create a function that does simple division but returns an exception if the divisor is 0:

open System

let tryDivide (x:decimal) (y:decimal) = // decimal -> decimal 
-> decimal
    try
        x/y 
    with
    | :? DivideByZeroException as ex -> raise ex

Whilst this code is perfectly valid, the function signature is lying to you; It doesn't always return a decimal. The only way I would know this is by looking at the code or getting the error when the code executed. This goes against the general ethos of F# coding.

Most functional languages implement a type that offers a choice between success and failure; F# is no exception. This is an example of a potential implementation:

type Result<'TSuccess,'TFailure> = 
   | Success of 'TSuccess
   | Failure of 'TFailure

Unsurprisingly, there is one built into the language (from F# 4.1) but rather than Success/Failure, it uses Ok/Error. Let's use the Result type in our tryDivide function:

let tryDivide (x:decimal) (y:decimal) = // decimal -> decimal 
-> Result
    try
        Ok (x/y) 
    with
    | :? DivideByZeroException as ex -> Error ex

let badDivide = tryDivide 1M 0M
let goodDivide = tryDivide 1M 1M

The failure type, exn, is the built-in F# error type. We'll use custom error types in a later post. Next we are going to look at how we can incorporate the Result type into the composition code we used in the last post.

Function Composition With Result

I have modified the getPurchases and increaseCreditIfVip functions to return Result types but have left the tryPromoteToVip function alone.

type Customer = {
    Id : int
    IsVip : bool
    Credit : decimal
}

let getPurchases customer = // Customer -> Result<
(Customer * decimal),exn>
    try
        // Imagine this function is fetching data from 
a Database
        let purchases = if customer.Id % 2 = 0 then 
(customer, 120M) else (customer, 80M)
        Ok purchases
    with
    | ex -> Error ex

let tryPromoteToVip purchases = // Customer * decimal 
-> Customer
    let customer, amount = purchases
    if amount > 100M then { customer with IsVip = true }
    else customer

let increaseCreditIfVip customer = // Customer 
-> Result
    try
        // Imagine this function could cause an exception            
        let result = 
            if customer.IsVip then { customer with Credit 
= customer.Credit + 100M }
            else { customer with Credit = customer.Credit 
+ 50M }
        Ok result
    with
    | ex -> Error ex

let upgradeCustomer customer =
    customer 
    |> getPurchases 
    |> tryPromoteToVip // Problem
    |> increaseCreditIfVip

let customerVIP = { Id = 1; IsVip = true; Credit = 0.0M }
let customerSTD = { Id = 2; IsVip = false; Credit = 100.0M }

let assertVIP = upgradeCustomer customerVIP = Ok 
{Id = 1; IsVip = true; Credit = 100.0M }
let assertSTDtoVIP = upgradeCustomer customerSTD = Ok 
{Id = 2; IsVip = true; Credit = 200.0M }
let assertSTD = upgradeCustomer { customerSTD with Id = 3; 
Credit = 50.0M } = Ok {Id = 3; IsVip = false; Credit = 100.0M }

Notice that there is a problem in the upgradeCustomer function on the call to tryPromoteToVip because the function signatures don't match up any longer.

Scott Wlaschin (https://fsharpforfunandprofit.com/rop/) visualises composition with the Result type as two parallel railway tracks which he calls Railway Oriented Programming (ROP), with one track for Ok and one for Error. He defines tryPromoteToVip as a one track function because it doesn't output a Result type and executes on the Ok track.

To fix the problem, we need to create a function that converts a normal one track function function into a Result function. The reason it is called map will become obvious later in this post:

let map oneTrackFunction resultInput = // ('a -> 'b) 
-> Result<'a,'c> -> Result<'b,'c>
    match resultInput with
    | Ok s -> Ok (oneTrackFunction s)
    | Error f -> Error f

Functions are first class citizens which means that you can use functions as inputs to other functions. With the map function, if the input is Error, the one track function never gets called and the error is passed through. Let's plug the map function in.

let upgradeCustomer customer =
    customer 
    |> getPurchases 
    |> map tryPromoteToVip 
    |> increaseCreditIfVip // Problem

That has fixed the problem with tryPromoteToVip but has pushed it on to increaseCreditIfVip. The function increaseCreditIfVip is a switch function in ROP which means that it has a Result output but doesn't handle the Error input case.

We need to create another function that converts a switch function into a Result function. It is very similar to the map function. Again the reason for calling it bind will become obvious later in this post:

let bind switchFunction resultInput = // ('a -> Result<'b,'c>) 
-> Result<'a,'c> -> Result<'b,'c>
    match resultInput with
    | Ok s -> switchFunction s
    | Error f -> Error f

The difference between bind and map is that we don't need to wrap the output in an Ok on the Ok track for bind.

Let's plug the bind function in.

let upgradeCustomer customer =
    customer 
    |> getPurchases 
    |> map tryPromoteToVip 
    |> bind increaseCreditIfVip

The code should now have no compiler warnings. Run the asserts to verify the code works as expected.

The reason for using map and bind as function names is because the Result module has them built in as it is a common requirement. Let's update the function to use the Result module functions rather than our own.

let upgradeCustomer customer =
    customer 
    |> getPurchases 
    |> Result.map tryPromoteToVip 
    |> Result.bind increaseCreditIfVip

We can delete our map and bind functions.

If you want to learn more about this style of programming, I highly recommend Scott's book "Domain Modelling Made Functional" (https://pragprog.com/book/swdddf/domain-modeling-made-functional). Not only does it cover ROP but also lots of very useful Domain-Driven Design information.

Summary

Another post completed with a lot of really useful features and concepts covered.

  • Option type and module

  • Result type and module

  • Exception handling

  • Null handling

  • Functions as inputs to other functions

In the next post we look at testing.

Part 2 Table of Contents Part 4

 

Introduction to Functional Programming in F#

 

Blog 3/22/23

Introduction to Functional Programming in F# – Part 8

Discover Units of Measure and Type Providers in F#. Enhance data management and type safety in your applications with these powerful tools.

Blog 10/1/22

Introduction to Functional Programming in F# – Part 4

Unlock F# collections and pipelines. Manage data efficiently and streamline your functional programming workflow with these powerful tools.

Blog 8/8/23

Introduction to Functional Programming in F# – Part 12

Explore reflection and meta-programming in F#. Learn how to dynamically manipulate code and enhance flexibility with advanced techniques.

Blog 3/22/23

Introduction to Functional Programming in F# – Part 9

Explore Active Patterns and Computation Expressions in F#. Enhance code clarity and functionality with these advanced techniques.

Blog 12/22/22

Introduction to Functional Programming in F# – Part 6

Learn error handling in F# with option types. Improve code reliability using F#'s powerful error-handling techniques.

Blog 9/13/22

Introduction to Functional Programming in F# – Part 2

Explore functions, types, and modules in F#. Enhance your skills with practical examples and insights in this detailed guide.

Blog 10/11/22

Introduction to Functional Programming in F# – Part 5

Master F# asynchronous workflows and parallelism. Enhance application performance with advanced functional programming techniques.

Blog 12/22/22

Introduction to Functional Programming in F# – Part 7

Explore LINQ and query expressions in F#. Simplify data manipulation and enhance your functional programming skills with this guide.

Blog 5/17/23

Introduction to Functional Programming in F# – Part 10

Discover Agents and Mailboxes in F#. Build responsive applications using these powerful concurrency tools in functional programming.

Blog 5/18/22

Introduction to Functional Programming in F#

Dive into functional programming with F# in our introductory series. Learn how to solve real business problems using F#'s functional programming features. This first part covers setting up your environment, basic F# syntax, and implementing a simple use case. Perfect for developers looking to enhance their skills in functional programming.

Blog 7/12/23

Introduction to Functional Programming in F# – Part 11

Learn type inference and generic functions in F#. Boost efficiency and flexibility in your code with these essential programming concepts.

Blog 7/21/20

Understanding F# applicatives and custom operators

In this post, Jonathan Channon, a newcomer to F#, discusses how he learnt about a slightly more advanced functional concept — Applicatives.

Blog 11/30/22

Introduction to Partial Function Application in F#

Partial Function Application is one of the core functional programming concepts that everyone should understand as it is widely used in most F# codebases.In this post I will introduce you to the grace and power of partial application. We will start with tupled arguments that most devs will recognise and then move onto curried arguments that allow us to use partial application.

Blog 3/10/21

Introduction to Web Programming in F# with Giraffe – Part 1

In this series we are investigating web programming with Giraffe and the Giraffe View Engine plus a few other useful F# libraries.

Blog 3/11/21

Introduction to Web Programming in F# with Giraffe – Part 2

In this series we are investigating web programming with Giraffe and the Giraffe View Engine plus a few other useful F# libraries.

Blog 3/12/21

Introduction to Web Programming in F# with Giraffe – Part 3

In this series we are investigating web programming with Giraffe and the Giraffe View Engine plus a few other useful F# libraries.

Blog 5/1/21

Ways of Creating Single Case Discriminated Unions in F#

There are quite a few ways of creating single case discriminated unions in F# and this makes them popular for wrapping primitives. In this post, I will go through a number of the approaches that I have seen.

Blog 9/17/21

How to gather data from Miro

Learn how to gather data from Miro boards with this step-by-step guide. Streamline your data collection for deeper insights.

Blog 8/7/20

Understanding F# Type Aliases

In this post, we discuss the difference between F# types and aliases that from a glance may appear to be the same thing.

Blog 10/21/20

Consistency and Aggregates in Event Sourcing

Learn how we ensures data consistency in event sourcing with effective use of aggregates, enhancing system reliability and performance.