さて、引き続きふつーのオブジェクト指向言語としてのScalaを説明していきます。…と言いたいところなのですが、早くもネタ切れしてきました。ですが、行けるとこまで行ってみましょう。
- sealed:列挙型の漏れを検出してくれるくらいの機能
この解説は実際には間違いなのですが、sealedの99.999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999%がこの用途だと思って問題ないでしょう*1。
さて、sealedの本来の意味はどうでもいいので、何のために使えるかを説明することにします。
まず、前提として、ScalaにはCとかC#やJava 5以降にあるような列挙型がありません。というと、列挙型を普段使っておられる方においては、大変不便に感じられるかもしれません。
しかし、Scalaでは列挙型とほぼ同等(というか実際にはもっと強力)な機能を実現できるイディオムがあるので、ちょっとめんどくさいですが、大体問題ありません。とりあえず、コードを見てみましょう。abstract class Language case object JAVA extends Language case object SCALA extends Language
ここで、JAVAとSCALAという識別子が列挙型の要素に相当し、Languageが列挙型の型に相当します。case objectとかいうものは、単にcase classのちょっとした変種です。
これを使うときには、次のようにします。val x: Language = JAVA x match { case JAVA => println("Java!") case SCALA => println("Scala!") } // Java!
何故かJAVAとかSCALAがパターンマッチで使えていますが、置いておきましょう。ここで実際に何が行われているかというと、Java風味(というかほとんどJavaですが)の擬似言語で説明すると、次のようになります。Language x = new JAVA(); if(x instanceof JAVA) { System.out.println("Java!"); }else if(x instanceof SCALA) { System.out.println("Scala!"); }else { throw new scala.MatchError("..."); }
オブジェクト指向プログラマの大敵たるinstanceofが出てきましたが、基本的に型安全なので安心してください。
ちなみに、最初の記事では書き忘れていましたが、Scalaのパターンマッチは、switchと違って、マッチしないケースに引っかかると例外を投げます。defaultを書き忘れても検出してくれるので便利ですね。
…横道にそれ過ぎたので、話を戻しましょう。とまあ、こんな感じでScalaでは列挙型をエミュレートできるのですが、一つ問題があります。たとえば、こんな
クラス定義(列挙型のエミュレート)があったとしましょう。abstract class Language case object JAVA extends Language case object SCALA extends Language case object CSHARP extends Language //俺も仲間に入れてくれ
ここで、
val x: Language = CSHARP x match { case JAVA => println("Java!") case SCALA => println("Scala!") }
とかすると、CSHARPなんて言語は知らんぜよ、とばかりにscala.MatchErrorが飛んできます。これはこれで、default書き忘れて変な挙動になるよりはマシなのですが、Scalaはせっかく静的型付け言語なのですから、静的に列挙型の漏れが無いかチェックしたいところですよね。
で、ここでようやく登場するのがsealedです。sealedを使ってクラス定義を書き直すと、次のようになります。abstract sealed class Language case object JAVA extends Language case object SCALA extends Language case object CSHARP extends Language //俺も仲間に入れてくれ
最初の一行にsealedが追加されただけですね。でも、これが重要な意味を持つのです。
このクラス定義をなんか別の.scalaファイルにおいてコンパイルしてから、次のコード(を含むクラス定義など)をコンパイルしてみましょう。val x: Language = CSHARP x match { case JAVA => println("Java!") case SCALA => println("Scala!") }
すると、コンパイル時に
UsingLanguage.scala:3: warning: match is not exhaustive! missing combination CSHARP x match { ^
という警告が表示されるようになります。要は列挙型の要素をパターンマッチで網羅してないよ、という意味です。
とまあ、このように、sealedを使うと、列挙型に対するパターンマッチの記述漏れを防ぐことができて便利なのです。
とかいうと、Javaだったらそんなのコンパイルオプションとかで検出できるよ、という声が聞こえて来そうです。
実際には、sealedは、単に列挙型のパターンマッチ記述漏れを検出するだけの機能ではないのですが、それを説明するのは面倒なので、また次の記事で紹介しましょう。ではでは。
*1:若干誇張しています