[Scala] PartialFunction と Function1

2018/02/23   #Scala 
このエントリーをはてなブックマークに追加

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

このへん? https://github.com/scala/scala/blob/v2.12.4/src/library/scala/collection/immutable/List.scala#L298-L325


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 を実装する感じかな?


Scala の関数 - tkawachi Blog

パターンマッチ匿名関数

SLS 8.5 の Pattern Matching Anonymous Functions には { case i => i + i } と(パターンマッチみたいに case がいくつあってもいい)いう記法も FunctionN オブジェクトになりうる。 「なる」ではなく「なりうる」なのは、型的に FunctionN が要求される箇所においては FunctionN として解釈される一方で、PartialFunction が要求される箇所においては PartialFunction として解釈されるからだ。

メソッドからの変換

メソッド名の後ろに _ を置くことで、メソッドから FunctionN へ変換できる。

また、FunctionN が型として要求される箇所にメソッドを書いても同じように eta expansion される。