コンピュータサイエンスにおけるクロージャは、それ自体が環境を持つ関数のことです。この環境には、少なくとも1つのバインドされた変数(数値などのを持つ名前)が含まれます。クロージャは、関数が定義されたときの変数束縛(スコープ)を保持し、その関数が呼び出される時点が関数定義時とは別のスコープにあっても、束縛された変数にアクセスできます。クロージャの環境は、クロージャが使用されるまで束縛変数をメモリ内に保持します。

定義と重要な用語

  • 自由変数(free variables):関数本体で使われているが、その関数のパラメータでも局所変数でもない変数。クロージャはこれらの自由変数をキャプチャして環境に保持します。
  • 環境(environment):自由変数とそれに対応する値の集合。クロージャはこの環境と関数コードを一緒に保持します。
  • レキシカル(静的)スコープ:クロージャの意味を定義するために一般的に使われるスコープルール。関数の外側の文脈で変数が決まります。動的スコープとは異なります。

仕組み(どのように動くか)

クロージャは「関数コード」と「その関数が参照する外側の変数の束(環境))」をペアで保持します。実装上は、しばしば次のようになります:

  • 関数が生成されるとき、その関数が参照する外側の変数への参照(またはコピー)が関数オブジェクトに結び付けられる。
  • その後、外側のスコープが抜けても、参照されている変数はガベージコレクションの対象にならず、クロージャが存在する限り生き続ける。
  • キャプチャは言語によって「値渡し(コピー)」「参照渡し」「閉包のための特殊なオブジェクトに格納」など実装が異なる。たとえば、C++のラムダは明示的に値や参照でキャプチャを指定できますし、Javaのラムダは「実質的にfinal」な変数をキャプチャします。

簡単な例

以下は理解しやすい例です。まず JavaScript の例:

// JavaScript function makeCounter() {   let count = 0;   return function() { // この無名関数がクロージャ     count += 1;     return count;   }; }  const c = makeCounter(); console.log(c()); // 1 console.log(c()); // 2 

上の例では、makeCounter の内部で定義された無名関数が外側の変数 count を参照しており、これがクロージャです。makeCounter の呼び出しが終わっても count はクロージャによって保持されます。

Python の例(ループ内でのキャプチャに注意):

# Python のよくある落とし穴 funcs = [] for i in range(3):     funcs.append(lambda: i)  # 期待: 0,1,2  だが実際は 2,2,2 になる(全て最後の i を参照しているため) for f in funcs:     print(f())  # 2,2,2  # 解決方法(デフォルト引数で値を固定する) funcs = [] for i in range(3):     funcs.append(lambda i=i: i)  for f in funcs:     print(f())  # 0,1,2 

歴史

Peter J. Landin は 1964 年にこのアイデアにクロージャという名前をつけました。クロージャが普及したのは、1975年以降のプログラミング言語「Schemeです。それ以降に作られた多くのプログラミング言語にもクロージャが搭載されています。

無名関数(匿名関数)との違い

匿名関数(名前のない関数)は、しばしばクロージャと混同されますが、両者は別の概念です。重要な点は次のとおりです:

  • 無名関数は「名前を持たない関数」であり、必ずしも外側の環境をキャプチャするわけではありません。
  • 無名関数が外側の変数をキャプチャしていれば、それはクロージャでもあります。逆に、名前付き関数でも外側の環境をキャプチャしていればクロージャになり得ます(「名前付きクロージャ」)。
  • したがって「匿名関数 = クロージャ」という等式は正しくなく、「クロージャ = 環境を保持する関数(匿名か名前付きかは問わない)」が正確です。

実装上の注意と利用例

  • メモリ管理:クロージャが外側の変数を保持するため、不要になっても参照が残るとメモリ解放が遅れる。言語実装はガベージコレクション等で管理しますが、長期間保持する場合は注意が必要です。
  • パフォーマンス:頻繁にクロージャを生成するとオーバーヘッドが出る場合がある。コンパイラやランタイムが最適化するケースも多い。
  • キャプチャのセマンティクス:参照コピーか値コピーかで振る舞いが異なる。言語仕様を確認すること(例:Java は実質的に final な変数のみ、C++ は capture list による指定)。
  • ユースケース:コールバック、イベントハンドラ、部分適用・カリー化、関数工場(ファクトリ)、イテレータやジェネレータの内部状態保持など。
  • デバッグの難しさ:クロージャは状態を隠蔽するため、意図しない副作用や寿命の問題の原因になり得る。適切な設計とテストが重要です。

まとめ

クロージャは「関数」と「その関数が参照する環境(束縛)」を一緒に保持する仕組みで、レキシカルスコープを前提に強力なプログラミング表現力を与えます。匿名関数と混同されがちですが、クロージャは「環境を保持する関数」であり、名前の有無とは独立した性質です。実装や言語仕様によってキャプチャの振る舞いが変わるため、具体的な言語での動作を理解して使うことが重要です。