クロージャー
[Wikipedia|▼Menu]

この項目では、プログラミング言語における関数の一種について記述しています。光ファイバーやメタル線等のケーブル同士の接続・分岐を行う際に使用する接続箱については「端子函」をご覧ください。

クロージャ(クロージャー、closure、閉包)はプログラミング言語における関数の一種。引数以外の変数を実行時の環境ではなく、自身が定義された環境(静的スコープ)において解決することを特徴とする。関数とそれを評価する環境のペアであるともいえる。この概念は少なくとも1960年代のSECDマシンまで遡ることができる。

まれに、関数ではなくとも、環境に紐付けられたデータ構造のことをクロージャと呼ぶ場合もある。
目次

1 概要

2 例

3 クロージャの用途

4 クロージャを持つプログラミング言語

5 セマンティクスの違い

6 クロージャに類似の構文

6.1 C

6.2 C++

6.3 Eiffel

6.4 Java


7 実装

8 関連項目

9 参考文献

10 外部リンク

//
概要

典型的には、クロージャはある関数全体が他の関数(以下、エンクロージャ)の内部で宣言されたときに発生し、内部の関数はエンクロージャのローカル変数(レキシカル変数)を参照する。実行時に外部の関数が実行された際、クロージャが形成される。クロージャは内部の関数のコードとエンクロージャのスコープ内の必要なすべての変数への参照からなる。

クロージャはプログラム内で環境を共有するための仕組みである。レキシカル変数はグローバルな名前空間を占有しないという点でグローバル変数とは異なっている。またオブジェクトインスタンス変数とは、オブジェクトのインスタンスではなく関数の呼び出しに束縛されているという点で異なる。

クロージャは関数型言語では遅延評価カプセル化のために、また高階関数の引数として広く用いられる。

クロージャを使ったカウンタの例をJavaScriptで示す。 function newCounter() { var i = 0; return function() { // 無名関数 i = i + 1; return i; } } c1 = newCounter(); alert(c1()); // 1 alert(c1()); // 2 alert(c1()); // 3 alert(c1()); // 4 alert(c1()); // 5

関数newCounterの中でクロージャが使用されている。c1に代入された無名関数はnewCounter内のローカル変数iを参照している。c1を呼び出すたびにiはインクリメントされていく。
クロージャの用途

クロージャには多くの用途がある。

ライブラリの設計者はクロージャを関数への引数として渡すことで利用者が挙動をカスタマイズ可能なようにできる。例えばソートを行う関数は比較のコードをクロージャとして引数にとることで利用者が定義した基準でソートできるようになる。

クロージャは遅延評価される(呼び出されるまで何も実行しない)ので、制御構造の定義に用いることができる。例として、Smalltalkの分岐(if-then-else)や繰り返し(while及びfor)を含むすべての標準制御構造は、クロージャを引数にとるメソッドを持つオブジェクトを利用することで定義されている。同様な方法で利用者は自作の制御構造を簡単に定義できる。

クロージャは時として ⇒オブジェクトのより良い代替となる。

クロージャを持つプログラミング言語

Schemeは完全な静的スコープのクロージャを持つ最初の言語として登場した。実質的にすべての関数型言語(ScalaHaskellOCamlなど)とSmalltalkに由来するオブジェクト指向言語は何らかの形でクロージャを持っている。その他のクロージャを持つ言語に、GroovyECMAScriptJavaScriptを含む)、PerlPythonRubyPHP(5.3以降)、LuaSquirrelC#などがある。

セマンティクスはそれぞれ大きく異なっているが、多くの現代的な汎用のプログラミング言語は静的スコープとクロージャのいくつかのバリエーションを持っている。
セマンティクスの違い

言語ごとにスコープのセマンティクスが異なるように、クロージャの定義も異なっている。汎用的な定義では、クロージャが捕捉する「環境」とは、あるスコープのすべての変数の束縛の集合である。しかし、この変数の束縛というものの意味も言語ごとに異なっている。命令型言語では、変数は値を格納するためのメモリ中の位置と束縛される。この束縛は変化せず、束縛された位置にある値が変化する。クロージャは束縛を捕捉しているので、そのような言語での変数への操作は、それがクロージャからであってもなくとも、同一のメモリ領域に対して実行される。例として、ECMAScriptを取り上げるとvar f, g;function foo(){ var x = 0; f = function() { ++x; }; g = function() { --x; }; x = 1; print(f()); // "2"}foo();g(); // "1"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風の言語におけるreturnbreakcontinueなどにおいて現れる。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における^は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の振る舞いを選ぶことができる。 def foo f = Proc.new { return "return from foo from inside proc" } f.call # control leaves foo here return "return from foo" end def bar f = lambda { return "return from lambda" } f.call # control does not leave bar here return "return from bar" end puts foo # prints "return from foo from inside proc" puts bar # prints "return from bar"

この例のProc.newとlambdaはどちらもクロージャを作るための方法である。しかし、それぞれが作ったクロージャのreturnの振る舞いに関しては、異なるセマンティクスを持っている。
クロージャに類似の構文
C

C言語では、コールバックをサポートするライブラリの中に、2つの値を利用したコールバックを行うものがある。関数ポインタと任意のデータを指すvoid*ポインタである。ライブラリがコールバック関数を実行するたび、データポインタを使用する。これによってコールバックは状態を管理することができ、登録した情報を参照できる。このイディオムはクロージャと機能面で似ているが、構文面では似ていない。
C++

C++では、operator()をオーバーロードすることで、関数オブジェクトが利用できる。これは関数型言語における関数にいくらか似た振る舞いをみせる。関数オブジェクトは実行時に作ることができ、状態を持つこともできる。しかし、クロージャのように自動的にローカル変数を捕捉するようなことはしない。C++にクロージャのサポートを追加する2つの提案(どちらもそれをラムダ関数と呼んでいる)がC++の標準化委員会で検討されている ( ⇒[1], ⇒[2])。2つの提案の主な違いは、クロージャにすべてのローカル変数のコピーを格納するか、元の変数への参照を格納するか、というデフォルトの振る舞いである。どちらの案でもデフォルトの振る舞いを上書きできる。


次ページ
記事の検索
おまかせリスト
▼オプションを表示
ブックマーク登録
mixiチェック!
Twitterに投稿
オプション/リンク一覧
英語版を参照
話題のニュース
列車運行情報
暇つぶしWikipedia

Size:22 KB
出典: フリー百科事典『ウィキペディア(Wikipedia)
担当:undef