PartialFunction

scalaのソースを読んでたら「PartialFunction」というモノが出てきて気になったのでちょっと調べてみました。
「partial」というのは「部分的な」という意味だそうです。てことは「PartialFunction」は「部分的な関数」という意味なのかな。

関数クラスを継承したクラス

PartialFunctionはFunction1クラスの子クラスです。Function1クラスっていうのは、「引数が1つの関数」クラスです。同じように「引数が2つの関数」クラスはFunction2というクラスで、これがFunction3、Function4…と続きFunction22まであるみたいです。
関数クラスにはapplyというメソッドが定義されています。自分自身(関数)を実行するメソッドなのだと思います。

変数 sum に 関数を代入するときは

var double = (i:Int) => i * 2

このように書くのですが、回りくどい書き方をすると

// Function1 を実装したクラス DoubleFunction を定義する
object double extends AnyRef with Function1[Int,Int] {
  override def apply(i:Int):Int = i * 2
}

こんな風になるわけですね

部分的な関数

本題のPartialFunctionですが、これはFunction1クラスを継承したクラスで、「ある特定の引数に対しては未定義」な関数を表しているみたいです。
「引数が1とか2だったらちゃんと値を返すけど、100とか200みたいな値を渡されたらどうなるかわかんないよー」みたいな感じなのでしょうか、中途半端でやる気のない印象です。

isDefinedAt メソッド

PartialFunction には isDefinedAt という抽象メソッドがあります。PartialFunctionを実装するクラスはこのメソッドを実装して、自分がどのような引数に対して真面目に働くのかを表明します。

// PartialFunction を実装したクラス DoubleFunction を定義する
object double extends AnyRef with PartialFunction[Int,Int] {
  // 引数を2倍した値を返す
  override def apply(i:Int):Int = i * 2
  // 引数がマイナスの数だったら働きたくない
  override def isDefinedAt(i:Int):Boolean = i < 0
}

orElse メソッド

PartialFunctionはなんだか中途半端でやる気のない穀潰しのような存在にしか見えないのですが、 このクラスには orElse というメソッドがあります。このメソッドがイメージの悪い PartialFunction をちょっとカッコよくみせています。
orElseというのは、PartialFunction同士をつなげて力を合わせるメソッドです。部分的にしか機能しなかったろくでなしの関数たちが、お互い補完しあって協力する関数を作ることができます。

var superPf = (pf1 orElse pf2 orElse pf3)

上のようにPartialFunction同士を orElse で繋ぐと新しいPartialFunctionを返します。
こうして生まれたスーパーPF関数に引数を渡して実行すると、まずpf1でその引数に対応できるかをisDefinedAtメソッドで調べます。その結果pf1では処理できない引数だったとしても、後ろにはpf2が控えています。pf2でも無理な場合はpf3を…という風に、一人の力では値を返すことができなくても、みんなで力を合わせたら値を返すことができるかもしれない。という素敵な関数だったのでした。

MapもListもPartialFunction

調べてて気がついたのですが、MapクラスとかListクラスはPartialFunctionを実装したクラスでした。
「MapとListが関数とはどういうことだ?」と思ったのですが、よく考えてみるとListはインデックス、Mapはキーという引数を渡して値を返すのだから、なるほど関数みたいなもんなのかー。

試しに何か作ってみよう

練習問題として、リストとかマップにデフォルト値を設定するための関数をPartialFunctionで作ってみました。

class DefaultResult[R](default:R) extends AnyRef with PartialFunction[Any,R] {
  override def apply(param:Any):R = default
  override def isDefinedAt(param:Any) = true
}

object DefaultResult {
  def apply[R](default: R): DefaultResult[R] = new DefaultResult(default)
}
( List(1,2,3) orElse DefaultResult(-1) )(1) // 2
( List(1,2,3) orElse DefaultResult(-1) )(4) // -1

うぉー! 動いた! 動いた! 嬉しい!!