Trustbit

View Original

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:

See this content in the original post

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.

See this content in the original post

You can also pattern match directly:

See this content in the original post

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

Run the following examples:

See this content in the original post

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:

See this content in the original post

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

See this content in the original post

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:

See this content in the original post

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:

See this content in the original post

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

See this content in the original post

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:

See this content in the original post

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:

See this content in the original post

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:

See this content in the original post

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:

See this content in the original post

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:

See this content in the original post

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.

See this content in the original post

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:

See this content in the original post

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.

See this content in the original post

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:

See this content in the original post

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.

See this content in the original post

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.

See this content in the original post

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