F# handling the option type
The steady march towards learning F# continues. This time lets look at the option type, what it is, how it compares to nullables in C# and the basics of how you are supposed to deal with it. I originally started this post because I was dealing with option in a lot of my exploratory code, and the things I’ve read online about options implied they were special in some ways, but the further through I got the more I realise they are really just the F# version of the
Nullable type in C#, its how they are handled functionally thats special.
First the C# Nullable type
The closest relative to the option type in C# world is the
Nullable<T> type. Which allows you to declare something thats not assignable to Null (e.g. an
int) as nullable, and has properties that declare whether the variable is assigned a value or not, and gives you access to the value. For example:
This piece of code will do absolutely nothing, because it declares the
Nullable<int> “test” with the shorthand
? operator. It then proceeds not to assign anything to test, and to check its
HasValue property to see if its
Null. If it wasn’t
Null it would then print its value to the screen. Now we could of course not do this null check like so:
System.InvalidOperationException: 'Nullable object must have a value.'
Of course, this code will throw an exception, which is one step further than the first code that did nothing. Finally, just for completeness:
This prints the value 1, because thats whats been assigned to the nullable int. So how does this relate to F#?
On to F# options
Well, functional languages don’t tend to work with nulls, instead they deal with something called a
maybe or in F# (and OCAML) world
option. In F# you declare something as an option type by prefixing it with the words
Some for a value or
None for no value. For example:
What this piece of code is effectively saying is that x may contain an integer value of 41, BUT consumers of this variable need to make sure they handle the case where it doesn’t exist. Fortunately for our example case above, theres an automatic conversion to a string for the option type and we got “Some(41)” printed to the console.
Just to show the
We’ve assigned None, and we got nothing printed, as you’d expect as long as the string conversions works correctly.
This is almost like for like how
Nullable works in C#, except F# explicitly prints
Some when converting to a string, and C# doesn’t. So.. if this is the same as a Nullable type in C# it follows that we can’t pass the variable directly to an int parameter:
And that turns out to be true, the compiler has rejected this code because the type
int are incompatible for the
+ operator. So how should we handle an optional type then?
The old (bad) nullable style handling
Of course, we could handle this case the same way as we do with
Nullable in C#. Option provides the methods
These work in much the same way as
Value, and calling the
Value function without first checking if there is a value will result in an exception.
You could work away using these, writing if statements just like in imperative languages, but because this is the functional world, we don’t have to do that, instead we can use some of the built in language features to assist us to work in a way where exceptions can’t really happen, and
None values are checked at compile time.
One of the really powerful features in F# is pattern matching. Its pretty useful for handling
option, its sort of a really concise switch statement. We can change our add function from above that failed to compile to correctly handle our option:
This time, we’ve handled that option type by creating a function called
addOption, all that function does is decide how to handle the logic of adding the two numbers together, either with a default value for the
None case, or with the actual value. The pattern matching syntax looks a little weird at first, but you get used to it, its basically
match <variable> with | where
| is the separator for each case. Within the first case we match x with
None, this is our condition where x isn’t set, so we declare it as
0 + y, which has no effect. In our second case, we say
Some x which checks that x has a value, and then we use x as a standard integer value in the corresponding function.
This is slightly different to how we’d have handled this in C#, you wouldn’t normally reach for the switch statement in this situation. However in functional land, functions, and the match statement are so easy to create, small and concise that this code makes a lot more sense. Its also quite reusable now, you can use this for adding any optional
int, and avoid the possibility of exceptions due to improper handling of the option.
Of course we can also refactor this code into a higher order function, and make it much more reusable.
The higher order function version
Higher order functions are functions that take another function and operate over that function and associated data. For C# developers probably the easiest example is a Lambda
Select statement, e.g.
The select function, is equivalent to F# map, it takes the
items and applies a function
x => x.ToString() to each item. The important thing to understand here is that
x => x.ToString() is just a function we define and are passing to the
Select function, the
Select function itself will just consist of the functionality to apply the passed function to the items array. So
Select is a higher order function, it doesn’t do much by itself, but it facilitates doing many different things to our items collection just by taking that function as an argument.
Back to our F# then, lets assume we want to multiply our optional numbers as well. We know that for an addition our Identity (i.e. the empty case if you remember about monoids) 0, as any
value + 0 will just return the original value. For multiplication, our Identity is 1 because 1 multiplied by any value just returns the original value too. So we need to pass in our identity, we also need to pass in the function for the operation (either + or -):
- So in this code we’ve renamed
optionOperationand added two preceeding arguments,
identitytakes our default value for an operation for when our value is null.
funcis our operation. We then use pretty much the same code as
addOption, but we replace the + with func before the two arguments
func x yand in our
Nonecase we pass the identity.
- These two lines are where the magic happens, we partially apply our
optionOperationfunction for add and multiply. For
addwe pass 0 as our Identity and we wrap the plus infix operator in brackets
(+)which turns it into a normal function that can be passed as an argument. We do exactly the same for the multiply, but with 1 and
- Finally in 3 we call our
multiplyfunctions (which are now functions in their own right), and pipe them through to be printed.
Our add and multiply function are also monoids
This is quite a neat solution, and it means we can reuse our option logic for different types too if we want. The
optionOperation can really run on any type now, with an identity and a function to apply. Lets append a function that concatenates two strings:
"I have been concatenated"
Null should not be used, if its empty, its an option
An important major distinction between F# and C# is that nothing should really be assigned
NULL . Instead
option is used, for everything, if it might not exist it is an option, and the compiler mostly forces you to handle the empty case, although as discussed not if you revert to the functions on option itself. In contrast in C# only types that can’t inherently be null can be dealt with explicitly using the
Nullable<T> type. Although C# 8 is about to add an option to change that.
For an example of F# allowing the use of option everywhere, heres a Tuple:
"Some((1, 2, 3, Fred))"
And we get the printed “Some” value as expected. And if you wanted to use the value within the tuple, you can pattern match just like with standard types. e.g.
"Some((1, 2, 3, Fred))"
Note: Here we use _ in our pattern match for the
tuple which denotes that we don’t care about that particular value.
Built in helper functions
option also has its own set of helper functions in the Option module. These assist in the processing of option types much like the standard list operations a couple of examples are:
applies a function to the option where the value exists:
Interestingly this prints
returns the value stored in the option.
This prints 1 then throws an exception, presumably because its just calling x.Value inside that method, similarly to the map, this is not really ideal behaviour and I think I’d rather use a specific pattern match.
Another particular function of interest is
defaultArg, which gets you the value or default for the option you pass.
There are a whole bunch of other method types available, I won’t list them all, but if you use them, its probably worth working out their behaviour for the
None case. Its more functional to avoid nulls and exceptions, and with the tools at your disposal to do this, theres not really a reason not to.
After all this, they’re a lot like nullables really aren’t they. F# Just provides much better tools to handle them, as long as you avoid the exception causing/null returning functions.