29

In F# its a big deal that they do not have null values and do not want to support it. Still the programmer has to make cases for None similar to C# programmers having to check != null.

Is None really less evil than null?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Carlo V. Dango
  • 13,322
  • 16
  • 71
  • 114
  • 2
    Certainly better than `type bool = True | False | FileNotFound` – Juliet Dec 17 '10 at 16:53
  • See also http://stackoverflow.com/questions/3989264/best-explanation-for-languages-without-null – Brian Dec 18 '10 at 03:12
  • 2
    I was asking myself the same question about `Maybe` in Haskell versus hypothetically *checked* `NullPointerException`s in Java. The thing that's better in Haskell, and Java probably won't have is that `Maybe` is an instance of the `Monad` typeclass, and this allows much more succinct calls, where the developers don't have to explicitly check against `Nothing` (Haskell's `None`). They can let it bubble up. – Ionuț G. Stan Dec 18 '10 at 10:14
  • 1
    On a related note, try evaluating `(None, None)` in F# interactive and see how it pretty prints. What would a better design have been? Why was the current solution chosen instead? – J D Dec 18 '10 at 14:42

4 Answers4

39

The problem with null is that you have the possibility to use it almost everywhere, i.e. introduce invalid states where this is neither intended nor makes sense.

Having an 'a option is always an explicit thing. You state that an operation can either produce Some meaningful value or None, which the compiler can enforce to be checked and processed correctly.

By discouraging null in favor of an 'a option-type, you basically have the guarantee that any value in your program is somehow meaningful. If some code is designed to work with these values, you cannot simply pass invalid ones, and if there is a function of option-type, you will have to cover all possibilities.

Aaron Marten
  • 6,548
  • 1
  • 32
  • 41
Dario
  • 48,658
  • 8
  • 97
  • 130
  • 2
    This answer is far more superior than the accepted one imo (although both are great). ShdNx's answer deals with why options are great but this one really compares it with null and answers how it really is less evil (and even fantastic!). Wish I could up-vote it a 2nd time; thank you Dario! – MasterMastic Jan 28 '14 at 17:44
32

Of course it is less evil!

If you don't check against None, then it most cases you'll have a type error in your application, meaning that it won't compile, therefore it cannot crash with a NullReferenceException (since None translates to null).

For example:

let myObject : option<_> = getObjectToUse() // you get a Some<'T>, added explicit typing for clarity
match myObject with
| Some o -> o.DoSomething()
| None -> ... // you have to explicitly handle this case

It is still possible to achieve C#-like behavior, but it is less intuitive, as you have to explicitly say "ignore that this can be None":

let o = myObject.Value // throws NullReferenceException if myObject = None

In C#, you're not forced to consider the case of your variable being null, so it is possible that you simply forget to make a check. Same example as above:

var myObject = GetObjectToUse(); // you get back a nullable type
myObject.DoSomething() // no type error, but a runtime error

Edit: Stephen Swensen is absolutely right, my example code had some flaws, was writing it in a hurry. Fixed. Thank you!

ShdNx
  • 3,172
  • 5
  • 40
  • 47
  • Even in C# you will get warnings in ReSharper if you don't check for null – JoelFan Dec 17 '10 at 11:27
  • 1
    @Carlo V. Dango, @SplashHit when working with option type you have very convinient methods Option.Map and Option.bind, plus it forces you to deal with the None and reason about it. Also you can easily extended your code in a safe way, in case you want to have more outcomes than the binary Some (somthing) or None (nothing). – jlezard Dec 17 '10 at 11:48
  • @SplashHit: With C#, you need additional (external) help to manage null checking. With F#, the Option type makes this part of the language. – pblasucci Dec 17 '10 at 13:16
14

Let's say I show you a function definition like this:

val getPersonByName : (name : string) -> Person

What do you think happens when you pass in a name of a person who doesn't exist in the data store?

  • Does the function throw a NotFound exception?
  • Does it return null?
  • Does it create the person if they don't exist?

Short of reading the code (if you have access to it), reading the documentation (if someone was kindly enough to write it), or just calling the function, you have no way of knowing. And that's basically the problem with null values: they look and act just like non-null values, at least until runtime.

Now let's say you have a function with this signature instead:

val getPersonByName : (name : string) -> option<Person>

This definition makes it very explicit what happens: you'll either get a person back or you won't, and this sort of information is communicated in the function's data type. Usually, you have a better guarantee of handling both cases of a option type than a potentially null value.

I'd say option types are much more benevolent than nulls.

Juliet
  • 80,494
  • 45
  • 196
  • 228
3

In F# its a big deal that they do not have null values and do not want to support it. Still the programmer has to make cases for None similar to C# programmers having to check != null.

Is None really less evil than null?

Whereas null introduces potential sources of run-time error (NullRefereceException) every time you dereference an object in C#, None forces you to make the sources of run-time error explicit in F#.

For example, invoking GetHashCode on a given object causes C# to silently inject a source of run-time error:

class Foo {
  int m;
  Foo(int n) { m=n; }
  int Hash() { return m; }
  static int hash(Foo o) { return o.Hash(); }
};

In contrast, the equivalent code in F# is expected to be null free:

type Foo =
  { m: int }
  member foo.Hash() = foo.m

let hash (o: Foo) = o.Hash()

If you really wanted an optional value in F# then you would use the option type and you must handle it explicitly or the compiler will give a warning or error:

let maybeHash (o: Foo option) =
  match o with
  | None -> 0
  | Some o -> o.Hash()

You can still get NullReferenceException in F# by circumventing the type system (which is required for interop):

> hash (box null |> unbox);;
System.NullReferenceException: Object reference not set to an instance of an object.
   at Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicFunctions.UnboxGeneric[T](Object source)
   at <StartupCode$FSI_0021>.$FSI_0021.main@()
Stopped due to error
Community
  • 1
  • 1
J D
  • 48,105
  • 13
  • 171
  • 274