クロージャ

クロージャ（クロージャー、英語: closure）、関数閉包はプログラミング言語における関数オブジェクトの一種。いくつかの言語ではラムダ式や無名関数にて利用可能な機能・概念である。引数以外の変数を実行時の環境ではなく、自身が定義された環境（静的スコープ）において解決することを特徴とする。関数とそれを評価する環境のペアであるともいえる。この概念は少なくとも1960年代のSECDマシンまで遡ることができる。まれに、関数ではなくとも、環境に紐付けられたデータ構造のことをクロージャと呼ぶ場合もある。クロージャをサポートする言語によるプログラミングでは、単に関数の中に関数を定義することができるだけでなく、その際に、外側の関数（エンクロージャ）で宣言された変数を暗黙的に内側の関数に取り込んで操作することができる。主な利点としてはグローバル変数の削減やコールバック関数記述の簡素化が挙げられる。 典型的にはクロージャは、エンクロージャの内側の関数リテラルや、ネストした関数定義によって必要になる。プログラミング言語により、そのような内側の関数内に出現する自由変数（内側の関数の仮引数でもなく、内側の関数自身のローカル変数でもない変数）の扱いは異なるが、自由変数をレキシカルに（字句的に）参照するのがクロージャである。エンクロージャが実行された際、クロージャが形成される。クロージャは内部の関数のコードとエンクロージャのスコープ内の必要なすべての変数への参照からなる。 クロージャはプログラム内で環境を共有するための仕組みである。レキシカル変数はグローバルな名前空間を占有しないという点でグローバル変数とは異なっている。またオブジェクト指向プログラミングにおけるオブジェクトのインスタンス変数とは、オブジェクトのインスタンスではなく関数の呼び出しに束縛されているという点で異なる。 クロージャは関数型言語では遅延評価やカプセル化のために、また高階関数の引数として広く用いられる。 例： クロージャを使ったカウンタの例を Scheme で示す。 (define (new-counter) (let ((count 0)) (lambda () (set! count (+ count 1)) count))) (define c (new-counter)) (display (c)) ; 1 (display (c)) ; 2 (display (c)) ; 3 関数 new-counter の中でクロージャが使用されている。c に代入された無名関数は new-counter 内のローカル変数 count を参照している。c を呼び出すたびに count はインクリメントされていく。 クロージャには多くの用途がある。 ライブラリの設計者は、関数（コールバック関数）を引数として受け取る関数（高階関数）を定義することで、利用者が挙動をカスタマイズできる汎用的なライブラリ関数を提供することができる。その際、クロージャを高階関数の引数として渡すことで、記述の簡素化や高階関数の外側の状態の参照が可能となる。例えばコレクションのソートを行う関数は、比較関数を引数に渡すことで、利用者が定義した基準でソートできるようになるが、クロージャを使うことでさらに自由度の高い比較処理を簡潔に記述することができるようになる。 クロージャは遅延評価される（呼び出されるまで何も実行しない）ので、制御構造の定義に用いることができる。例として、Smalltalk の分岐 (if-then-else) や繰り返し (while、for) を含むすべての標準制御構造は、クロージャを引数にとるメソッドを持つオブジェクトを利用することで定義されている。同様な方法で利用者は自作の制御構造を簡単に定義できる。 遅延評価される引数のように、その値を求めるためのものは揃っているが、まだ値自体は計算されていない、というものを記憶しておくために、追加の引数を持たないクロージャのようなデータ構造を使う。これをサンク（thinkの過去形）という。ALGOL 60の名前渡しの実装において考案された。 Schemeは完全な静的スコープのクロージャを持つ最初の言語として登場した。Common Lispはそれを取り入れた。実質的にすべての関数型言語（Scala、Haskell、OCamlなど）とSmalltalkに由来するオブジェクト指向言語は何らかの形でクロージャを持っている。 クラスを使用するオブジェクト指向言語では、完全なクロージャになるにはメソッドの中でクラス定義できることが必要だが、メソッドあるいは関数の中でラムダ式／無名関数が使え、その中から外のローカル変数を読み書きできれば、一般的にはそのプログラミング言語はクロージャを使えるとみなされる。よって、クロージャを持つ言語には、C#（3.0以降）、C++（C++11以降）、ECMAScript（JavaScriptを含む）、Groovy、Java（8以降）、Perl、Python、Ruby、PHP（5.3以降）、Lua、Squirrelなどがある。 セマンティクス[要曖昧さ回避]はそれぞれ大きく異なっているが、多くの現代的な汎用のプログラミング言語は静的スコープとクロージャのいくつかのバリエーションを持っている。 言語ごとにスコープのセマンティクスが異なるように、クロージャの定義も異なっている。汎用的な定義では、クロージャが捕捉する「環境」とは、あるスコープのすべての変数の束縛の集合である。しかし、この変数の束縛というものの意味も言語ごとに異なっている。命令型言語では、変数は値を格納するためのメモリ中の位置と束縛される。この束縛は変化せず、束縛された位置にある値が変化する。クロージャは束縛を捕捉しているので、そのような言語での変数への操作は、それがクロージャからであってもなくとも、同一のメモリ領域に対して実行される。例として、ECMAScriptを取り上げると var f, g; function foo() { var x = 0; f = function() { x += 1; return x; }; g = function() { x -= 1; return x; }; x = 1; console.log(f()); // "2" } foo(); console.log(g()); // "1" console.log(f()); // "2" 関数 foo と2つのクロージャがローカル変数 x に束縛された同一のメモリ領域を使用していることに注意。 一方、多くの関数型言語、例えばML、は変数を直接、値に束縛する。この場合、一度束縛された変数の値を変える方法はないので、クロージャ間で状態を共有する必要はない。単に同じ値を使うだけである。 さらに、Haskellなど、遅延評価を行う関数型言語では、変数は将来の計算結果に束縛される。例を挙げる。 foo x y = let r = x / y in (\z -> z + r) f = foo 1 0 main = do putStr (show (f 123)) r は計算 (x / y) に束縛されており、この場合は0による除算である。しかしながら、クロージャが参照しているのはその値ではなく計算であるので、エラーはクロージャが実行され、実際にその束縛を使おうと試みたときに現れる。 さらなる違いは静的スコープである制御構文、C言語風の言語における return・break・continue などにおいて現れる。ECMAScriptなどの言語では、これらはクロージャ毎に束縛され、構文上の束縛を隠蔽する。つまり、クロージャ内からの return はクロージャを呼び出したコードに制御を渡す。しかしSmalltalkでは、このような動作はトップレベルでしか起こらず、クロージャに捕捉される。例を示して、この違いを明らかにする。 "Smalltalk" foo | xs | xs := #(1 2 3 4). xs do: [:x | ^x]. ^0 bar Transcript show: (self foo) "prints 1" // ECMAScript function foo() { var xs = new Array(1, 2, 3, 4); xs.forEach(function(x) { return x; }); return 0; } print(foo()); // prints 0 Smalltalkにおける