クロージャ

出典: フリー百科事典『ウィキペディア(Wikipedia)』
移動: 案内, 検索

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

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

目次

[編集] 概要

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

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

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

[編集]

クロージャを使ったカウンタの例を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は完全な静的スコープのクロージャを持つ最初の言語として登場した。Common Lispはそれを取り入れた。実質的にすべての関数型言語(ScalaHaskellOCamlなど)とSmalltalkに由来するオブジェクト指向言語は何らかの形でクロージャを持っている。その他のクロージャを持つ言語に、GroovyECMAScriptJavaScriptを含む)、PerlPythonRubyPHP(5.3以降)、LuaSquirrelなどがある。

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

[編集] セマンティクスの違い

言語ごとにスコープのセマンティクスが異なるように、クロージャの定義も異なっている。汎用的な定義では、クロージャが捕捉する「環境」とは、あるスコープのすべての変数の束縛の集合である。しかし、この変数の束縛というものの意味も言語ごとに異なっている。命令型言語では、変数は値を格納するためのメモリ中の位置と束縛される。この束縛は変化せず、束縛された位置にある値が変化する。クロージャは束縛を捕捉しているので、そのような言語での変数への操作は、それがクロージャからであってもなくとも、同一のメモリ領域に対して実行される。例として、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や、Common Lisp上に作られたCLAZYパッケージ(言語拡張)など、遅延評価を行う関数型言語では、変数は将来の計算結果に束縛される。例を挙げる。

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.newlambda はどちらもクロージャを作るための方法である。しかし、それぞれが作ったクロージャの return の振る舞いに関しては、異なるセマンティクスを持っている。

[編集] クロージャに類似の構文

[編集] C

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

[編集] C++

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

void foo(string myname) {
  typedef vector<string> names;
  int y;
  names n;
  // ...
  names::iterator i =
    find_if(n.begin(), n.end(), [](const string& s){return s != myname && s.size() > y;});
  // <tt>i</tt> is now either <tt>n.end()</tt> or points to the first string in <tt>n</tt>
  // which is not equal to <tt> myname</tt> and which length is greater than <tt>y</tt>
}

このようなコードが書けるようになる。

[編集] Eiffel

Eiffelにはクロージャを定義するためのinline agent(インラインエージェント)がある。インラインエージェントはルーチンを表すオブジェクトで、次のように利用する。

OK_button.click_event.subscribe(
    agent(x, y: INTEGER) do
        country := map.country_at_coordinates(x, y)
        country.display
    end
)

subscribe の実引数はインラインエージェントで、2つの引数を持つ手続きである。ユーザがこのボタンをクリックして、click_event タイプのイベントが起こると、マウスの座標を引数としてこの手続きが実行される。

Eiffelのインラインエージェントの大きな限界は、外側のスコープのローカル変数を参照できないという点である。

[編集] Java

Javaでは、メソッド内部に「無名クラス」を定義することができる。無名クラスからは、そのメソッドの final (リードオンリー)なローカル変数を、無名クラスのメンバ変数と名前が被らない限り、参照できる。

class CalculationWindow extends JFrame {
    private JButton saveButton;
    ...
 
    public final void calculateInSeparateThread(final URI uri) {
        // The expression "new Runnable() { ... }" is an anonymous class.
        Runnable runner = new Runnable() {
                void run() {
                    // It can access final local variables:
                    calculate(uri);
                    // It can access private fields of the enclosing class:
                    // Always update the Graphic components into the Swing Thread
                    SwingUtilities.invokeLater(new Runnable() {
                         public void run() {
                             saveButton.setEnabled(true);
                         }
                    });                   
                }
            };
        new Thread(runner).start();
    }
}

要素が1つの配列を final な参照で保持すれば、クロージャで1つのローカル変数を参照する機能をエミュレートできる。内部クラスはその参照の値そのものを変えることはできないが、参照されている配列の要素の値は変えることができるからである。このテクニックはJavaに限ったものではなく、Pythonなど似た制限を持つ言語でも有効である。

Javaに完全なクロージャを追加するという言語拡張が検討されていたが、様々な問題によりクロージャを導入せずに、SAM Typeなインターフェース(抽象メソッドを一つだけ持つインターフェース)を簡単に書けるようにラムダ式を導入することが検討されている。[3]

[編集] 実装

クロージャは典型的には関数コードへのポインタ及び関数の作成時の環境の表現(例えば、使用可能な変数とその値の集合など)を含む特別なデータ構造によって実装される。

ある言語処理系の実行時のメモリモデルがすべてのローカル変数を線形なスタックに確保するものであれば、クロージャを完璧に実装するのは容易ではない。それは、以下のような理由による。

  1. クロージャをつくった関数(エンクロージャ)の呼び出し元に復帰した際に、クロージャが参照するスタック上のローカル変数(レキシカル変数)が開放されてしまう。しかしクロージャにはレキシカル変数がエンクロージャの終了後も存続することが必要である。したがってレキシカル変数は必要がなくなるまで存続するように確保されなければならない。
  2. クロージャが実行された時に、レキシカル変数のスタック上の位置を知ることは困難である。

第1の問題を解決するため、クロージャを実装するプログラミング言語が大抵、ガベージコレクションを備えている。この場合、クロージャへの参照が全て無効になった時に、レキシカル変数はガベージコレクタに渡される。

第2の問題を解決するためには、デリゲートのように、関数の参照と実行環境の参照をセットで扱える必要がある。しかし、これではC言語のようなネイティブコードの関数の呼び出しとの互換性がなくなる。そのため、実行時にスタックやヒープに、エンクロージャのスタックポインタを埋め込んだ、実際の関数を起動するだけの小さな関数(トランポリン関数)を動的に生成することでも実装できる。しかし、セキュリティの観点から近代的なOSでは標準でスタックやヒープ上のコードの実行を禁止しているのが一般的であり、この制限を一時的・部分的に解除することをサポートしている環境でなければ実現できない。

現代的なScheme処理系は、クロージャに使用される可能性のあるローカル変数は動的に確保し、そうでないものはスタックに確保するなどの最適化を行うものが多い。

[編集] 関連項目

[編集] 参考文献

[編集] 脚注

[編集] 外部リンク

個人用ツール
名前空間
変種
操作
案内
ヘルプ
ツールボックス
他の言語