お前に本当のカリー化を見せてやろう、Groovyで
はじめに
思いの外話題になっているようなので、Groovyでちゃんとしたカリー化をする関数を書いてみました。ちょっとタイトルはあれですがお許し下さい。
こんな感じでいいんですよね?(ツッコミ大歓迎)
ここでは、addが引数を3つ取って、加算するクロージャです。メソッドrealCurryはクロージャを引数にとり、カリー化したクロージャを返却します。
add = {a, b, c -> a + b + c } ↓カリー化! curriedAdd = {a -> {b -> {c -> a + b + c}}}
本当はClosure.metaClass.getCurryとかでやりたかったんですけど、どうもdelegateまわりが変な挙動をするのでバッサリ諦めました。あと、都合によりアンカリー化は実装しておりませぬ。
foldLeftで使ってみる
(2011-09-06 追記)
カリー化 != 部分適用 - Scalaとか構文解析についてあれこれ書く日記 に載っていた例でも使えるか試してみました。
使えました。
そもそもカリー化とは?
関数 f が f : (X x Z) -> Z の形のとき、f をカリー化したものを g とすると、g は f : X -> ( Y -> Z ) の形を取る。非カリー化 (uncurrying) とは、これの逆の変換である。
カリー化 - Wikipedia
つまり、引数を一つ与えると新たな関数(クロージャ)が返却されるように変換しているわけですね。これが出来るとパーサコンビネータとか書くのに便利なんですかね?よくわかりません。Haskellさんとかはそもそもすべてこのカリー化された状態を想定して動作しているとか。素敵、さすがHaskellさん。
GroovyのClosure#curry(Object...)は何をやっているのか
GroovyのClosure#curry(Object...)がやっているのは厳密にはカリー化ではなく部分適用と呼ばれるもので、たとえば上記の例の場合、
curriedAdd1 = {a, b, c -> a + b + c}.curry(1)
とすると、
curriedAdd1(2, 3)
のように、元々のクロージャの残りの引数を与えるだけで利用できるような、引数が固定化されたクロージャが返却されるわけです。
この動作はガッツリ部分適用ですね。なのにGroovyではそれをカリー化と呼んじゃっているわけで…それが今回の騒動の引き金になってしまったようです。ただ、Groovyイン・アクションでも「これは厳密にはカリー化じゃないよ、ごめんね」って触れられてるらしい(未確認)ので、確信犯的な感じはします。はい。
おわりに
今回のように言語について熱く議論されるのは個人的には嫌いじゃないので、いいぞもっとやれな状況です。色々な議論が交わされておりますので、詳しくはカリー化と部分適用の違いと誤用 - Togetterを参照して頂けるとよいと思います。これを気にGroovyのコアにもちゃんとした本当のカリー化が導入されることを期待します。
追記
2011-09-06
JIRAに報告してみました。
2011-09-11
JIRAに報告したことにより、当件はv2.0向けのBUGとして検討されはじめたようです。
もしかすると、v2.0v3.0では
// カリー化 assert { a , b , c -> a + b + c }.curry() == { a -> { b -> { c -> a + b + c } } } assert { a , b , c -> a + b + c }(2) == { a , b -> 2 + a + b }
という風に部分適用と本当のカリー化が書けるようになるかもしれません。期待して待ちましょう。
参考リンク・参考書籍
カリー化 - Wikipedia
Currying - Wikipedia, the free encyclopedia
Closure (Groovy 1.8.6)
Groovyイン・アクション
関連リンク
カリー化と部分適用の違いと誤用 - Togetter
ruby-dev jp - [ruby-dev:33676] Suggestion: Proc#curry
カリー化 != 部分適用 - Scalaとか構文解析についてあれこれ書く日記
更新履歴
- 2011-09-06 JIRAに報告 http://jira.codehaus.org/browse/GROOVY-4998
- 2011-09-06 コメントいただいたリンクを関連リンクに追加しつつfoldLeftの例を追加