私はパターンマッチ大好きです。PEG も好きだし、Haskell や Erlang のパターンマッチも好きだし、オブジェクト指向言語のメソッドディスパッチもパターンマッチみたいな物です。しかし最近自家製 lisp しか触っていなくて、何をするにも car と cdr で大変フラストレーションが溜まっています。そこでちょっと変わった所で Scala のパターンマッチについて調べてみました。
Scala の使い方
いつの間にか私の macports に scala が入っていたのでそれを使います。コマンドラインで scala と入力するとそのまま実行出来るみたいです。
$ scala scala> 3 + 4 res0: Int = 7
同じ jvm 言語でも clojure と比べてかなりマッタリしてますが、repl が使えるだけ有り難いです。
プログラムを保存するには .scala という拡張子を付けるだけでいいみたいです。
// hello.scala println("Hello, World!\n")
scala コマンドを使うと、これまたまったりしてるが一応動きます。
$ scala hello.scala
コンパイルしたいときはアプリケーションオブジェクトを作らないといけないらしいです。
// HelloApp.scala object HelloApp extends Application { println("Hello, World!\n") }
この object という構文は、クラス定義無しでアドホックにオブジェクトを作る時に使うみたいです。
コンパイルには scalac コマンドをつかいます。また、出来たクラスファイルを実行するのも scala 命令です。
$ scalac HelloApp.scala $ scala HelloApp Hello, World!
これで準備完了。
Case Class を使った パターンマッチング
Case Class というのは構造体みたいなもので、要素をカプセル化しないで持つオブジェクトに最適です。座標を現すクラス Cartesian を作ってみます。しょうもない例しか思い浮かびませんが、直行座標同士の足し算が出来る演算子 + を定義したあとパターンマッチにかけます。x 座標が 4 の時だけ、 x is four! と表示します。
// cartesian.scala case class Cartesian(x: Double, y: Double) { def + (that: Cartesian): Cartesian = Cartesian(this.x + that.x, this.y + that.y) } var p = Cartesian(1, 2) + Cartesian(3, 4) p match { case Cartesian(4, _) => println(p + "'s x is four!") case _ => println("Nothing happens") }
この match 分で変数 p の値 (4,6 のはず) を上から調べてはまる文を実行するわけです。
$ scala cartesian.scala Cartesian(4.0,6.0)'s x is four!
Extractor Objects を使ったパターンマッチング
Scala の面白いのはここからです。オブジェクト指向の利点は実装を隠せる事にありますが、上のように Case Class を使うと実装がまるみえです。例えば極座標のように見えて実際には直交座標で値を保持するオブジェクトを作ってみます。
// Polar conversion object Polar { def apply(r: Double, theta: Double) = Cartesian(r * Math.cos(theta), r * Math.sin(theta)) def unapply(p: Cartesian) = Some(Math.sqrt(p.x * p.x + p.y * p.y), Math.atan2(p.y, p.x)) }
apply と unapply というメソッド名は決まっていて、apply はオブジェクトを作る時、unapply はパターンマッチの時に左辺で使います。object というキーワードを使いますが、ここは単に Polar という関数とその逆関数を定義していると考えた方が分かりやすいです。Polar.apply は二つの引き数を取り一つの値を返すので、その逆関数である Polar.unapply は一つの値を取り二つの値を返すのが自然です。二つの値を返すのには Some(一つ目, 二つ目, ...) のように指定します。Some の代わりに None を返すと、マッチ失敗の意味になります。実際にパターンマッチを書いてみましょう。x 軸から 90 度方向で距離 100 の点を作り、それをパターンマッチで再取得しています。
var p = Polar(100, Math.Pi / 2) p match { case Polar(r, theta) => println("distance: " + r + " degree: " + theta + " implementation: " + p) }
結果はこんなかんじです。
$ scala cartesian.scala distance: 100.0 degree: 1.5707963267948966 implementation: Cartesian(6.123233995736766E-15,100.0)
だいたい上手く行ってます!
Scala のパターンマッチは、オブジェクト指向のカプセル化を守りつつ関数型言語の特徴も生かすために、エクストラクタすなわち「コンストラクタの逆関数」という画期的なアイデアを使ったとても凄い仕組みです。このエクストラクタは、逆の計算が出来るという点では、prolog の述語を彷彿させますし、実行の流れが分かりやすい分 prolog より良いんじゃないかと思います。ただ、文法はちょっとださいですね。
- Case Classes: http://www.scala-lang.org/node/107
- Extractor Objects: http://www.scala-lang.org/node/112