F# was supposed to free us of the tyranny of the unchecked null. Alas not only does it not do that, it introduces several more kinds of null. First consider this all too common problem in C# code.
int GetLength(string value) { return value.Length; }
Unless you have code analysis turned on and the function is publically available, you don’t get so much as a warning that this function may throw a NullReferenceException. Now let’s consider the F# equivalent.
let GetLength (value : string) = a.Length
Just like the C# version, this will throw a NullReferenceException if you accidentally pass a null to it. But unlike C#, you don’t even get so much as a warning.
Next up are nullable structures. Our test code in C# is followed by the F# equivalent.
static public bool IsPositive(int? value) { return value.Value > 0; }
let IsPositive( value : Nullable<int>) = value.Value > 0
Again, both versions are susceptible to throwing exceptions. In this case an InvalidOperationException.
Now that we established that using traditional types are just as dangerous as every, we turn to the new option types. First we will recode GetLength using an “option
let GetLength2 (value : option<int>) = value.Value.Length
Now we have the possibility for two different exceptions. If you pass “None” to the function you will get an InvalidOperationException. If you pass a “Some(null)” to the function, you will get a NullReferenceException. And again, there are no compiler warnings telling you that your code can fail.
F# also adds the concept of triple nulls. Since you can nest options inside options, you can write the downright silly functions such as:
let GetLength3 (value : option<string>) = value.Value.Value.Length
let IsPositive( value : option<int>) = value.Value.Value > 0
When using F# types instead of normal CLS types, things are somewhat better. Classes defined in F# cannot be assigned the value null. However, they can still be wrapped in an option type, throwing away the null-safety and bringing us back to the problem of no decent compiler warnings.
Community comments
Nothing wrong with the functions
by Chris Pellett,
Re: Nothing wrong with the functions
by Jonathan Allen,
Re: Nothing wrong with the functions
by Chris Pellett,
Re: Nothing wrong with the functions
by Jonathan Allen,
Re: Nothing wrong with the functions
by Michael Robin,
I guess I don't agree
by Sadek Drobi,
Interop is messy; idiomatic F# code need not be
by Mark Seemann,
Nothing wrong with the functions
by Chris Pellett,
Your message is awaiting moderation. Thank you for participating in the discussion.
I don't see any problem with a function like:
let getLength (s: string) = s.length
I'm betting your problem is coming from where your value is coming from (that is then being passed to this function). Somewhere along the lines you are retrieving the value for "s" - and that is where you should be doing your option checking.
For example, say I have this:
let getString input = ...
Function "getString" should actually return an Option<String>. Then, when I use it, I should do the proper pattern matching checks, like:
let length =
match getString foo with
| Some(s) -> getLength s
| None -> getLength "" // or some other action goes here
So, to sum up - your example "getLength" function is actually correct and shouldn't do the null checking (since it is expecting a non-null object)</string>
Re: Nothing wrong with the functions
by Jonathan Allen,
Your message is awaiting moderation. Thank you for participating in the discussion.
I see I wasn't completely clear.
Options and nulls are not the same thing. The Some(s) pattern will match on a Some(null), meaning you are passing a null to getLength(string) function.
If you are using an "string option" you need to make two checks. First pattern match to see if it is Some(s) or None. Then make a second check to determine if it is Some(null) or Some("actual string").
Re: Nothing wrong with the functions
by Chris Pellett,
Your message is awaiting moderation. Thank you for participating in the discussion.
But again, you are defaulting to NULLs when you should in fact be using the options. Why would you return a NULL value instead of an Option? The whole point of Options is to encourage you not to.
If you choose to go the NULL route, that is equivalent to an argument stating that F# fails functional principles because of the imperative code you write. F# doesn't fail at NULL support - it exceeds if you follow the Functional approach (of Options)
Re: Nothing wrong with the functions
by Jonathan Allen,
Your message is awaiting moderation. Thank you for participating in the discussion.
1. Because someone screwed up.
I'm willing to bet that 99% of the time, a function that returns a null is doing so by mistake.
2. Because you have to deal with the BCL.
F# users cannot ignore the Base Class Library. Even if you manage to ignore the rest of the .NET libraries, you still have to work with things like System.String. This in turn means you have to deal with wild nulls poping up in your otherwise clean code.
Re: Nothing wrong with the functions
by Michael Robin,
Your message is awaiting moderation. Thank you for participating in the discussion.
Try Option.Map or a variation on the Maybe Modad.
I guess I don't agree
by Sadek Drobi,
Your message is awaiting moderation. Thank you for participating in the discussion.
I would say that it is unusual in F# to use null and null is left only for compatibility with the rest of .net. So normally you'll not have to check for null when calling api's written in F#. Yes discipline is needed but this is always a tough call since you don't want to break compatibility with exiting libraries that form a part of F#'s power. Same problem you'd find in Scala but personally I can live with it. I wouldn't imagine OTH converting Strings in and out between F# and other languages so I guess I am saying I am quite satisfied with the trade off.
About nesting Option types it can be very meaningful and desired in a lot of situations (exactly like lists of lists) and it is easy (I doubt it is already implemented) to have a join function to flatten the type (monadic join like the one of List monad).
Sadek
Interop is messy; idiomatic F# code need not be
by Mark Seemann,
Your message is awaiting moderation. Thank you for participating in the discussion.
F# executes on, and integrates with, .NET. Objects and values in .NET can be null. When F# code interoperates with .NET software written in C# or Visual Basic, it has to deal with their imperfections.
When interoperating with the rest of the framework, F#ers often write small wrapper modules that translate .NET interop into to more idiomatic F# code. These are what Eric Evans calls Anti-Corruption Layers.
As an example, I've never seen idiomatic code using Nullable<T>. This type is a clumsy attempt to deal with the imperfections of C#, from C#. In F#, you can simply ignore it, unless you're doing interop.
Likewise, when using option types, you're not supposed to use the Value property, but instead either pattern match on the option, or use the functions in the Option module.
The third group of examples doesn't compile...