言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

Scala の Extractor について調べる。

私はパターンマッチ大好きです。PEG も好きだし、HaskellErlang のパターンマッチも好きだし、オブジェクト指向言語のメソッドディスパッチもパターンマッチみたいな物です。しかし最近自家製 lisp しか触っていなくて、何をするにも car と cdr で大変フラストレーションが溜まっています。そこでちょっと変わった所で Scala のパターンマッチについて調べてみました。

Scala の使い方

いつの間にか私の macportsscala が入っていたのでそれを使います。コマンドライン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 より良いんじゃないかと思います。ただ、文法はちょっとださいですね。