LtU on Dart
lambda-the-ultimate.org
lambda-the-ultimate.org
Not that I think that Dart is particularly creative from a PL design standpoint, but I read the complaints about covariant genetics and think: who cares about the type system? It's optional after all!
And type system is optional? Besides Assembly, I can't think of any widely used programming language that is untyped.
Yes, there are plenty of languages with weak typing or dynamic typing, but that's very different from no typing at all.
And at least the static type system in Dart is optional. Yet all I hear about Dart is digs at its static type system. Can't express non-nulity, has covariant generics, etc. Who really cares? It's designed to throw up a warning when you pass a string to Math.min, not to allow expressing every static invariant possible through the static type system.
Imagine if I were to put out a language with great fanfare given to the fact that it has a built-in string type. What would your reaction be?
I can see some Smalltalk influence in Dart, but only as much as I see in Java.
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.
Some 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.
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
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.
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 inputHowever, 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.
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.