If C # has the concept of null for reference types and Nullabe for structures. This can take one of the following 2 forms (for demonstration, I use the int type here, but the type can be any structure).
Disclaimer Nullabe Nullable<T> , - ,
They are both equivalent.
The Nullable class provides several helper properties and methods that make it easy to work with null types and structures. These are the following:
- Hasvalue
- Value
- GetValueOrDefault ()
- GetValueOrDefault (T)
There is something slightly different from F # in the form of an
Option type, which is a more F # friendly type that prefers not to deal with null, but prefers to deal with things in terms of “May contain type value” or “May not have value". It sounds like Nullable, but in the end it is type F #, so you can expect it to be used normally in F # things.
Another thing worth noting with the Option F # type is that it can be used for any type, not just structures. This is different from .NET Nullable, which can only be used with structures.
The value
None is used when there is no value; otherwise, the expression
Some (...) assigns a value. The values ​​Some and None can be used when matching with a pattern, an example of which we will see in this post.
Like Nullable, the F # Option type has several helper properties / methods, which are shown in the table below.
None
'T option
A static property that allows you to create a parameter value with a value of None.
Isnone
bool
Returns true if the parameter is None.
Issome
bool
Returns true if the parameter has a value other than None.
Some
'T option
A static member that creates a parameter whose value is not None.
Value
'T
Returns a base value or throws a NullReferenceException if the value is None.
Creating Options
So, now that we know what Option types are, how do we create them. Let's look at some examples. Note that in this example, I used the helper methods IsSome / IsNone. Personally, I believe that matching with a pattern is the best way, as it will help you compare all cases, including the No option.
In fact, I will show you how easy it is to get something wrong when you work with Option types if you decide to use helper methods, but first let's look at an example of the right case.
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
But what if we try it again using this code, where we have None for the Option value, which 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
As you can see, we have a problem here. The problem is that we tried to get the Option value using the helper property Option.Value, in this case it is None, so we got a NullReferenceException. It is shown in the table above that when you use auxiliary properties and methods, you can get an exception. Well, you could use the IsNone method and you would always check the value using this when you could just use a nice clean pattern match.
If you cannot accept this, ask yourself how many times you had to check for a null value when using C #. This even led people to include functional constructs, such as Maybe Null Monad, in regular .NET code.
So, now that we have seen the danger of using helper methods / properties, let's now turn our attention to how we could avoid these exceptions:
et 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 opinion is that it is more readable than code that would be dotted with IsSome / IsNone everywhere. This, of course, is everyone’s business, but the fact that we have covered all the basics here in this simple function cannot be ignored.
Option vs Null
So, we talked about Option in F # compared to Nullabe, and we know that the Option type can be used with any type, while Nullable can only be used with structures. But what about Option types compared to regular reference types in .NET. Well, one big win for Option is that when you use the reference type in .NET, you are dealing with a pointer reference, which as such can be set to null. However, the type of the object remains the same as it was declared, which may contain a valid reference to the object in the heap (Heap) or may be null.
It would be perfectly normal to write like this:
string s1 = "cats"; int s1Length = s1.Length; string s2 = null; int s2Length = s2.Length;
This will be successfully compiled. However, when we run this, we will get a NullReferenceException, for which we will be forced to get out to protect all the code from the possible presence of null. This gets tedious pretty quickly, even if you have a nice little class for protection that will check the value and handle it / throw a more meaningful exception.
This screen shot uses LinqPad, so it might seem a little unusual if you haven't seen LinqPad before, but believe me, you still get the same result in a different IDE.
Now let's see how equivalent code in F # will look
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;
On this code, you will get an immediate compilation error, since s3 will be considered another type that does not have the “Length” property.
Option Comparison
Option types are considered equal, they are of the same type, and that the types they contain are equal, which obeys the equality rules of the held type.
So this kind of thing can lead to an immediate compile-time error in F #
let o1 = Some 1 let o2 = Some "Cats" printfn "o1=o2 : %b" (o1 = o2)
This will 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)