scala.NothingはScalaのクラス階層における、「一番下」に位置するクラスで全ての型のサブタイプになるが、これが何のためにあるかという点でつまづく人が時々居るようだ。というわけで、scala.Nothingがあると何が嬉しいのかという点をちょっと説明してみようと思う。
- 例外やエラーを投げる式のような、戻ってこない計算の型として使う
たとえば、次のような、階乗を計算する関数factorialを考えてみよう。factorialはnの階乗を計算する関数で、nを引数に取りnの階乗を計算して返す。また、nは0以上の整数でなければならないものとし、0未満の値が引数として渡されたらIllegalArgumentExceptionを投げるものとする。このとき、factorialの定義は、たとえば
def factorial(n: Int): Int = { if(n < 0) throw new IllegalArgumentException("n must be >= 0") else (1/:(1 to n))(_*_) }
のようになる。このとき、if式全体がIntを返す式である必要があるため、n < 0が成立したときの本体の式、つまりthrow式の型はIntである必要がある。じゃあ、throw式の型はInt型にすれば良いのかと言えば、たとえばDouble型を対象にして同様の関数を書いたときに型が合わなくなってしまうので困る。ここでNothingの出番になる。Nothingは全ての型のサブタイプであるため、どんな型の値にでも化けられるという性質がある。throw式の型をNothingとすれば、どんな型が要求されている箇所にthrow式が現れても適切に型付けされる。そのため、scalaではthrow式の型はscala.Nothingであるということになっている。
- (varianceと組み合わせて)空のコレクションの要素型として使う
Listの型について考えてみよう。
たとえば、
List(1, 2, 3)
の型はList[Int]だ。また、
List("A", "B", "C")
の型はList[String]だ。じゃあ、空リスト
List()
や
Nil
の型はどうなるだろう?MLなどの言語を知っている人ならば、List['a]のような型を与えれば良いのでは、と思われるかもしれない。しかしながら、Scalaでは、メソッドやクラスを多相的にすることはできても、MLなどにおける引数無しのデータコンストラクタに相当するobjectの定義にList['a]のような多相的な型を与えることはできない。
ここで少し話を変えるが、scalaのListはcovariantであり、二つの型List[A]とList[B]について、AがBのサブタイプならばList[A]もList[B]のサブタイプであるという性質を持つ。ここで、Nothingは全ての型のサブタイプであるため、List[Nothing]とList[B]という二つの型について、常にList[Nothing]はList[B]のサブタイプという性質が成り立つ。つまり、List[Nothing]はどんな要素型のListにも化けられるListということになる。List[Nothing]はどんな型のListにも化けられるため、List()やNilの型として、List[Nothing]を与えることで、空リストに対して適切な型を与えることができる。