そもそもカーリー化とは何か
複数の引数を取る関数は、一つの引数を取る、関数を返す関数の連続として表現できるということ、と言葉で表現しても抽象的すぎるので、ちょっと式で表してみる。
まず初めにラムダの導入
例として、ある整数に対してプラス1する関数を定義する。このような関数は、として表現できる。
ここでこの関数はplusone
という名前を与えられているが、このx + 1
という関数そのものを表現するような記法があると便利だろう。そこで、をそのような記法として定義する。
上記のは
として表現できるようになる。つまり、関数それ自体を表す記法を導入することによって、関数の名前と、関数それ自体を区別することができるようになる。
カーリー化
このような考え方が便利なのは、関数を返す関数というものを表現できるようになるからだ。
例えば、という記法は、
という関数を返すものとしてとらえらる。
このladd
は、当然のことながらという、複数の引数を持つ関数としても定義ができる。このとき、
と同じ値を返すものとしてみることが可能である。このように、一つの引数の連続によって、複数引数の関数を表現することが可能になる。
抽象的なので、ちょっとコードで
では上記の文章をコードに実際に書き直すとどうなるか。例えば、Rubyだと下のようになる:
def add(x, y) x + y end ladd = ->(x){->(y){ x + y}} p add(3, 5) p ladd[3][5]
このように、二つの引数であったadd
は、複数のProc
(lambdaみたいなもの)に変換できることがわかる。
部分適用との混合
カーリー化は、その性質上、部分適用との混合が生まれやすい。例えば、次のようなコードは部分適用ということが可能だ。
ladd = ->(x){->(y) {x + y}} plus_one = ladd[1] p plus_one[3]
カーリー化と部分適用の関係は、カーリー化があくまでも「複数の引数を取る関数は、一つの関数を取る関数の連続として定義できる」ということが焦点になっている。そして、重要なのは、カーリー化された結果として、部分適用が可能になるという区別である[要マサカリ]。
クロージャーとはちょっと違うの?
上のコードを見たとき、少しだけ「あれ、これってクロージャみたいなものじゃないの」という感じは、自分の中ではあった。
確かに、カーリー化自体は、クロージャによって実現することが可能であるとは言える。実際に、例示されたコードはクロージャ(つまり内部の引数のスコープを後続の関数が引き継ぐ)であることは間違いないのだが、あくまでもカーリー化はそういうことができる、ということであって、それをどう実装するか、ということとは別問題であると思う[要マサカリ]。
Rubyにおけるカーリー化
Rubyにおいては、既にProc#curryというのメソッドが存在しているため、先ほどの関数(正確にいうとRubyは全部メソッドなので……という面倒臭い言い訳がいる)も、カーリー化することが容易だ。
def add(x, y) x + y end ladd = method(:add).to_proc.curry p ladd[3][5]
まとめ
最近、計算論を勉強していて、ちょっとこのあたりについてそういえば自分も混合していたところがあったなあと思ったので、簡単にメモをしておいた。もし何かの参考になれば幸いである。
合わせて読みたい
- 「関数型Ruby」という病(3) - カリー化(Proc#curry, Proc#flip) - ( ꒪⌓꒪) ゆるよろ日記
- Rubyでのカリー化、をまじめに。 - DT戦記(zonu_exeの日記)
蛇足
ちなみに今回はRubyをサンプルとして書きましたが、下の意見もあり:
一方、カリー化(を行う関数)と部分適用(を行う関数)では静的な型が異なるため、静的型付き言語ではこのような勘違いは起きづらいです。カリー化という用語の正しい使い方について調べるときは、動的型付き言語のサンプルコードを用いた解説ではなく、できる限り静的型付き言語(Haskell, OCaml, Standard ML)を用いた解説を読む事をお勧めします。
この辺りを意識するといいかもしれません(逆に言うと、Rubyのような言語だと、このような区別に対して無頓着になりがちであるというところなのでしょう)。
Haskellはもともとカーリー化されているので、自分的には説明しにくい感じもありますが、試しにコードを書いてみると、下のようにかけるでしょう:
-- Preludeのソースコードより -- https://hackage.haskell.org/package/base-4.7.0.2/docs/src/Data-Tuple.html#curry curry :: ((a, b) -> c) -> a -> b -> c curry f x y = f (x, y) add :: (Integer, Integer) -> Integer add (x, y) = x +y plus_one :: Integer -> Integer plus_one = my_curry add 1
plus_oneのところが、実際に部分適用しているところで、curryというのが実際にカーリー化する部分です。ここでも明確なように、カーリー化とは、カーリー化されていない関数を変換することが、型のところからわかり、部分適用は単に引数を渡してあげているということがわかります。
参考書籍
- 作者: 萩谷昌己,西崎真也
- 出版社/メーカー: 岩波書店
- 発売日: 2007/06/27
- メディア: 単行本
- 購入: 14人 クリック: 442回
- この商品を含むブログ (36件) を見る
計算論 計算可能性とラムダ計算 (コンピュータサイエンス大学講座)
- 作者: 高橋正子
- 出版社/メーカー: 近代科学社
- 発売日: 1991/08
- メディア: 単行本
- 購入: 8人 クリック: 160回
- この商品を含むブログ (36件) を見る
- 作者: Richard Bird,山下伸夫
- 出版社/メーカー: オーム社
- 発売日: 2012/10/26
- メディア: 単行本(ソフトカバー)
- 購入: 3人 クリック: 28回
- この商品を含むブログ (4件) を見る