CodeProject, F#

F#9 : Option types

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)

  • 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 following

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 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.

 

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: http://msdn.microsoft.com/en-us/library/ee370544.aspx

 

Creating Options

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)

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:

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:

image

 

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:

image

 

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.

 

image

 

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

image

 

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

image

5 thoughts on “F#9 : Option types

  1. An important difference between options and null is that options get rid of the ambiguity of nulls. In C# (and most other OO Languages) null can mean one of two things: 1) value not yet assigned (implicit), or 2) value is assigned as null (explicit). With option types, None is always an explicit assignment. This gives you confidence that any None-value passed to your function is not an accident, but an explicit choice by the caller.

  2. This code doesn’t appear to compile
    let printTheOption (opt :Option) =
    match opt with
    | Some a -> printfn “opt is Some, and has value %A” a
    | None -> printfn “opt is None”

    Says “This expression was expected to have type Option but here has type ‘a option”

    1. Ha ha, its because wordpress / LIveWriter stripped some chars when posting the code.

      The line “let printTheOption (opt :Option) = ”

      should be

      “let printTheOption (opt :Option<‘a>) = “

    2. I have fixed the blog posts now, sadly livewriter and wordpress does not like F# generics brackets

Leave a comment