読者です 読者をやめる 読者になる 読者になる

seratch's weblog in Japanese

About Scala, Java and Ruby programming in Japaense. If you need English information, go to http://blog.seratch.net/

Scala School意訳(Pattern matching & functional composition)

http://twitter.github.com/scala_school/pattern-matching-and-functional-composition.html

以下は私の方でtypoや表示崩れを直したものです。

https://github.com/seratch/scala_school/blob/master/web/_posts/2011-05-04-lesson.textile

誤訳などありましたら、お手数ですが、ご指摘いただければ幸いです。

Function Composition

二つの便利な関数をつくってみましょう。

scala> def addUmm(x: String) = x + " umm"
addUmm: (x: String)String

scala>  def addAhem(x: String) = x + " ahem"
addAhem: (x: String)String
compose
composeは二番目の関数を呼び出して、次に一番目の関数を呼び出します。
composeはf(g(x))と同じです。

// scala> val ummThenAhem = addAhem(_).compose(addUmm(_))
// ummThenAhem: (String) => String = <function1>
scala> val ummThenAhem = addAhem _ compose addUmm _
ummThenAhem: (String) => java.lang.String = <function1>

scala> ummThenAhem("well")
res0: String = well umm ahem

(訳者追記)
順に実行されるときの前の関数の戻り値を次の関数が受け取れない場合、コンパイルエラーになります。

def plus1(i: Long): Long = i+ 1
def plus2(i: Int): String = (i + 2).toString
val plus3 = plus1 _ compose plus2 _

scala> val plus3 = plus1 _ compose plus2 _
<console>:9: error: type mismatch;
 found   : String
 required: Long
       val plus3 = plus1 _ compose plus2 _
                                   ^

この例は以下のように書き換えるとうまくいきます。plus3では途中に出てくるStringは型にはあらわれなくなっています。

def plus1(i: String): Long = i.toLong + 1
def plus2(i: Int): String = (i + 2).toString
val plus3 = plus1 _ compose plus2 _

scala> val plus3 = plus1 _ compose plus2 _
plus3: Int => Long = <function1>

以下のように暗黙の型変換によってcomposeができるようになるケースもあります。

implicit def fromStringToLong(str: String) = str.toLong
def plus1(i: Long): Long = i+ 1
def plus2(i: Int): String = (i + 2).toString
val plus3 = plus1 _ compose plus2 _

scala> val plus3 = plus1 _ compose plus2 _
plus3: Int => Long = <function1>
andThen
andThenは一番目の関数を呼び出して、次に二番目の関数を呼び出します。
andThenはg(f(x))と同じです。

// scala> val ahemThenUmm = addAhem(_).andThen(addUmm (_))
// ahemThenUmm: (String) => String = <function1>
scala> val ahemThenUmm = addAhem _ andThen addUmm _
ahemThenUmm: (String) => java.lang.String = <function1>

scala> ahemThenUmm("well")
res1: String = well ahem umm
Currying vs Partial Application
Case statements
case文とは一体何でしょうか?それはPartialFunctionと呼ばれる関数のサブクラスです。

複数のcase文の集まりとは何でしょうか?それは合成された複数のPartialFunctionということになります。

Understanding PartialFunction

関数は定義された型について処理を行います。
言い換えると、たとえば(Int) => Stringのように定義されている関数は、任意のInt型の値を受け取り、String型を返します。

部分関数(partial function)はあくまでも指定された型に対する処理であることが定義されているだけです。
それはつまり(Int) => Stringという部分関数は全てのInt型を受け取ることができないかもしれないということです。

isDefinedAtメソッドはPartialFunctionが与えられた引数を受け取ることができるかどうかを判定するのに使われるPartialFunctionのメソッドです。

PartialFunctionは前に出てきた部分適用された関数とは関係ありませんので注意してください。

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

scala> one.isDefinedAt(1)
res0: Boolean = true

scala> one.isDefinedAt(2)
res1: Boolean = false
You can apply a partial function.

scala> one(1)
res2: String = one

PartialFunctionは「orElse」というメソッドを使って合成することができます。
これはPartialFunctionが与えられた引数に対するマッチを定義しているかどうかにも反映されます。

scala> val two: PartialFunction[Int, String] = { case 2 => "two" }
two: PartialFunction[Int,String] = <function1>

scala> val three: PartialFunction[Int, String] = { case 3 => "three" }
three: PartialFunction[Int,String] = <function1>

scala> val wildcard: PartialFunction[Int, String] = { case _ => "something else" }
wildcard: PartialFunction[Int,String] = <function1>

scala> one orElse two orElse three orElse wildcard
res23: PartialFunction[Int,String] = <function1>

scala> res23(5)
res24: String = something else

scala> res23(3)
res25: String = three

scala> res23(2)
res26: String = two

scala> res23(1)
res27: String = one

scala> res23(0)
res28: String = something else

(訳者追記)

PartialFunctionをよく使う例としては、catch節があげられます。

try {
  println("do something")
  throw new RuntimeException
} catch {
  case e: Throwable => println("catched!")
}

val errorHandler: PartialFunction[Throwable, String] = { case e: RuntimeException => {
    println("Runtime!")
    "NG" 
  }
}
val res = try { 
  println("do something") 
  throw new RuntimeException("oops!")
  "OK"
} catch errorHandler 

The mystery of case

先週、私達は面白いものを見たと思います。このように(filterの引数として渡す)関数のところでcase文が使われていました。

scala> case class PhoneExt(name: String, ext: Int)
defined class PhoneExt

scala> val extensions = List(PhoneExt("steve", 100), PhoneExt("robey", 200))
extensions: List[PhoneExt] = List(PhoneExt(steve,100), PhoneExt(robey,200))

scala> extensions.filter { case PhoneExt(name, extension) => extension < 200 }
res0: List[PhoneExt] = List(PhoneExt(steve,100))

これはなぜうまくいくのでしょうか?

filterは関数(Function型)を引数としてとります。このケースでは(PhoneExt) => Booleanという述語関数です。

そして、PartialFunctionはFunction型のサブ型なので、filterはPartialFunctionを受け取ることもできるということなのです!

(訳者追記)
これはつまりfilterが受け取る引数がFunction型であってPartialFunctionはそのサブ型なので、いきなりcaseから書き始められるということを言っています。
引数リストをタプルととらえるなら上記のようなFunction1に限らずあらゆる関数が対象となります。

scala> List(3,1,2) sortWith { (a, b) => a < b }
res57: List[Int] = List(1, 2, 3)

scala> List(3,1,2) sortWith { case (a, b) => a < b }
res58: List[Int] = List(1, 2, 3)