Example7.2

7.2 パターンマッチング (Pattern Matching)

パターンマッチングは 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 は式の中で他には使われていない新規の変数、と見ることができます。


名前:
コメント:

タグ:

+ タグ編集
  • タグ:

このサイトはreCAPTCHAによって保護されており、Googleの プライバシーポリシー利用規約 が適用されます。

最終更新:2011年02月24日 08:40
ツールボックス

下から選んでください:

新しいページを作成する
ヘルプ / FAQ もご覧ください。