Scala基礎復習勉強会 に PartialFunction
についての説明があった。
事前に「呼び出し可能かチェック可能」な関数
partial function(部分関数)という用語は不適切
PartialFunction
は、
{ case … } 式から自動生成可能
なるほど、何の気なしに書いてたけどこういうことか。
Pattern Matching Anonymous Function(匿名パターンマッチ関数)は、
期待型が FunctionN の場合に適用される
PartialFunction と混同されがち
collect と map
collectは PartialFunction を引数に取る
map は Function1 を引数に取る
このへんをもう少し掘り下げてみる。
MapにFunction1/PartialFunctionがmix-inされている理由(美しい・・) - Qiita
PartialFunctionですが、Function1を継承しています。
trait PartialFunction[-A, +B] extends (A) ⇒ B
scaladocに書かれている説明では「総称型Aのすべての値を満たすわけではない単項の関数」とあります。
PartialFunction
は、以下のような「すべての値を網羅しているわけではない引数を1つとる関数」というイメージか。
val pf1: PartialFunction[Int, String] = { case 1 => "one" }
val pf2: PartialFunction[Int, String] = { case 2 => "two" }
val pf: PartialFunction[Int, String] = pf1 orElse pf2
pf(1) // one
pf(2) // two
pf(3) // MatchError発生
何が嬉しいのか? お馴染みの関数達の中にはFunction1やPartialFunctionを引数に取るものが多いため、うまくいけばMap/Listだけで代用出来るようになり、より簡潔に記述できるようになります。
で、戻って
map は Function1 を引数に取る
List(1,2,3,4) map { n =>
if(n == 1) "one"
else if(n == 2) "two"
else if(n == 3) "three"
else "unknown"
}
※なお、PartialFunctionはFunction1の子なので当然PartialFunctionを渡しても同様の結果を得ることが出来ます。
List(1,2,3,4) map {
case 1 => "one"
case 2 => "two"
case 3 => "three"
case _ => "unknown"
}
複数の PartialFunction の合成でもいける。
List(1,2,3,4) map { Map(1->"one", 2->"two", 3->"three") orElse { case _ => "unknown" } }
List の map では、マッチしないものがないようにしなければ
scala> list map { case 1 => "one" }
scala.MatchError: 2 (of class java.lang.Integer)
at .$anonfun$res0$1(<console>:13)
at .$anonfun$res0$1$adapted(<console>:13)
at scala.collection.immutable.List.map(List.scala:287)
... 29 elided
のような感じに scala.MatchError
になる。
https://github.com/scala/scala/blob/v2.12.4/src/library/scala/collection/immutable/List.scala#L280-L296
一方、collect は、
collectは PartialFunction を引数に取る
で、
List(1,2,3,4) collect { Map(1->"one", 2->"two", 3->"three") }
でも ok
SetはA=>Booleanをmixinしている
なるほど、こういう mix-in をすることで、Option#exists などの (A) ⇒ Boolean
を要求するメソッドにそのまま渡せるようになって便利というわけか。
HashSet などで (A) => Boolean
を実装しているところは以下?
https://github.com/scala/scala/blob/v2.12.4/src/library/scala/collection/GenSetLike.scala#L44
Function1 の apply は abstract def apply(v1: T1): R
なので、trait 内でその apply を実装する感じかな?
パターンマッチ匿名関数
SLS 8.5 の Pattern Matching Anonymous Functions には { case i => i + i } と(パターンマッチみたいに case がいくつあってもいい)いう記法も FunctionN オブジェクトになりうる。 「なる」ではなく「なりうる」なのは、型的に FunctionN が要求される箇所においては FunctionN として解釈される一方で、PartialFunction が要求される箇所においては PartialFunction として解釈されるからだ。
メソッドからの変換
メソッド名の後ろに _ を置くことで、メソッドから FunctionN へ変換できる。
また、FunctionN が型として要求される箇所にメソッドを書いても同じように eta expansion される。