前回まで羽生田さんによるScalaの文法的な特徴の紹介がありましたが,今回はゲストということで,Scalaの型システムの概要やそれに関連した機能を紹介します。特にScalaのクラス階層とJavaのクラス階層の違い,Genericsの共変と反変,Existential Type,Structural Type,Compound Typeについて紹介します。
Javaとの類似点
Scalaの型システムの基本的な部分はJavaと非常に類似しています。ScalaはJavaと同様に単一のルートクラスを持っていますし,Javaにインタフェースの多重継承があるのと同様に,Scalaではtraitを多重継承することができます(traitは実装を持てるため,Javaのインタフェースとはもちろん異なりますが,traitはそれ自体で型としての機能も持っているため,実装を持たないtraitはJavaのインタフェースと同じようにみなすことができます)。
しかし,いくつかの点でJavaとは異なる部分と,Javaに比べて追加された機能があります。この記事の以降の部分では,主にJavaと異なる点や追加された機能に重点を置いてScalaの型システムについて説明します。
Scalaのクラス階層(1) - Any, AnyVal, AnyRef
連載第4回でも簡単に説明がありましたが,Scalaのクラス階層はJavaのそれと似通っています。まず,単一のルートクラスであるAnyが存在しており,Scalaにおけるすべての型は直接・間接にAnyを継承しています。これは,Javaの参照型におけるルートクラスがjava.lang.Objectであることに似ています。
一方で,Javaとは異なった部分もあります。まず,ScalaにはJavaのようなプリミティブ型は存在しません。では,どうなっているのかというと,Javaのプリミティブ型に相当するすべての型はクラスとなっており,かつ,Anyを継承したAnyValのサブクラスになっているのです。
また,すべての参照型はAnyRefの直接または間接のサブクラスになっています。先ほど,Anyがjava.lang.Objectに似ているということを書きましたが,実際の意味付けを考えると,AnyRefがjava.lang.Objectに対応していると言うほうが正確です(図1)。

ところで,本題からはずれますが,プリミティブ型に相当するクラスで,整数(Byte,Short,Char,Int,Long)や浮動小数点を表す(Float,Double)クラスが親子関係ではなく兄弟関係にあるのを見て,「ByteからIntなど,Javaなら暗黙に変換してくれる場合まで,Scalaだといちいち明示的に変換しなければいけないのか。面倒くさい」というような感想を持たれた方も居るのではないでしょうか。
実は,Scalaではimplicit conversionという,暗黙の型変換をユーザーが定義できる機能が備わっており,標準ライブラリにおいて,ByteからInt,ShortからIntなど,Javaなら暗黙の内に型変換してくれるような場合において,同様の挙動をしてくれるようにimplicit conversionが定義されています。このimplicit conversionという機能は非常に興味深い機能なので,連載の後のほうで説明があるかもしれません。
Scalaのクラス階層(2) - Nothing, Null
Scalaのクラス階層には,Javaと異なり,bottomと言うべき型が存在します。それがNothingです。Nothingはすべての型のサブタイプである,つまりNothing型であればどんな型の変数にでも代入できるような特殊な型で,Nothing型の値は存在しません。
値を持たない型など何の意味があるのかと思われるかもしれません。しかし,Nothing型は,絶対にreturnしない関数の返り値の型や,後述するGenericsにおいて,空のコレクションの要素型を表すためなど,Scalaにおいて重要な役割を持っています。
また,すべての参照型(AnyRef)のサブタイプである,つまりすべての参照型の変数に代入できるNull型が存在します。Null型の値はnullリテラルのみです(実は,Javaでもnull型があり,ScalaにおけるNull型と似た役割を担っているのですが,Scalaと違い,Javaではnull型を明示的に書く手段が存在しないため,意識する機会はほとんど無いでしょう)。
これらの関係を図にすると,図2のようになります。
