Closure(これは古い日記です)

プログラミング言語におけるクロージャの概念について。

いくつかのプログラミング言語にはclosure(閉包)という概念がある。数学的にclosureと言ったら別の意味になるらしいが、Lisp等のプログラミング言語では、これは通称lambdaと呼ばれるもので、多少語弊があるかもしれないが、簡単に言えば無名関数的なものである。

; Lisp
(lambda (x y) (+ x y))

xとyを引数に取り、それらの和を返すclosure。ただの関数と違う点は、それに名前をつける必要がない点である。例えば、Common Lispのsortという関数は、第1引数にソート対象のリスト、第2引数に比較関数を取る。

; Common Lisp
(sort '(3 8 7 2 6) #'(lambda (x y) (< x y)))

Cのqsort()、bsearch()といった関数でも同様、最後の引数に要素を比較するための関数ポインタを取る必要がある。ただ、Cにはclosureの概念がないため、その関数に名前をつけ、どこか別のところに定義しておかなければならない。これは非常に馬鹿げている。上の例のように、引数に与える関数の内容が比較的汎用的なものなら、名前を付けて関数を定義しておくことに意味はあるだろうが、closureを使用したい場合の多くはそうではない。例えるなら、closureの中身は、if文やfor文の中身と同じように、使用したいその場でしか意味を持たない場合が多い。ifやforを使うたびに、ifやforの中身に、わざわざ関数を定義して使う馬鹿はいない。closureを持つ言語から見れば、closureを持たない言語は、それと同じくらい馬鹿げているように思える。

関数型言語等の比較的マイナーな言語はclosureの概念を持っている場合が多いが、メジャーな言語ではこの概念を持っていないものが多い。俺が知っている数少ない言語のうち、この概念を持っているものは、Common Lisp, Scheme, Ruby, Python, Perl だけだ。
ただ、Pythonのlambdaは中身に1つの式しか書けないという制限があるため、実用になるかどうかは疑問である。
Javaでは匿名クラスというものを用いればclosureと似たようなことはできる。
DやC#ではdelegateというものがあるが、これは関数ポインタを抽象化したようなもので、closureではない。(Dのdelegateは動的クロージャと呼ばれているが、これはここで言うクロージャとは別のものだと思う。Dはちょっとしか知らないが、ちょっと触った感じでは、lambdaのようなことはできなかった。)

Common Lispでは、関数は他の変数とは異なる何か特別なものとされているようだが、Schemeではそのような違いはない。Schemeで関数を定義した場合、それは実際には関数名(変数名)にclosureをバインドしていることになる。

; Scheme
(define (f x y) (+ x y))
; 上と下は同じ
(define f (lambda (x y) (+ x y)))

下の構文は、単なる変数定義である、

(define x 99)

と同じ構文である。つまりSchemeにおける関数とは、文字通り、closureに名前をつけただけのものだ。

前の日記で、C++関数型言語のような機能が使える、ということを書いたが、C++にはclosureがないため、Lispのような使い方はできない。
2chのプログラム板のtemplate関係のスレで、次のようなコードが書かれていた。

std::vector<int> v;
// v に適当に要素を追加する...
v.erase(std::remove_if(v.begin(), v.end(), std::bind2nd(std::equal_to<int>(), 2)), v.end()); 

これはvから2と等しい要素を削除する、という意味である。
この後のレスで、そんなコードは意味がわからないとか、こういう場合はループを書きたくなるとか、上のような書き方をすべきでループを書くべきではない、などと論争があったが、確かに上のようなコードはすぐには思いつかないし、俺もループを書きたくなることが多い。
それは何故かと考えていたら、2と等しい要素を削除する、なら上のようにできるが、vの中から(x % 4) == 3を満たすxを削除する、ということが、上のようにはできないということに気付いた。

"等しい"、を表す述語クラス(のテンプレート)としてstd::equal_toは定義されているが、"mod(x)がyと等しい"を表すようなものは標準ライブラリの中には含まれていない。mod(x)がyと等しい要素を削除する、という操作を行いたい場合、それに対応する述語クラス(または単純な関数でも良い)を自分で書かなければならない。ここで書かなければならない関数というものが、まさにループ内に記述したいことそのものなのである。つまり、C++で上の例のような書き方よりもループを記述したくなるのは、C++にclosureがないためである、と言える。

なお、Boostと呼ばれる次期C++の標準ライブラリに追加されるであろうライブラリを使用すれば、lambdaとかcomposeとか、関数型言語っぽい機能が多数使えるようになるが、俺はBoostはほとんど知らないのでここでは説明できない。

言語は思考を制限すると言われる。Cしか知らないような人には、closureの利点は理解できない。関数ポインタって何に使うの?とか言ってるようなレベルの人間は尚更である。従って、closureを知らない人には、closureの概念を持つ言語を学ぶことを勧める。

ちなみに、俺の知識はかなり怪しい部分が多いので、上に書いたことは見る人が見れば間違いだらけかもしれません。ろくに知りもしないでこういう偉そうなことを書くべきじゃないんだろうけど。