Scalaの関数部分適用とカリー化

Programming In Scalaで、関数の部分適用とカリー化について混乱。

関数の部分適用(Pratially applied functions)は
[Chapter 8 Functions and Closures]という章で紹介されている。

この章では、まず無名関数(function literal)が登場して、その後に部分適用を紹介している。

まず無名関数(filterの例)。

scala> val someNumbers = List(-11,1,2,0,-4,9)     
someNumbers: List[Int] = List(-11, 1, 2, 0, -4, 9)

scala> someNumbers.filter((x:Int)=>x>0)   //無名関数をfilterに渡す
res7: List[Int] = List(1, 2, 9)

scala> someNumbers.filter(_>0)        //同じことを _ を使った省略表記で
res8: List[Int] = List(1, 2, 9)

んで、_は引数を全部取ったことにしますよという説明が続く(斜め読みなので適当)

scala> def sum( x : Int, y : Int, z : Int ) : Int = x + y + z
sum: (Int,Int,Int)Int

scala> val mySum = sum _
mySum: (Int, Int, Int) => Int = <function>

scala> mySum(99,100,101)
res9: Int = 300

ここまでは単なる関数を変数として扱えるだけに見える(_をつけるのがちょっと面倒)。
これだけだとそんなに嬉しくない。で、3つの引数を取る関数sumに、1番目と2番目の引数をあらかじめ適用した1引数関数を生成するには、こんな書き方ができますよという形で部分適用が出てくる。

scala> val mySum199 = sum( 99, 100, _:Int)   //1番目の引数に99を、2番目に100を適用
mySum199: (Int) => Int = <function>

scala> mySum199(101)
res14: Int = 300

val mySum199 = sum( 99, 100, _:Int)と書くとき、 _の型(Int)を省略するとエラーになる。何故だ、、。


とりあえず他の引数を適用した関数も作れる。

scala> val mySum_100_ = sum(_:Int, 100, _:Int)  //2番目の引数だけ適用
mySum_100_: (Int, Int) => Int = <function>

scala> mySum_100_(99,101)
res15: Int = 300

ここまで関数の部分適用。

カリー化については別の章[Chapter 9 Control Abstraction]で紹介されている。この章ではカリー化とnamed parameter(遅延評価)を使えば新しい構文(っぽいもの)が実現できるよという紹介の仕方。

まぁとにかくカリー化を使った部分適用(この書き方が正しいのか?)はこんな感じ。
引数を()に分けていく。

scala> def sumCurried(x:Int)(y:Int)(z:Int): Int = x + y + z //カリー化版
sumCurried: (Int)(Int)(Int)Int

scala> sumCurried(1)(2)(3)
res31: Int = 6

scala> val mySumCurried199 = sumCurried(99)(100)      
<console>:5: error: missing arguments for method sumCurried in object $iw;
follow this method with `_' if you want to treat it as a partially applied function
       val mySumCurried199 = sumCurried(99)(100)
                             ^

scala> val mySumCurried199 = sumCurried(99)(100)_
mySumCurried199: (Int) => Int = <function>

scala> mySumCurried199(101)
res32: Int = 300

2番目の引数だけ適用とかは、この方法ではできないっぽいなぁ。最初から順番に適用していくようだ。
上の例では1番目と2番目の引数を続けて適用して、_もつけてやっと部分適用できた。最初からカリー化用に書いておかなきゃいけなくて面倒だ。

・・・ここで、Scalaでは()の代わりに{}も使えることが明かされる。

scala> println("hello")
hello

scala> println{"hello"}          //反則に思えるw
hello

ただし、{}内には一つの引数しか使えない。

scala> def times(x:Int,y:Int) = x * y
times: (Int,Int)Int

scala> times(7,7)
res37: Int = 49

scala> times{7,7}
<console>:1: error: ';' expected but ',' found.
       times{7,7}
              ^

でもカリー化関数にしておけば上手く使える。

scala> def timesCurried(x:Int)(y:Int) = x * y
timesCurried: (Int)(Int)Int

scala> timesCurried(7){7}
res39: Int = 49

これを利用して見た目ブロック構文が作れそう。

scala> def myIf(x:Boolean)(f:Unit) =     
     | if (x) f
myIf: (Boolean)(Unit)Unit

scala> myIf(100 > 0){println("true!!")}
true!!

scala> myIf(-1 > 0){println("true!!")} 
true!!

最後が明らかにおかしい。これは、{println("true")}部分がmyIf関数に入る前に評価されるため。
true!!という表示はmyIf関数に入る前に既に実行されている。

ここでnamed parameter(遅延評価)登場。named parameterは関数の中で必要に応じて評価される引数。これを使えばmyIf完成

scala> def myIf2(x:Boolean)(f: => Unit) = 
     | if (x) f
myIf2: (Boolean)(=> Unit)Unit

scala> myIf2(100 >0) {println("true!!")}
true!!

scala> myIf2(-1 >0) {println("true!!") }
(なにも表示されない)

引数をnamed parameterにする場合は、普通の(x:Int)という書き方ではなく、(x: => Int)という様に=>を使う。
(上の例では fがnamed parameterで、Unit型を返す。Unitは何も返さないけど・・・)


まとめ:部分適用は_を使う。カリー化関数も使えるけど、最初からカリー化関数にしておく必要があるので若干面倒な気が。構文生成にはカリー化関数+named parameterの合わせ技。

う〜ん、でもそもそも部分適用とカリー化の違いがよくわからない。。


Programming Scala、なんだかちょっと読みにくいなぁ。例が算数の例(足し算とか引き算とか有理数)ばかりなので、現実味があまり感じられないのだろうか・・・。
といいつつ、今回の例は自分も算数ばっかりだ。。

Programming in Scala: A Comprehensive Step-by-step Guide

Programming in Scala: A Comprehensive Step-by-step Guide