ケースクラスとパターンマッチング(2)

パターンマッチングの威力は、昨日見たような型による分岐にとどまらない。というのも各パターンは(ケースクラスがパラメータを持つならば)パラメータを持つこともできるからだ。これでほぼ Haskell のパターンマッチングと同様のことができる。

例として、整数の四則演算を表す構文木を考えよう。各ノードは Exp を継承し(つまりすべては式である)、2項演算を表す部分木(2つの子を持つ)と整数リテラルを表す葉からなる。各クラスの定義はこうだ。

scala> sealed abstract class Exp
defined class Exp

scala> case class Add(lhs: Exp, rhs: Exp) extends Exp
defined class Add

scala> case class Sub(lhs: Exp, rhs: Exp) extends Exp
defined class Sub

scala> case class Mul(lhs: Exp, rhs: Exp) extends Exp
defined class Mul

scala> case class Div(lhs: Exp, rhs: Exp) extends Exp
defined class Div

scala> case class Lit(value: Int) extends Exp
defined class Lit

これを組み合わせて、(1 + ((2 * 3) / 2)) という式の構文木を作るとこうなる。

scala> val example = Add(Lit(1), Div(Mul(Lit(2), Lit(3)), Lit(2)))
example: Add = Add(Lit(1),Div(Mul(Lit(2),Lit(3)),Lit(2)))

さて、ここからが本番。この構文木を評価する評価器(メソッド)を作ろう。

scala> def eval(exp: Exp): Int = exp match {
     |     case Add(l, r) => eval(l) + eval(r)
     |     case Sub(l, r) => eval(l) - eval(r)
     |     case Mul(l, r) => eval(l) * eval(r)
     |     case Div(l, r) => eval(l) / eval(r)
     |     case Lit(v) => v
     | }
eval: (exp: Exp)Int

このパターンマッチングでは、

  1. ノードの種類と構造によって分岐し
  2. ネストしたノードを分解し
  3. 分解した結果を変数に束縛する

ということがいっぺんに行われている。もちろん束縛した変数は評価に使うことができる。

さて、それじゃ上の構文木 example を評価してみよう。

scala> eval(example)
res2: Int = 4

ちゃんと (1 + ((2 * 3) / 2)) の計算結果である 4 が得られた。