Previously, to me "null" was just "that other value that a variable gets if the function doesn't work or something unexpected happens." It was useful if ugly. I didn't fully grasp how much that null possibility permeated my code, since I didn't know any other way.
However, in Haskell, if something is an integer, it really is an integer of some sort. Null is not allowed.
So if you're writing a function that works with numbers, you really have to think about what are valid inputs and outputs. If you're working with a function whose domain really is all integers, then you can indicate that with a Integer -> Integer function, and you're on rock solid ground.
However, if you're writing a function like, e.g. square root, where the domain is not all integers, then you have to worry about what happens if you get a negative one. Now you can choose to bring in "null" as an allowable value, but it will have to be explicitly indicated and dealt with. Your function, for all the world to see, will be Integer -> Maybe Integer, and then any other function that uses that function will be forced to deal with the fact that a null might happen.
Tony Hoare recently (2009) called null references his “Billion Dollar Mistake” [1, 2, 3].
1. http://qconlondon.com/london-2009/presentation/Null+Referenc... 2. http://www.infoq.com/presentations/Null-References-The-Billi... 3. http://lambda-the-ultimate.org/node/3186
OCaml and Haskell are not-nullable by default, and things that are nullable have an extra layer around them that must be dealt with. This is quite sensible.
Value types (booleans, numbers, strings, structs, arrays) cannot be null, while reference types (pointers, slices, maps, channels) may be null.
Also, Go calls null 'nil', which is fitting since nil is the zero value of a reference type. (All variables are initialized to their zero value unless otherwise specified.)
If you write a function that performs an operation on a dictionary/hashtable/map/associative array, it would still be nice to be able to define it into the method signature that it's required (read: errors will be caught at compile time) to actually receive a reference to a dictionary. Or alternatively, that a null value is explicitly possible and that this should be accounted for.
I agree with jganetsk and others -- after having used languages like Haskell where there are non-nullable types by default, every language that doesn't do this simply feels broken by comparison.
It's sort of like going back to languages that require to you manually allocate memory and perform pointer arithmetic -- you can do it, but after years in the modern language of your choice that does it for you, it just seems unnecessary and basically wrong.
That right there is the reason. Those crashes are a huge burden on the software industry, it's even been called the "billion dollar mistake",
http://lambda-the-ultimate.org/node/3186
And of course it is possible to have references which cannot be null, for example, even C++ has this: References as opposed to pointers. Many new languages have adopted the non-null approach, off the top of my head, Rust.
Alternately, you have Either[A, B], with implementations Left[A](value: A), and Right[B](value: B).
So you can define a function that takes an Either[Int, ErrorMessage], rather than an Int or null. Getting a None[Int] is better than null, because it won't throw NullPointerExceptions if you access it sensibly.
Examples:
def x2or0(i: Option[Int]): Int = i.getOrElse(0) * 2
def x2(i: Option[Int]): Option[Int] = i.map(_ * 2)
def x2danger(iOrPossiblyNull: Int) = i * 2
// throws NullPointerException on null inputSome languages (I'm thinking of ML and Haskell) require you to opt into nullability with an option type (e.g. http://www.standardml.org/Basis/option.html).
What's worse is that many mainstream languages do not allow you to opt out of nullability. I.e. most or all types are always nullable.
When a var is a boolean, you can't assume it's true or false, because it could also be null. Or when you pass a Car object into a function, you've been promised a Car but were lied to because it might also be null.
In a proper type system, if you ask for a boolean, you know that variable is either true or false and when you ask for a car, you're guaranteed to get a car.
Of course, you can always have the option of allowing null values, but you should explicitly have to state that it's okay.
However, they're all largely inapposite for Dart. Dart's type system can't even ensure, at compile-time, that you'll never see an integer in a variable typed as a string. It can catch a lot of such cases, but it's fundamentally a dynamically-typed language and can't make really any guarantees at all statically. So non-nullable types in Dart would be a fiction. Any object typed anything might end up being null at runtime.
Doesn't Dart's use of optional typing mean that it really behaves as a statically-typed language in some contexts? The Go language sort of has this via interface types. Consider the following:
type Foo struct { a, b int }
func Bar(x interface{}) Foo {
if f, ok := x.(Foo); ok {
return f // x is a Foo at runtime so return it
}
return Foo{0, 1}
}
There is no need for the Foo type to have a null value. Go does have general zero-values for all types but that's somewhat different from null and not the only solution either. I think a Haskell-like model (neither null nor zero-value) could also work.It not only doesn't give an error, but it runs just fine. main() passes "cake" to bar(), which passes "cake3" to foo(), because String overloads the + operator to coerce its RHS to a string before concatenating. foo() ends up receiving a string despite being declared as receiving a number, but does what one would expect because '+' is defined for strings as well as numbers.
The compiler does not complain because it does not try to unify types globally. It does not care that bar(), which is declared as taking any type then passes that to foo() which is declared as taking a number.
If you change the 'var' in the declaration of bar() to 'num' you will indeed get a type error in main() when it tries to pass bar() a string.
So making types non-nullable in Dart would have little utility. Dart can't statically ensure that a variable declared as 'num' doesn't end up being a 'String' at run time, much less ensure that a variable is non-null.
I don't see this as a weakness of Dart. The point of Dart's optional types is to enforce some structure, provide documentation, and avoid obvious mistakes in an otherwise dynamically typed language. Statically enforcing invariants like non-nulity is really beyond its scope.