Carlo V. Dango
Carlo V. Dango

Reputation: 13852

Is None less evil than null?

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?

Upvotes: 29

Views: 3620

Answers (4)

Dario
Dario

Reputation: 49218

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.

Upvotes: 39

J D
J D

Reputation: 48687

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

Upvotes: 3

ShdNx
ShdNx

Reputation: 3213

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!

Upvotes: 32

Juliet
Juliet

Reputation: 81526

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.

Upvotes: 14

Related Questions