DT戦記: 2011-09-10 カリー化ってなぁに? をRubyから。では、カリー化とは何かといふことを、かなり廻りくどいやりかたで説明しました。廻りくどいだけで、これ自体は別に悪いアプローチではないのですが。
この記事でも前回の記事と同じく、Rubyのラムダ式、つまり->(arg){ ... }
や lambda{ |arg| ... }
で生成した Proc(lambda)クラスのオブジェクトを「函数」と呼ぶことにします。
前回のコードをひとつ引用してみます。
f1 = ->(x, y){ return x + y } f2 = ->(x){ return ->(y){ return x + y } }
このカリー化されたコードの問題は、「値をふたつとって、足した値を返したい」って明確な目的があるのに、f2ではxとyを分けて書かざるを得ず、ばらばらになってコードが読みにくくなることが挙げられます。2引数のこの例ならまだともかく、5個の引数を、あるいは10個の引数を要求する函数が必要な場合は……?
func = ->(a){ ->(b){ ->(c){ ->(d){ ->(e){ ->(f){ ->(g){ ->(h){ ->(i){ ->(j){ a + b * c - d * f + g / h - i * j }}}}}}}}}}
やめて! と叫びたくなりますね。Rubyではreturn
が必須ではないので省きましたが、それでもこんな量に……。
この場合は、部分適用が必要でなければ、非カリーな函数を作った方が見通しは良くなる気がします。
func = ->(a, b, c, d, e, f, g, h, i, j){
return a + b * c - d * f + g / h - i * j
}
これならふつうに見やすいですね。
どうやらこの問題は、Rubyでの「カリー化函数を書く記法の煩雑さ」が元凶のような気がします。
その点で、F#では(おそらくOCamlも互換)、こんな風にコードを書くことができます。
let curried_func a b c d e f g h i j = a + b * c - d * f + g / h - i * j let uncurry_func (a, b, c, d, e, f, g, h, i, j) = a + b * c - d * f + g / h - i * j
上がカリー化函数、下が非カリー函数*1です。下の場合はタプル(Rubyに同様の型はないが、配列がほぼ同じ目的に利用できる)で渡すようになってるので、カリー化されてない函数を書く方が煩雑なのです。
よって、カリー化函数を書くためのコストは言語の設計に依存することがわかります。
ではRubyでカリー化函数のメリットを十分に享受することはできないのか? 答は否。Rubyではもっと簡易にカリー化函数を利用できる仕組みが提供されてゐるのです。
func = ->(a, b, c, d, e, f, g, h, i, j){ return a + b * c - d * f + g / h - i * j } curried_func = func.curry curried_func.call(2).call(8).call(2).call(8)
あらふしぎ、.curry
を噛ませるだけでfuncが部分適用可能な函数(=カリー化函数)になっちゃいました。何が起こってるのかわからないって方は、自分で引数の少ない簡単な函数を作ってみて、動かして試してみてくださいな。
……実はここが数日前のカリー化議論に関ってくるポイントで、Rubyのこのcurryメソッド(ラムダ式{=この場では「函数」}はProcオブジェクトなので、Rubyの流儀ではこのメソッドは Proc#curry と表記します)は、函数(Procオブジェクト)を部分適用可能な状態に変換します。
ところがGroovyといふ言語のcurryメソッド(Closure#curry)は、Groovy: カレー化クロージャーによるファンクショナル・プログラミングのように*2、「函数を部分適用可能な状態にする」のではなく、「函数を部分適用する」操作なのです。誤用だ誤用だ!
import functools def xyz(x,y,z): return x + y + z functools.partial(xyz, 1, 2)(3) functools.partial(xyz, 1)(2, 3)
Pythonではこんなコードが書けます。これは "partial" の名前通り、カリー化ではなく、飽くまで部分適用です。functional.partial
に一つしか引数を渡さなかったときに2引数の函数が帰ってくることから、これがカリー化でないことは明らかですね。
あとはC++にあるboost::bind は部分適用らしいですが、使ったことがないのでよくわからないでs…ああっ囲まないで! 囲まないで! 闇が!? ……けふんけふん。C++0xではC++11では言語組込みで std::bind があったりラムダ式があったり、なかなか素敵かもしれません。
まとめ
前回のぶんと併せて。
おまけ
curry = ->(f){ args = [] return rec = ->(a){ args << a return f[*args] if (args.length == f.arity) return rec } } if __FILE__ == $0 func = ->(a,b,c){ a * b * c } puts func[1,2,3] puts curry[func][1][2][3] end
Proc#curry への反逆。自分でやってみたかっただけ。やってみただけ。適当。