minimize

事業拡大のため、新しい仲間を募集しています。
→詳しくはこちら

PartialFunction

Scala を始めて非常にわかりにくい概念の一つに、PartialFunction がある。
「部分関数」と言ってもよくわからないので、ここは一つ普通の関数だと考えてしまおう(いいのかそれで?)。

例えば、List の collect 関数の定義を見ると以下のようになっている。

def collect [B] (pf: PartialFunction[A, B]): List[B]
  [use case]

最初これを見ただけだと、おそらく使い方が全くわからない。
しかし使っているコードを見れば、何となく理解できるようになる。以下のようなコードだ。

List(5, 4, -1).collect { case x => "**" + x }
List(5, 4, -1).collect { case 5 => 10; case x => x }

API に [use case] とあるように、この関数は引数部に case 文を使う。

pf は引数に A(ListのGeneric型)を取り、B(任意の型)を返す。
最初の例だと、Int を取って String を返している。
次の例だと、Int を取って同じく Int を返している。
それぞれの結果は以下になる。

List("**5", "**4", "**-1")
List(10, 4, -1)

collect の引数として定義した匿名関数には、リストの各要素が次々と渡されてくる。
それを case 文で処理した戻り値をつなげたものが最終的な collect の結果となる。
結果を見てわかるように、型を変えることもできるのがイカす。

ここまで読んで、部分関数を意識する必要があっただろうか?
普通の関数として考えていても何の問題も無い(少なくとも初期の段階では)。

ただ一つ、関数の定義がややこしいので再確認しておく。
PartialFunction[A, B] とは、引数に A を取って B を返す関数を意味する。
これだけわかっておけば大丈夫だ。問題ない。

部分関数とは

一応、部分関数と呼ばれる所以についても調べておこう。

List(5, 4, -1).collect { case 5 => 10 }

結果は以下となる。

List(10)

5 以外の値について、この匿名関数は値を返さない。
その場合 collect はこの値を捨てる。結果的に、長さの違う List が返されることになる。
引数に与えられた値のうち、一部にしか対応していない(=全てに対応しなくても良い)のが PartialFunction である。

collect と map の違い

ちなみに List には、collect とよく似た map 関数も定義されている。

def map [B] (f: (A) ⇒ B): List[B]
  [use case]

これは collect と全く同じ使い方ができる。

List(5, 4, -1).collect { case 5 => 10; case x => x }

結果も map と同じだ。

ではどう使い分けるのか。次の文を実行してみよう。

List(5, 4, -1).map { case 5 => 10 }

これは、実行時エラーとなる。
map の引数は通常の関数だから、4という値に対して戻り値が定義されていないこの匿名関数は認められない。
関数は必ず値を返さないといけないのだ。

まとめ

PartialFunction は、条件によって値を返さないことを認められている「ユルい」関数なのだ。