Skip to content

Instantly share code, notes, and snippets.

@takezoe
Created December 20, 2014 15:21
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save takezoe/29ebbd5575b6fcdded3b to your computer and use it in GitHub Desktop.
Save takezoe/29ebbd5575b6fcdded3b to your computer and use it in GitHub Desktop.
ScalaのPartialFunction

ScalaのPartialFunction

コレクションのcollectメソッド

コレクションの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メソッドはfiltermapを同時にやるようなもの。以下のコードと結果は同じ。

val names = users.filter(_.isAdmin).map(_.name)

collectメソッドではこれを一発でできる(パターンマッチでキャストとかも一発でできるのでもっと強力)。ただし、通常のパターンマッチだとisAdminfalseの要素があった場合は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とは?

特定の引数のみ処理する関数。こんな感じで定義する。このとき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"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment