Line 1: Error: Invalid Blog('by Esehara' )

または私は如何にして心配するのを止めてバグを愛するようになったか

>> Zanmemo

あと何かあれは 「esehara あっと じーめーる」 か @esehara まで

カーリー化、部分適用、クロージャという間違いやすい三種についての簡単なメモ書き

そもそもカーリー化とは何か

複数の引数を取る関数は、一つの引数を取る、関数を返す関数の連続として表現できるということ、と言葉で表現しても抽象的すぎるので、ちょっと式で表してみる。

まず初めにラムダの導入

例として、ある整数に対してプラス1する関数を定義する。このような関数は、{plusone(x) = x  + 1}として表現できる。

ここでこの関数はplusoneという名前を与えられているが、このx + 1という関数そのものを表現するような記法があると便利だろう。そこで、{\lambda}をそのような記法として定義する。

上記の{plusone(x) = x + 1}{plusone = \lambda(x)x + 1}として表現できるようになる。つまり、関数それ自体を表す記法を導入することによって、関数の名前と、関数それ自体を区別することができるようになる。

カーリー化

このような考え方が便利なのは、関数を返す関数というものを表現できるようになるからだ。

例えば、{ladd = \lambda (x).\lambda (y) x + y}という記法は、{ladd(3) = \lambda(y)3 + y}という関数を返すものとしてとらえらる。

このladdは、当然のことながら{add(x, y) = x + y}という、複数の引数を持つ関数としても定義ができる。このとき、{add(3, 5) = ladd(3)(5)}と同じ値を返すものとしてみることが可能である。このように、一つの引数の連続によって、複数引数の関数を表現することが可能になる。

抽象的なので、ちょっとコードで

では上記の文章をコードに実際に書き直すとどうなるか。例えば、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をサンプルとして書きましたが、下の意見もあり:

一方、カリー化(を行う関数)と部分適用(を行う関数)では静的な型が異なるため、静的型付き言語ではこのような勘違いは起きづらいです。カリー化という用語の正しい使い方について調べるときは、動的型付き言語のサンプルコードを用いた解説ではなく、できる限り静的型付き言語(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というのが実際にカーリー化する部分です。ここでも明確なように、カーリー化とは、カーリー化されていない関数を変換することが、型のところからわかり、部分適用は単に引数を渡してあげているということがわかります。

参考書籍

論理と計算のしくみ

論理と計算のしくみ

計算論 計算可能性とラムダ計算 (コンピュータサイエンス大学講座)

計算論 計算可能性とラムダ計算 (コンピュータサイエンス大学講座)

関数プログラミング入門 ―Haskellで学ぶ原理と技法―

関数プログラミング入門 ―Haskellで学ぶ原理と技法―