Wed 20 Aug 2008
A Post About Nothing
Posted by Matt under Scala
[3] Comments
One of the main complaints you hear about the Scala language is that it’s too complicated compared to Java. The average developer will never be able to achieve a sufficient understanding of the type system, the functional programming idioms, etc. That’s the argument. To support this position, you’ll often hear it pointed out that Scala includes several notions of nothingness (Null, null, Nil, Nothing, None, and Unit) and that you have to know which one to use in each situation. I’ve read an argument like this more than once.
It’s not as bad as all that. Yes, each of those things is part of Scala, and yes, you have to use the right one in the right situation. But the situations are so wildly different it’s not hard to figure out once you know what each of these things mean.
Null and null
First, let’s tackle Null and null. Null is a trait, which (if you’re not familiar with traits) is sort of like an abstract class in Java. There exists exactly one instance of Null, and that is null. Not so hard. The literal null serves the same purpose as it does in Java. It is the value of a reference that is not refering to any object. So if you write a method that takes a parameter of type Null, you can only pass in two things: null itself or a reference of type Null. Observe:
scala> def tryit(thing: Null): Unit = { println("That worked!"); } tryit: (Null)Unit scala> tryit("hey") <console>:6: error: type mismatch; found : java.lang.String("hey") required: Null tryit("hey") ^ scala> val someRef: String = null someRef: String = null scala> tryit(someRef) <console>:7: error: type mismatch; found : String required: Null tryit(someRef) ^ scala> tryit(null) That worked! scala> val nullRef: Null = null nullRef: Null = null scala> tryit(nullRef) That worked!
In line 4 we try to pass in a String, and of course that doesn’t work. Then in line 14 we try to pass in a null reference, but that doesn’t work either! Why? It’s a null reference to a String. It may be null at run-time, but compile-time type checking says this is a no-no.
But look at line 21. We can pass in the literal null. And in line 27 we pass in another null reference, but this one is actually of type Null. Notice that we initialized nullRef to null. That’s the only value to which we could have initialized it, because null is the sole instance of Null.
Nil
Nil is an easy one. Nil is an object that extends List[Nothing] (we’ll talk about Nothing next). It’s an empty list. Here’s some example code using Nil:
scala> Nil res4: Nil.type = List() scala> Nil.length res5: Int = 0 scala> Nil + "ABC" res6: List[java.lang.String] = List(ABC) scala> Nil + Nil res7: List[object Nil] = List(List())
See? It’s basically a constant encapsulating an empty list of anything. It’s has zero length. It doesn’t really represent ‘nothingness’ at all. It’s a thing, a List. There are just no contents.
Nothing
If any of these is a little difficult to get, it’s Nothing. Nothing is another trait. It extends class Any. Any is the root type of the entire Scala type system. An Any can refer to object types as well as values such as plain old integers or doubles. There are no instances of Nothing, but (here’s the tricky bit) Nothing is a subtype of everything. Nothing is a subtype of List, it’s a subtype of String, it’s a subtype of Int, it’s a subtype of YourOwnCustomClass.
Remember Nil? It’s a List[Nothing] and it’s empty. Since Nothing is a subtype of everything, Nil can be used as an empty List of Strings, an empty List of Ints, an empty List of Any. So Nothing is useful for defining base cases for collections or other classes that take type parameters. Here’s a snippet of a scala session:
scala> val emptyStringList: List[String] = List[Nothing]() emptyStringList: List[String] = List() scala> val emptyIntList: List[Int] = List[Nothing]() emptyIntList: List[Int] = List() scala> val emptyStringList: List[String] = List[Nothing]("abc") <console>:4: error: type mismatch; found : java.lang.String("abc") required: Nothing val emptyStringList: List[String] = List[Nothing]("abc")
On line 1 we assign a List[Nothing] to a reference to List[String]. A Nothing is a String, so this works. On line 4 we assign a List[Nothing] to a reference to List[Int]. A Nothing is also an Int, so this works too. A Nothing is a subtype of everything. But both of these List[Nothing] instances contain no members. What happens when we try to create a List[Nothing] containing a String and assign that List to a List[String] reference? It fails because although Nothing is a subtype of everything, it isn’t a superclass of anything and there are no instances of Nothing, including String “abc”. So any collection of Nothing must necessarily be empty.
One other use of Nothing is as a return type for methods that never return. It makes sense if you think about it. If a method’s return type is Nothing, and there exists absolutely no instance of Nothing, then such a method must never return.
None
When you’re writing a function in Java and run into a situation where you don’t have a useful value to return, what do you do? There are a few ways to handle it. You could return null, but this causes problems. If the caller isn’t expecting to get a null, he could be faced with a NullPointerException when he tries to use it, or else the caller must check for null. Some functions will definitely never return null, but some may. As a caller, you don’t know. There is a way to declare in the function signature that you might not be able to return a good value, the throws keyword. But there is a cost associated with try/catch blocks, and you usually want to reserve the use of exceptions for truly exceptional situations, not just to signify an ordinary no-result situation.
Scala has a built-in solution to this problem. If you want to return a String, for example, but you know that you may not be able to return a sensible value you can return an Option[String]. Here’s a simple example.
scala> def getAStringMaybe(num: Int): Option[String] = { | if ( num >= 0 ) Some("A positive number!") | else None // A number less than 0? Impossible! | } getAStringMaybe: (Int)Option[String] scala> def printResult(num: Int) = { | getAStringMaybe(num) match { | case Some(str) => println(str) | case None => println("No string!") | } | } printResult: (Int)Unit scala> printResult(100) A positive number! scala> printResult(-50) No string!
The method getAStringMaybe returns Option[String]. Option is an abstract class with exactly two subclasses, class Some and object None. Those are the only two ways to instantiate an Option. So getAStringMaybe returns either a Some[String] or None. Some and None are case classes, so you can use the handy match/case construct to handle the result. None is object that signifies no result from the method.
The purpose of an Option[T] return type is to tell callers that the method might return a T in the form of a Some[T], or it might return None to signify no result. This way, the caller supposedly knows when he does and does not need to check for a good return value.
On the other hand, just because a method is declared as returning some non-Option type doesn’t mean it can’t return null. Moreover, a method declared as returning Option can, in fact, return a null. So the technique isn’t perfect.
This is a neat trick, but can you imagine a codebase peppered with Option[This] and Option[That] all over the place, and all those ensuing match blocks? I say use Option sparingly.
Unit
This is another easy one. Unit is the type of a method that doesn’t return a value of any sort. Sound familiar? It’s like a void return type in Java. Here’s an example:
scala> def doThreeTimes(fn: (Int) => Unit) = { | fn(1); fn(2); fn(3); | } doThreeTimes: ((Int) => Unit)Unit scala> doThreeTimes(println) 1 2 3 scala> def specialPrint(num: Int) = { | println(">>>" + num + "<<<") | } specialPrint: (Int)Unit scala> doThreeTimes(specialPrint) >>>1<<< >>>2<<< >>>3<<<
In the definition of doThreeTimes we specify that the method takes a parameter called fn, which has a type of (Int) => Unit. This means that fn is a method that takes a single parameter of type Int and a return type of Unit, which is to say fn isn’t supposed to return a value at all just like a Java void function.
That’s it. Those are the ‘nothingness’ items in Scala. If you know of any more, please leave a comment! There is admitedly a lot to learn when you’re taking up Scala, but in return you get an incredibly expressive and succinct language.
Hi Matt, Very good explanation! Could you please elaborate on Nothing.
val emptyIntList: List[Int] = List[Nothing]()
Above thing can be achieved using below approach:
val emptyIntList: List[Int] = List()
Thanks in advance
“If any of these is a little difficult to get, it’s Nothing. Nothing is another trait.”
But it’s not a trait it is an abstract final class!
Thanks for the clear comparisons. I understand them all as separate now, with the exception of None and Unit. If a method that doesn’t return a value of any sort returns Unit (a unary type, with a single instantiation), couldn’t Option[T] be rather defined as either T or Unit. Is there a reason for the None object? It seems to me None.type has a single instantiation None and Unit has a single instantiation (). These seem parallel to me. Perhaps I’ve missed something.
I’ve tried searching for distinctions without success. Just interested if someone knows