コレクションのcollect
メソッドについて考えてみる。
case class User(id: Int, name: String, isAdmin: Boolean)
val users: List[User] = List(
User(1, "taro", true),
User(2, "jiro", false),
User(3, "saburo", true)
)
// isAdminがtrueのユーザのnameプロパティを取得
val names: List[String] = users.collect { case user if user.isAdmin =>
user.name
}
println(names) // => List(taro, saburo)
collect
メソッドはfilter
とmap
を同時にやるようなもの。以下のコードと結果は同じ。
val names = users.filter(_.isAdmin).map(_.name)
collect
メソッドではこれを一発でできる(パターンマッチでキャストとかも一発でできるのでもっと強力)。ただし、通常のパターンマッチだとisAdmin
がfalse
の要素があった場合はMatchError
になってしまう。どうなっているのか?
collect
メソッドの引数を見ると、以下のようにPartialFunction
になっている。
def collect[B, That](pf: PartialFunction[A, B])(implicit bf: CanBuildFrom[Repr, B, That]): That = {
val b = bf(repr)
for (x <- this) if (pf.isDefinedAt(x)) b += pf(x)
b.result
}
特定の引数のみ処理する関数。こんな感じで定義する。このときPartialFunctionの型宣言は省略できないので注意!
val pf: PartialFunction[String, String] = { case "foo" => "bar" }
普通の関数と同じように呼び出せる。単体だとパターンにマッチしない場合はMatchError
になる。
println(pf("foo")) // => bar
println(pf("hoge")) // => MatchError
もちろん複数の条件を記述することもできる。
val pf: PartialFunction[String, String] = {
case "foo" => "bar"
case "hoge" => "fuga"
}
PartialFunctionは合成することができる。
val pf1: PartialFunction[String, String] = { case "foo" => "bar" }
val pf2: PartialFunction[String, String] = { case "hoge" => "fuga" }
// pf1にマッチしない場合はpf2を適用するPartialFunctionを生成
val pf3 = pf1 orElse pf2
println(pf3("foo")) // => bar
println(pf3("hoge")) // => fuga
println(pf3("www")) // => MatchError
引数がパターンにマッチするかどうかはisDefinedAt
メソッドで調べることができる。
val pf: PartialFunction[String, String] = { case "foo" => "bar" }
println(pf.isDefinedAt("foo")) // => true
println(pf.isDefinedAt("hoge")) // => false
Seq[String]とPartialFunctionを受け取り、各要素に対してPartialFunctionにマッチした場合はその戻り値、マッチしない場合は元の要素を格納したSeqを返す関数(以下の例のreplace関数)を作ってみよう。
val langs = Seq("Java", "JavaScript", "Scala")
val result = replace(langs, { case "Scala" => "Cool" })
println(result) // => Seq(Java, JavaScript, Cool)
定義例その1。普通に書くならこんな感じ。
def replace(seq: Seq[String], pf: PartialFunction[String, String]): Seq[String] = {
seq.map { x =>
if(pf.isDefinedAt(x)) pf(x) else x
}
}
カリー化しておくとコレクションのメソッドっぽく関数を渡せるようになる。PartialFunctionじゃなくても関数を受け取るメソッドはカリー化しておくといい感じ。
def replace(seq: Seq[String])(pf: PartialFunction[String, String]): Seq[String] = {
seq.map { x =>
if(pf.isDefinedAt(x)) pf(x) else x
}
}
val result = replace(langs){
case "Scala" => "Cool"
}