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
- 作者: Martin Odersky,Lex Spoon,Bill Venners
- 出版社/メーカー: Artima Inc
- 発売日: 2008/11/26
- メディア: ペーパーバック
- 購入: 2人 クリック: 44回
- この商品を含むブログ (14件) を見る