7.2 パターンマッチング
パターンマッチングは C や Java の switch 文をクラス階層に一般化したものです。switch 文の代わりに標準メソッド match があり、それは Scala のルートクラス Any で定義されていて、したがって全てのオブジェクトで使用できます。match メソッドは引数として複数のケースを取ります。たとえば、次はパターンマッチングを用いた eval の実装です。
def eval(e: Expr): Int = e match { case Number(n) => n case Sum(l, r) => eval(l) + eval(r) }
この例では,2つのケースがあります。各ケースはパターンと式を関連付けます。パターンはセレクタ値 e に対してマッチされます。この例の最初のパターンでは、Number(n) は形式 Number(v)、ただし v は任意の値、のすべての値にマッチします。この場合、 パターン変数 n は値 v に束縛されます。同様に、パターン Sum(l,r) は形式 Sum(v1,v2) のすべてのセレクタ値にマッチし、パターン変数 l と r を v1 と v2 へそれぞれ束縛します。
一般的に、パターンは次項より構成されます
ケースクラスのコンストラクタ、たとえば Number, Sum などであり、その引数もまたパターン
パターン変数、たとえば n, e1, e2
「ワイルドカード」パターン _
リテラル、たとえば 1, true, "abc"
定数識別子、たとえば MAXINT, EmptySet
パターン変数は常に小文字で始まりますが、それは大文字で始まる定数識別子と区別するためです。各変数名は一つのパターンに1回だけ登場できます。たとえば Sum(x, x) は正しくないパターンですが、それはパターン変数 x が2回登場するからです。
パターンマッチングの意味 パターンマッチング式
e match { case p1 => e1 ... case pn => en }
は、パターン p1,...pn に、書かれた順番で、セレクタ値 e に対してマッチします。
コンストラクタパターン C(p1,...,p2) は型 C (あるいはそのサブタイプ) であり、パターン p1,...pn にマッチする、C の引数で生成されるすべての値でマッチします。
変数パターン x は任意の値にマッチし、変数名をその値に束縛します。
ワイルドカードパターン '_' は任意の値にマッチしますが、名前をその値に束縛しません。
定数パターン C は C と (==の意味で) 等しい値にマッチします。
パターンマッチング式は、セレクタ値が最初にマッチするパターンの、ケースの右辺へと書き換えられます。パターン変数への参照は、対応するコンストラクタ引数で置き換えられます。もしどのパターンにもマッチしなければ、パターンマッチ式は MatchError 例外でアボートされます。
Example 7.2.1 プログラム評価の置き換えモデルはきわめて自然にパターンマッチングへ拡張できます。たとえば次は、簡単な式に適用された eval がどの様に書き換えるかを示します。
eval(Sum(Number(1), Number(2)))
-> (適用を書き換え)
Sum(Number(1), Number(2)) match { case Number(n) => n case Sum(e1, e2) => eval(e1) + eval(e2) }
-> (パターンマッチを書き換え)
eval(Number(1)) + eval(Number(2))
-> (最初の適用を書き換え)
Number(1) match { case Number(n) => n case Sum(e1, e2) => eval(e1) + eval(e2) } + eval(Number(2))
-> (パターンマッチを書き換え)
1 + eval(Number(2))
->∗ 1 + 2 -> 3
パターンマッチングとメソッド 前の例では、パターンマッチングを、マッチするクラス階層の外で定義された関数で使用しました。もちろん、そのクラス階層自身の中でもパターンマッチングを定義できます。たとえば eval を基底クラス Expr のメソッドとして定義しても、パターンマッチングをその実装で使えます。
abstract class Expr { def eval: Int = this match { case Number(n) => n case Sum(e1, e2) => e1.eval + e2.eval } }
演習 7.2.2 整数の木を表現する次の定義について考えなさい。この定義は IntSet の別表現とみることもできます。
abstract class IntTree case object EmptyTree extends IntTree case class Node(elem: Int, left: IntTree, right: IntTree) extends IntTree
次の IntTree の関数 contains と insert の実装を完成させなさい。
def contains(t: IntTree, v: Int): Boolean = t match { ... ... } def insert(t: IntTree, v: Int): IntTree = t match { ... ... }
パターンマッチング無名関数 ここまでケース式は常に match 操作と一緒に表れました。しかしケース式だけでも使用できます。次のようなケース式ブロック
{ case P1 => E1 ... case Pn => En }
は、それ自身が引数をパターン P1,...Pn にマッチさせ、E1,...,En のどれか一つの結果を生み出す関数 (もしどのパターンにもマッチしなければ、関数は代わりに MatchError 例外を投げます) 、と見ることもできます。別の言い方をすれば、上の式は、無名関数
(x => x match { case P1 => E1 ... case Pn => En })
の短縮形、ただし x は式の中で他には使われていない新規の変数、と見ることができます。