If C#, there is a concept of null
for reference types and Nullable<T>
class for value types (struct
s). 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):
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/Method | Description |
HasValue | Gets a value indicating whether the current Nullable<T> object has a valid value of its underlying type. |
Value | Gets 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/Method | Type | Description |
None | ‘T option | A static property that enables you to create an option value that has the None value. |
IsNone | bool | Returns true if the option has the None value. |
IsSome | bool | Returns true if the option has a value that is not None . |
Some | ‘T option | A static member that creates an option that has a value that is not None . |
Value | ‘T | Returns 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:
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):
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:
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:
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 Exception
s 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 Exception
s by simply using some simple pattern matching such as that shown here:
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:
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 (struct
s). 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:
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.
Now let's what the equivalent code would look like in F#, which would be this:
let s1 = "Cats"
let s1Length = s1.Length;
let s2 = None
let s2Length = s2.Length;
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#.
let o1 = Some 1
let o2 = Some "Cats"
printfn "o1=o2 : %b" (o1 = o2)
This will give this result:
Whilst this would work as expected since the types are the same:
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: