kmizuの日記

プログラミングや形式言語に関係のあることを書いたり書かなかったり。

Partial function

http://d.hatena.ne.jp/kmizushima/20080930/1222759243のコメント欄より

それ以外の使い道に興味津々ですw

{ case p1 ... }

という形の式は、関数型(A => B, (A, B) => C など)が要求されている箇所で出現した場合、たとえば、

val succ: Int => Int = {
  case x => x + 1
}

の場合、

val succ: Int => Int = (s1: Int) => s1 match {
  case x => x + 1
}

のような、無名関数+パターンマッチ式の形に展開されます。これが通常の場合です。しかし、PartialFunction型(これ自体はscalaパッケージにあるtraitであり、言語組み込みの特別な型ではありません)が要求されている箇所に出現した場合、たとえば、

val succ: PartialFunction[Int, Int] = {
  case x => if x >=0 => x + 1
}

の場合、

val succ: PartialFunction[Int, Int] = new PartialFunction[Int, Int] {
  def apply(x: Int): Int = x match {
    case x if x >=0 => x + 1
  }
  def isDefinedAt(x: Int): Boolean = x match {
    case x if x >= 0=> true
    case _ => false
  }
}

のような、PartialFunctionの無名サブクラスが生成され、その中で、applyメソッドおよびisDefinedAtが定義されます。ここで、isDefinedAtというメソッドが自動的に定義されるのがポイントです。上記のisDefinedAtメソッドの定義を見ればわかるように、isDefinedAtメソッドを呼ぶことで、無名関数の本体を実行せずにパターンにマッチしたかどうかのみをチェックすることができるようになっています。上の例で言うと、succは引数が0以上でなければパターンにマッチしませんから、succ.isDefinedAt(-1)はfalseを、succ.isDefinedAt(0)はtrueを返します。

この機能は、特にActorライブラリで活用されています。たとえば、Actorライブラリには、Actor#receive[R](f: PartialFunction[Any, R]) : Rメソッドという、メッセージの受信およびメッセージに対する処理を行うためのメソッドがありますが、メッセージに対する処理を実行する関数fはPartialFunction型になっています。ここで、パターンにマッチするメッセージを受信したときにのみfを呼び出すということが可能になる点が、fがPartialFunction型であることで嬉しい点です。

たとえば、

actor.receive {
  case i:Int => println(i)
}

とした場合、

actor ! "FOO"

など、パターンにマッチしないメッセージを送った場合、receiveに渡した無名関数は実行されないことになります。