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 2 forms (for demo purposes I am using a 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 following
|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 ok to use in F# things, such as pattern matching etc 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.
|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.|
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: http://msdn.microsoft.com/en-us/library/ee370544.aspx
So now that we know what Option types are, how do we go about creating them. Lets see some examples shall we. Note in this example I has 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 lets see an example of the ok case (though pattern matching is still preferred).
So lets say we had this code:
let someInt = Some(43) let someString = Some("cat") let printTheOption (opt :Option<'a>) = 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<'a>) = 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 issues is that we tried to get the passed in Option value using the Option.Value helper property, which is 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 lets now turn our attention to how we could avoid these Exceptions by simply using some simple pattern matching such as that shown here:
let printTheOption (opt :Option<'a>) = 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, can not be ignored.
Here is the result of running this:
Option vs Null
So we have talked about F#s Option compared to Nullabe<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 total 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 screen shot 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 lets 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; //excplicily 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 sis 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