Edited at

MapにFunction1/PartialFunctionがmix-inされている理由(美しい・・)

More than 3 years have passed since last update.


はじめに

Mapのscaladocを見ていた時、継承しているもの一覧中に異質なものを見つけました。

他のはなんとなくわかると思うのですが、Function1? PartialFunction?? んんん??

scala.png


Function1とPartialFunctionおさらい

Function1は引数一個を取る関数を表します。

以下、数字を文字列に変換するFunction1の宣言例です。

val int2String: Function1[Int, String] = _.toString

次にPartialFunctionですが、Function1を継承しています。

trait PartialFunction[-A, +B] extends (A) ⇒ B

scaladocに書かれている説明では「総称型Aのすべての値を満たすわけではない単項の関数」とあります。

以下、PartialFunctionの宣言例です。

val pf: PartialFunction[Int, String] = { case 1 => "one" }

orElse(やandThen/compose)関数を使って関数合成が行えます。

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発生

なお、上記はお馴染みのこれと同義ですね。

n match {

case 1 => "one"
case 2 => "two"
}


MapにFunction1/PartialFunctionがmix-inされている理由

もうお気づきだと思いますが、要はMapからキーを指定して値を取り出す処理というのが、引数一つを与えて結果を返すFunction1[K,V]/PartialFunction[K,V]だよねってことですね。

val map = Map(1->"one", 2->"two")

map(1) // one

ということは・・・

やはり!Listにも同様にmix-inされていました。

scala.png

但しこちらはFunction1[Int, A]/PartialFunction[Int, A]で、入力は常にInt型で固定されている、というのがMapとの違いです。

興味深いですね。

val list = List("zero", "one", "two")

list(0) // zero


何が嬉しいのか?

お馴染みの関数達の中にはFunction1やPartialFunctionを引数に取るものが多いため、うまくいけばMap/Listだけで代用出来るようになり、より簡潔に記述できるようになります。


map関数

map関数は引数にFunction1を取ります。


before

戻り値はList(one, two, three, unknown)となります。

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"
}


after

mapはすべての要素について写像処理を行うため、マッチしないものがないようorElseで関数合成してMapを補足してやります。

List(1,2,3,4) map { Map(1->"one", 2->"two", 3->"three") orElse { case _ => "unknown" } }


collect関数

collect関数は引数にPartialFunctionを取ります。

この関数の振る舞いはfilter + mapを1度に行うもので、PartialFunctionを満たさないものは処理されません。


before

用意していない4は処理されず、戻り値はList(one, two, three)となります。

List(1,2,3,4) collect { 

case 1 => "one"
case 2 => "two"
case 3 => "three"
}


after

List(1,2,3,4) collect { Map(1->"one", 2->"two", 3->"three") }

Scalaの構造の美しさに大変感動しました。


追記

@gakuzzzz さんに、SetはA=>Booleanをmixinしているという情報をtwitterで教えていただきました

有難うございます。

val acceptable = Set(1,2)

val o = Option(1)
o.exists(acceptable)