突然だけど、実は僕はカリーが大好きなんだ!
だから、Scalaを勉強していて カリー の文字が見えた時、急に親近感が湧いたんだ。
新宿中村屋のインドカリーみたいでしょ。
でも、インドカリーとカリー化は全く関係ないんだよ。
そこだけは 絶対 におさえてね!
そう、今回は カリー化 を語ってみるよ。
JavaScriptでも出てくる話だから、知っている人も多いんじゃないかな?
カリー化とは
カリー化とは Haskell Brooks Curry に因んで名付けられたんだ。
あれ?このHaskellって?そう、なんと、 Haskell という関数型言語も彼に因んで名付けられたんだ。
で、カリー化とは何か?
良く 部分適用 と同時に語られているんだけど、同じとして語られていることがあったり
説明しているサイト毎にちょっとずつ違ってたりしていて混乱しやすいんだよね。
かく言う僕も、以下の理解で一旦落ち着いていた。
カリー化とは、 部分適用をした関数を返すことができる関数を定義すること
と思ったら、なんとこれが間違っているみたいなんだ!
これはカリー化と部分適用を区別できていない間違いだったんだ。。。
改めてカリー化
複数の引数を取る関数を、1つの引数を取る関数のチェーンに変換すること。
色々調べて考えた結果、これが一番腹落ちしました。
言葉だけだとわからないでしょ?
ソースコードを見てみないと何のことやら全く分からないと思うので
次から説明してみるよ。
複数の引数を取る関数
まず 複数の引数を取る関数 を用意してみる。
scala> val add = {(x:Int, y:Int, z:Int) => x + y + z}
add: (Int, Int, Int) => Int = <function3>
この関数add
はx, y, z
という3つのInt
の引数を受取り、足し合わせた結果をInt
で返す関数だ。
ちなみにadd: (Int, Int, Int) => Int = <function3>
は
[関数名]: (引数) => (戻り値) <functionトレイト>
ということで、<functionトレイト>
は引数の数によってfunctionN
のN
の数字が決まる。
関数add
であれば引数が3なので、function3
となっている。
1つの引数を取る関数のチェーンに変換
次に用意した関数を 1つの引数を取る関数のチェーンに変換 してみる。
scalaにはcurried
というメソッドが用意されているんだね。
scala> val addCurried = add.curried
addCurried: Int => (Int => (Int => Int)) = <function1>
これでカリー化ができちゃいました!
カリー化した関数をaddCurried
に束縛しています。
カリー化した関数を使う
カリー化した関数を使ってみよう!
チェーン を意識して、1個ずつ見てみるよ。
まず関数addCurried
に1つ引数を与えてみよう!
scala> val addCurriedWithX = addCurried(1)
addCurriedWithX: Int => (Int => Int) = <function1>
続けて2つ目の引数を与えてみる。
scala> val addCurriedWithXY = addCurriedWithX(2)
addCurriedWithXY: Int => Int = <function1>
最後に3つ目の引数を与えるよ!
scala> addCurriedWithXY(3)
res2: Int = 6
値が返ってきた!
3つの引数を与え終わったので、やっと関数が評価されたんだ。
ちなみにこんな使い方もできるよ。
scala> addCurried(1)(2)(3)
res1: Int = 6
Scalaでの一般的なカリー化の実現
そういえばScalaには、curried
メソッドよりも有名なカリー化がありました。
以下のように引数を複数の括弧にバラすとカリー化です。
scala> def addScalaCurried(x:Int)(y:Int)(z:Int) = x + y + z
addScalaCurried: (x: Int)(y: Int)(z: Int)Int
ただこれじゃcurried
メソッドを使った場合と同じかちょっとわかりにいくと思います。
なのでこうしてみる。
scala> val addScalaCurriedFunc = addScalaCurried _
addScalaCurriedFunc: Int => (Int => (Int => Int)) = <function1>
これって先程作ったaddCurried
関数と同じだね。
こっちのカリー化の方が一般的だと思います。
部分適用
複数の引数を取る関数に対して、一部の引数に値を束縛した関数を返すこと。
ソースで見てみよう。
まず関数を定義する。
scala> def addPartial(x:Int, y:Int, z:Int) = x + y + z
addPartial: (x: Int, y: Int, z: Int)Int
この関数に部分適用をしてみる。
今回は第3引数を部分適用し、第1・第2引数を受ける関数をaddPartialWithX
に束縛してみた。
scala> val addPartialWithX = addPartial(_:Int, _:Int, 5)
addPartialWithX: (Int, Int) => Int = <function2>
そして最後に、関数addPartialWithX
に2つ引数を与えて評価してみる。
scala> addPartialWithX(2, 3)
res3: Int = 10
カリー化は関数のチェーンだったけど、部分適用はいきなり第3引数を部分適用なんてこともできる。
あとどの引数にも部分適用していない関数もできる。
その場合は_
を使うんだ。
scala> val addPartialNone = addPartial _
addPartialNone: (Int, Int, Int) => Int = <function3>
カリー化と部分適用、なんか全然違うものに見えてきました。。。
カリー化した関数と部分適用した関数は、結果として評価が同じになったけど、かなり違うモノですね。
まとめ
カリー化を語ってみたけど、書いている途中に部分適用との違いに悩みまくったよ。
関数そのもの考え方をもっと勉強しないといけないことに、気づいてしまいました。。。
でもカリー化の利点って、関数を段階的に変換できる所なんじゃないかなと思っている。
あとは感覚的だけど、builderパターンとかの作り上げるパターンに似ている気がする。
以下のような、先にクライアントとかデータソースとかを用意して
あとは使い回したいケースで使い易いのかな。
- HTTP通信
- DBアクセス
君は
ナン派?ライス派?
僕は ライス派 だけどね。