ScalaのCaseクラスとパターンマッチ

アクターモデルの説明あたりで謎が残ったCaseクラスとパターンマッチ。ようやくわかるのだろうか。

概要
Caseクラスとパターンマッチは、相互に関連するクラス群をグループ化して定義し、クラス群のそれぞれに対して異なる操作が必要になった際にクラス群を分解して個別に処理を行わせることができる仕組みの1つ。なんのこっちゃ。
OO言語のポリモーフィズムについて考えてみると、これは完全に上記の説明に当てはまることがわかると思う。ポリモーフィズムでは関連するクラス群を型階層としてグループ化し、個別の型に対して異なる操作が必要になった場合には、それぞれのオブジェクト型にふさわしい操作が動的に選択されて実行される。
Caseクラスとパターンマッチは、これを違う角度から実現するもの。イメージ的には、Enumとsiwtch + instanceofをまぜあわせて言語仕様に組み込んだような感じ。Enumライクにグループ化したクラス群に対して個別の操作が必要になった場合、Case文+パターンでマッチしたクラスに対して任意の処理を行う。
実際、instanceofで処理を切り替えるのと何が違うのかは疑問だ。パターンマッチは型の一致以外にも色んなルールが扱えるからより柔軟なのは確かだけど、本質的にはあまり変わらない気がするんだけど。instanceofによるメソッド内での処理の選択も、OOにとらわれなければそれなりの正当性があったということなんだろうか。

Caseクラス
case classesの定義とその特徴については、アクターモデルでの説明以上の情報は特にない。classの前にcaseをつけるだけで作成できる。ちなみに、これまでの例で出てきたようなCase共通の親クラスは必須ではない。
object ExprSample extends Application {
abstract class Expr
case class Number(n: Int)
case class MyString(n: String)

println(MyString("aaaaa"))
println(MyString("aaaaa").n)
}
こんなしょうもないサンプルも一応動く。ただし、たいていの場合には親クラスを継承する一連のグループを作ることになると思われる。でないと、Caseクラスを扱う操作で型がAnyRefとかになっちゃうし。

パターンマッチ
パターンマッチは、Caseクラスを分解するための仕組み。サンプル見た方が早い。
def eval(e: Expr): Int = e match {
case Number(x) => x
case Sum(l, r) => eval(l) + eval(r)
}
という感じで、引数に対して規定のパターンマッチ書式でマッチングをかけ、マッチした場合は 「=>」の右側の式が実行される。パターンマッチは以下から構成される。
  • Caseクラスのコンストラクタ。Number(~ や Sum(~ 。コンストラクタの引数もパターンに含まれる。
  • パターン変数。x, l, rなど。=> の右側の式でそのまま使える。
  • ワイルドカード(_)。 なんでもマッチするけど => の右側の式で使えない。
  • 定数識別子。よくわかんない。
なんかHaskellチックだ。想像の通り、書かれた順に評価され、最初にマッチしたら以降は無視される。一つもマッチしないと例外がスローされる。

パターンマッチと匿名関数
ここでようやく、Actorのパターンマッチの謎が解けた。
Case式は常にmatchと一緒に現れるはずだが、単独で使える場合もあるらしい。それがCase式のブロックで、こんなようなもの。
{ case P1 => E1 ... case Pn => En }
これってActorのとこで出てきたやつだよね。ブロック引数に対してパターンマッチするらしい。つまりこれは、以下の匿名関数と等価。
(x => x match { case P1 => E1 ... case Pn => En })
謎が半分解けてすっきり。後はブロック引数の仕様がわかれば完璧。

コメント