Click here to Skip to main content
15,885,546 members
Articles / Programming Languages / F#

F# 9: Option Types

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
13 Mar 2014CPOL6 min read 18K   4  
Option types in F#

If C#, there is a concept of null for reference types and Nullable<T> class for value types (structs). For value type, this can take one of the following two forms (for demo purposes, I am using an int type here, but the type could be any value type):

  • Nullable<int>
  • int?

These are both equivalent.

The Nullable<T> class exposes a few helper properties and methods that make it easier to work with null and value types. These are as follows:

Property/MethodDescription
HasValueGets a value indicating whether the current Nullable<T> object has a valid value of its underlying type.
ValueGets the value of the current Nullable<T> object if it has been assigned a valid underlying value.
GetValueOrDefault()Retrieves the value of the current Nullable<T> object, or the object’s default value.
GetValueOrDefault(T)Retrieves the value of the current Nullable<T> object, or the specified default value.

In F#, there is something slightly different to this, in the form of an Option type, which is more F# friendly type, that prefers to not deal in terms of null and not null, but rather prefers to deal with things in terms of “Can hold a value of a type” or “May not have a value”. This does sound like Nullable<T> I give you that, but it is a F# type after all, so you can expect it to be ok to use in F# things, such as pattern matching, etc.

Another thing to note with F’’#s Option type is that is may be used for any type, not just value types. This is different from the .NET Nullable<T> which may only be used with value types.

The value None is used when an option does not have an actual value. Otherwise, the expression Some( … ) gives the option a value. The values Some and None can obviously be used in pattern matching, which we will see an example of in this post.

Like Nullable<T>, the F# Option type has several helper properties / methods, which are shown in the table below.

Property/MethodTypeDescription
None‘T optionA static property that enables you to create an option value that has the None value.
IsNoneboolReturns true if the option has the None value.
IsSomeboolReturns true if the option has a value that is not None.
Some‘T optionA static member that creates an option that has a value that is not None.
Value‘TReturns the underlying value, or throws a NullReferenceException if the value is None.

Additional Helpers

There is an Option module that contains a few more helpers for dealing with Option types in F#. You can read more about this here.

Creating Options

So now that we know what Option types are, how do we go about creating them. Let's see some examples, shall we? Note in this example I have used the helper methods IsSome / IsNone. Personally, I think pattern matching is a better way to go, as it will make you match against all cases including the None case.

In fact, I will show you just how easy it is to get something wrong, when dealing with Option types, should you choose to use the helper methods, but first let's see an example of the ok case (though pattern matching is still preferred).

So let's say we had this code:

F#
let someInt = Some(43)
let someString = Some("cat")

let printTheOption (opt :Option) =
    printfn "Actual Option=%A" opt
    printfn "IsNone=%b, IsSome=%b Value=%A\r\n\r\n" opt.IsNone opt.IsSome opt.Value
        
printfn "let someInt = Some(43)"
printfn "let someString = Some(\"cat\")"
printfn "printTheOption (someInt)"
printTheOption someInt
printfn "printTheOption (someString)"
printTheOption someString

This would give us the following results (as expected):

image

But what about we try that again using this code, where we have a None for the value of the Option we will pass to the “printTheOption” function:

F#
let demoNone = None

let printTheOption (opt :Option) =
    printfn "Actual Option=%A" opt
    printfn "IsNone=%b, IsSome=%b Value=%A\r\n\r\n" opt.IsNone opt.IsSome opt.Value
        
printfn "let demoNone = None"
printfn "printTheOption demoNone"
printTheOption demoNone

This time, if we attempt to run this code, we get this result:

image

As you can see, we have an issue here. The issue is that we tried to get the passed in Option value using the Option.Value helper property, which in this case was None, so we got a NullReferenceException. This is shown in the table above, when you use the helper properties and methods you may get Exceptions thrown such as this one. Ok, you could use the IsNone method, but that would then ripple through your code, and you would be forever checking the value using this, when you could just use a nice clean pattern match, job done.

If you can’t relate to this, ask yourself how many times you have had to check for null when using C#, for me, that is a fair amount. This has even given rise to people bringing functional constructs like the Maybe Null Monad into regular .NET code, but this certainly isn’t part of the BCL.

Luckily, we can avoid these issues by using pattern matching which we look at next.

Pattern Matching Options

So now that we have seen the dangers of using the helper methods/properties, and how easy it is to forget about the None case. So let's now turn our attention to how we could avoid these Exceptions by simply using some simple pattern matching such as that shown here:

F#
let printTheOption (opt :Option) = 
    match opt with
    | Some a -> printfn "opt is Some, and has value %A" a
    | None -> printfn "opt is None"        

let demoNone = None
let someInt = Some 1
let someString = Some "crazy dude in the monkey house"

printTheOption demoNone
printTheOption someInt
printTheOption someString

My personal feeling is that the pattern matching is actually way more readable, than code that would be littered with IsSome / IsNone all over the place. That is of course a personal feeling, but the fact that we have covered all our bases here in this one simple pattern matched function, cannot be ignored.

Here is the result of running this:

image

Option vs Null

So we have talked about F#s Option compared to Nullable<T> and we know that an Option type can be used with any type whilst Nullable<T> can only be used with value types (structs). But what about Option types versus regular reference types in .NET. Well, one huge win for F# Option is that when you use a reference type in .NET, you are really dealing with a pointer reference, which as such can be set to null. The object type however is still the same as it was declared as, which may hold a valid reference to an object on the heap, or may be null.

So it is totally ok to write something like this:

F#
string s1 = "cats";
int s1Length = s1.Length;

string s2 = null;
int s2Length = s2.Length;

This will compile just fine, for the reasons I stated above. However, when we run this, we will get a NullReferenceException, for which we would apply defensive programming to protect all code from the possible presence of null. This does become tedious pretty quickly even if you have a nice little guard class that will test a value and handle it / throw some more meaningful Exception.

This small screenshot uses LinqPad so it may look a bit strange if you have not seen LinqPad before, but trust me, you would still get the same result in a different IDE.

image

Now let's what the equivalent code would look like in F#, which would be this:

F#
let s1 = "Cats"
let s1Length = s1.Length;
      
let s2 = None
let s2Length = s2.Length;

//explicitly string typed None
let s3 = Option.None
let s3Length = s3.Length;

Here is what this looks like in Visual Studio. It can be seen that this is an immediate compile time error, where s is considered a completely different type, and as such has no concept of a “Length” property.

Equality With Options

Option types are considered equal they hold the same type, and that the type they hold are equal, which is subject to the equality rules of the held type.

So this sort of thing would cause an immediate compile time error in F#.

F#
let o1 = Some 1
let o2 = Some "Cats"
     
printfn "o1=o2 : %b" (o1 = o2)

This will give this result:

image

Whilst this would work as expected since the types are the same:

F#
let o1 = Some "Cats"
let o2 = Some "Cats"
let o3 = Some "cats"
     
printfn "o1=o2 : %b" (o1 = o2)    
printfn "o2=o3 : %b" (o2 = o3)   

Which yields this result:

image

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
-- There are no messages in this forum --