読み込み中...クロージャ (クロージャー、Closure) は、プログラミング言語において引数以外の変数を実行時の環境ではなく、自身が定義された環境(静的スコープ)において解決する関数のことである。関数とそれを評価する環境のペアであるともいえる。この概念は少なくとも1960年代のSECDマシンまで遡ることができる。
まれに、関数ではなくとも、環境に紐付けられたデータ構造のことをクロージャと呼ぶ場合もある。
典型的には、クロージャはある関数全体が他の関数(以下、エンクロージャ)の内部で宣言されたときに発生し、内部の関数はエンクロージャのローカル変数(レキシカル変数)を参照する。実行時に外部の関数が実行された際、クロージャが形成される。クロージャは内部の関数のコードとエンクロージャのスコープ内の必要なすべての変数への参照からなる。
クロージャはプログラム内で環境を共有するための仕組みである。レキシカル変数はグローバルな名前空間を占有しないという点でグローバル変数とは異なっている。またオブジェクトのインスタンス変数とは、オブジェクトのインスタンスではなく関数の呼び出しに束縛されているという点で異なる。
クロージャは関数型言語では遅延評価やカプセル化のために、また高階関数の引数として広く用いられる。
関数newCounterの中でクロージャが使用されている。c1に代入された無名関数はnewCounter内のローカル変数iを参照している。c1を呼び出すたびにiはインクリメントされていく。
クロージャには多くの用途がある。
その他のクロージャを持つ言語に、Groovy、ECMAScript(JavaScriptを含む)、Perl、Python、Ruby、Lua、C#などがある。
セマンティクスは非常にそれぞれ異なっているが、多くの現代的な汎用のプログラミング言語は静的スコープとクロージャのいくつかのバリエーションを持っている。
関数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における^はECMAScriptにおけるreturnにあたるものだと頭に入れれば、一目見た限りではどちらのコードも同じことをするように見える。違いは、ECMAScriptの例ではreturnはクロージャを抜けるが関数fooは抜けず、Smalltalkの例では^はクロージャだけではなくメソッドfooをも抜ける、という点である。後者の特徴はより高い表現力をもたらす。Smalltalkのdo:は通常のメソッドであり、自然に制御構文が定義できている。一方、ECMAScriptではreturnの意味が変わってしまうので、同じ目的にはforeachという新しい構文を導入しなければならない。
しかし、スコープを越えて生存する継続には問題もある。
foo ^[ x: | ^x ] bar | f | f := self foo. f value: 123 "error!"上の例でメソッドfooが返すブロックが実行されたとき、fooから値を返そうとする。しかし、fooの呼び出しは既に完了しているので、この操作はエラーとなる。
Rubyなどの言語では、プログラマがreturnの振る舞いを選ぶことができる。
この例のProc.newとlambdaはどちらもクロージャを作るための方法である。しかし、それぞれが作ったクロージャのreturnの振る舞いに関しては、異なるセマンティクスを持っている。
C言語では、コールバックをサポートするライブラリの中に、2つの値を利用したコールバックを行うものがある。関数ポインタと任意のデータを指すvoid*ポインタである。ライブラリがコールバック関数を実行するたび、データポインタを使用する。これによってコールバックは状態を管理することができ、登録した情報を参照できる。このイディオムはクロージャと機能面で似ているが、構文面では似ていない。
subscribeの実引数はインラインエージェントで、2つの引数を持つ手続きである。ユーザがこのボタンをクリックして、click_eventタイプのイベントが起こると、マウスの座標を引数としてこの手続きが実行される。
Eiffelのインラインエージェントの大きな限界は、外側のスコープのローカル変数を参照できないという点である。
operator()をオーバーロードすることで、関数オブジェクトが利用できる。これは関数型言語における関数にいくらか似た振る舞いをみせる。関数オブジェクトは実行時に作ることができ、状態を持つこともできる。しかし、クロージャのように自動的にローカル変数を捕捉するようなことはしない。C++にクロージャのサポートを追加する2つの提案(どちらもそれをラムダ関数と呼んでいる)がC++の標準化委員会で検討されている (http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2006/#mailing2006-02, http://val.samko.info/lambda/)。2つの提案の主な違いは、クロージャにすべてのローカル変数のコピーを格納するか、元の変数への参照を格納するか、というデフォルトの振る舞いである。どちらの案でもデフォルトの振る舞いを上書きできる。そのような提案が通れば、
void foo(string myname) {
typedef vectorこのようなコードが書けるようになる。
Javaでは、メソッド内部に「無名クラス」を定義することができる。無名クラスからは、そのメソッドのfinal(リードオンリー)なローカル変数を、無名クラスのメンバ変数と名前が被らない限り、参照できる。
要素が1つの配列をfinalな参照で保持すれば、クロージャで1つのローカル変数を参照する機能をエミュレートできる。内部クラスはその参照の値そのものを変えることはできないが、参照されている配列の要素の値は変えることができるからである。このテクニックはJavaに限ったものではなく、Pythonなど似た制限を持つ言語でも有効である。
クロージャは典型的には関数コードへのポインタ及び関数の作成時の環境の表現(例えば、使用可能な変数とその値の集合など)を含む特別なデータ構造によって実装される。
ある言語処理系の実行時のメモリモデルがすべてのローカル変数を線形なスタックに確保するものであれば、クロージャを完璧に実装するのは容易ではない。そのような言語では関数の呼び出し元に復帰した際に、スタック上のローカル変数が開放されてしまう。しかしクロージャには参照している変数がクロージャをつくった関数の終了後も存続することが必要である。したがってそれらの変数は必要がなくなるまで存続するように確保されなければならない。これがクロージャを実装するプログラミング言語が大抵、ガベージコレクションを備える理由である。
また、実行時にスタックやヒープに、呼び出し元のスタックポインタを埋め込んだ、実際の関数を起動するだけの小さな関数(トランポリン関数)を動的に生成することでも実装できる。しかし、セキュリティの観点から近代的なOSでは標準でスタックやヒープ上のコードの実行を禁止しているのが一般的であり、この制限を一時的・部分的に解除することをサポートしている環境でなければ実現できない。
現代的なScheme処理系は、クロージャに使用される可能性のあるローカル変数は動的に確保し、そうでないものはスタックに確保するなどの最適化を行うものが多い。
読み込み中...