時々「クロージャ」が言及されているのを見て、それを調べてみましたが、Wikiには理解できる説明がありません。誰かがここで私を助けることができますか?
時々「クロージャ」が言及されているのを見て、それを調べてみましたが、Wikiには理解できる説明がありません。誰かがここで私を助けることができますか?
回答:
(免責事項:これは基本的な説明です。定義に関しては、少し簡略化しています)
クロージャーを考える最も簡単な方法は、変数として保存できる関数(「ファーストクラス関数」と呼ばれる)です。これは、作成されたスコープにローカルな他の変数にアクセスする特別な機能を持ちます。
例(JavaScript):
var setKeyPress = function(callback) {
document.onkeypress = callback;
};
var initialize = function() {
var black = false;
document.onclick = function() {
black = !black;
document.body.style.backgroundColor = black ? "#000000" : "transparent";
}
var displayValOfBlack = function() {
alert(black);
}
setKeyPress(displayValOfBlack);
};
initialize();
機能1に割り当てられているdocument.onclick
とはdisplayValOfBlack
閉鎖されています。どちらもブール変数を参照していることがわかりますblack
が、その変数は関数の外部で割り当てられています。ためblack
である関数が定義された範囲に局所、この変数へのポインタが保存されます。
これをHTMLページに配置する場合:
これは、両方が同じ black
にアクセスでき、ラッパーオブジェクトなしで状態を格納するために使用できることを示しています。
への呼び出しsetKeyPress
は、関数が他の変数と同じように渡される方法を示すことです。スコープ閉鎖に保存さは、まだ関数が定義されたものです。
クロージャーは、特にJavaScriptおよびActionScriptでイベントハンドラーとして一般的に使用されます。クロージャーを適切に使用すると、オブジェクトラッパーを作成せずに、変数をイベントハンドラーに暗黙的にバインドできます。ただし、不注意に使用すると、メモリリークが発生します(メモリ内の大きなオブジェクト、特にDOMオブジェクトを保持する未使用のイベントハンドラーのみがガベージコレクションを防止する場合など)。
1:実際には、JavaScriptのすべての関数はクロージャーです。
black
は関数内で宣言されているので、スタックが巻き戻されると破壊されませんか?
black
関数内で宣言されているので破棄されない」と言ったときに参照しているようだからです。また、関数でオブジェクトを宣言し、それを別の場所にある変数に割り当てると、そのオブジェクトには他の参照があるため保持されることも覚えておいてください。
クロージャは基本的にオブジェクトを見るための別の方法です。オブジェクトは、1つ以上の関数がバインドされたデータです。クロージャーは、1つ以上の変数がバインドされている関数です。少なくとも実装レベルでは、この2つは基本的に同じです。本当の違いは、どこから来たのかということです。
オブジェクト指向プログラミングでは、事前にメンバー変数とそのメソッド(メンバー関数)を定義してオブジェクトクラスを宣言し、そのクラスのインスタンスを作成します。各インスタンスには、コンストラクターによって初期化されたメンバーデータのコピーが付属しています。次に、オブジェクト型の変数を取得し、それをデータとして渡します。これは、データとしての性質に焦点が当てられているためです。
一方、クロージャーでは、オブジェクトはオブジェクトクラスのように事前に定義されておらず、コード内のコンストラクター呼び出しによってインスタンス化されていません。代わりに、クロージャーを別の関数内の関数として記述します。クロージャは、外部関数のローカル変数のいずれかを参照でき、コンパイラはそれを検出し、これらの変数を外部関数のスタックスペースからクロージャの隠しオブジェクト宣言に移動します。次に、クロージャー型の変数を取得します。基本的に内部のオブジェクトですが、関数としての性質に焦点が当てられているため、関数参照として渡します。
クロージャーという用語は、コード(ブロック、関数)が、コードのブロックが定義されている環境によって閉じられた(つまり、値にバインドされた)自由変数を持つことができるという事実に由来します。
たとえば、Scala関数の定義を見てみましょう。
def addConstant(v: Int): Int = v + k
関数本体には、2つの名前(変数)がv
あり、k
2つの整数値を示します。名前v
は、関数の引数として宣言されているため、バインドaddConstant
されています(v
関数の呼び出し時に値が割り当てられることがわかっている関数宣言を確認することにより)。関数にはバインドされている値(および方法)の手がかりが含まれていないため、名前k
は関数に対して自由です。addConstant
k
次のような呼び出しを評価するには:
val n = addConstant(10)
k
値を割り当てる必要があります。これk
は、定義されているコンテキストで名前が定義されている場合にのみ発生しますaddConstant
。例えば:
def increaseAll(values: List[Int]): List[Int] =
{
val k = 2
def addConstant(v: Int): Int = v + k
values.map(addConstant)
}
今、我々が定義されていることがaddConstant
文脈でk
定義されている、addConstant
となっている閉鎖すべての自由変数が今しているので、閉じ(値にバインド):addConstant
それは関数であるかのように呼び出され、周りに渡すことができます。k
クロージャーが定義されると自由変数が値にバインドされるのに対しv
、クロージャーが呼び出されると引数変数がバインドされることに注意してください。
したがって、クロージャは基本的に、コンテキストによってバインドされた後、自由変数を介して非ローカル値にアクセスできる関数またはコードブロックです。
多くの言語では、クロージャを一度だけ使用すると、匿名にすることができます。例えば
def increaseAll(values: List[Int]): List[Int] =
{
val k = 2
values.map(v => v + k)
}
自由変数のない関数は、(自由変数の空のセットを持つ)クロージャーの特殊なケースであることに注意してください。同様に、匿名関数は匿名クロージャの特殊なケースです。つまり、匿名関数は自由変数のない匿名クロージャです。
JavaScriptの簡単な説明:
var closure_example = function() {
var closure = 0;
// after first iteration the value will not be erased from the memory
// because it is bound with the returned alertValue function.
return {
alertValue : function() {
closure++;
alert(closure);
}
};
};
closure_example();
alert(closure)
は、以前に作成されたの値を使用しますclosure
。返されたalertValue
関数の名前空間は、closure
変数が存在する名前空間に接続されます。関数全体を削除すると、closure
変数の値は削除されますが、それまでは、alertValue
関数は常にvariableの値を読み書きできますclosure
。
このコードを実行すると、最初の反復で値0がclosure
変数に割り当てられ、関数が次のように書き換えられます。
var closure_example = function(){
alertValue : function(){
closure++;
alert(closure);
}
}
また、関数を実行するためalertValue
にローカル変数が必要なのでclosure
、以前に割り当てられたローカル変数の値に自身をバインドしますclosure
。
そして、closure_example
関数を呼び出すたびに、バインドされているclosure
ため、変数の増分値を書き出しますalert(closure)
。
closure_example.alertValue()//alerts value 1
closure_example.alertValue()//alerts value 2
closure_example.alertValue()//alerts value 3
//etc.
「クロージャ」とは、本質的に、ローカル状態とコードをパッケージにまとめたものです。通常、ローカル状態は周囲の(字句)スコープから取得され、コードは(本質的に)内部関数であり、外部に返されます。クロージャーは、内部関数が認識するキャプチャーされた変数と内部関数のコードの組み合わせです。
残念ながら、なじみがないため、説明するのは少し難しいのです。
私が過去に使った成功例の1つは、「私たちが「本」と呼んでいるものが部屋の閉まりの中にあると想像してください。「本」とは、TAOCP 、それはドレスデンファイルの本のコピーです。したがって、あなたがどの閉鎖をしているかに応じて、コードは「本をくれ」という結果になります。
static
ローカル変数を持つC関数をクロージャと見なすことができますか?Haskellのクロージャには状態が関係していますか?
static
ローカル変数を使用して、1つの関数から複数のクロージャーを作成できるようにしたい場合は、1つだけにします)。
「状態」の概念を定義せずに、クロージャが何であるかを定義することは困難です。
基本的に、関数を第一級の値として扱う完全な字句スコープを持つ言語では、特別なことが起こります。私が次のようなことをする場合:
function foo(x)
return x
end
x = foo
変数x
は参照するだけでなく、最後に戻ったときに残っていfunction foo()
た状態も参照しfoo
ます。本当の魔法は、foo
そのスコープ内でさらに定義された他の関数があるときに起こります。独自のミニ環境のようなものです(「通常」と同様に、グローバル環境で機能を定義します)。
機能的には、C ++(C?)の 'static'キーワードと同じ問題の多くを解決できます。これは、複数の関数呼び出しを通してローカル変数の状態を保持します。ただし、関数はファーストクラスの値であるため、同じ原理(静的変数)を関数に適用することに似ています。クロージャは、保存される関数全体の状態のサポートを追加します(C ++の静的関数とは関係ありません)。
関数を最初のクラス値として扱い、クロージャーのサポートを追加することは、同じ関数の複数のインスタンスをメモリに保持できることも意味します(クラスと同様)。これが意味することは、関数内でC ++静的変数を処理するときに必要となるように、関数の状態をリセットすることなく同じコードを再利用できることです(これについて間違っているかもしれません)。
Luaのクロージャーサポートのテストを次に示します。
--Closure testing
--By Trae Barlow
--
function myclosure()
print(pvalue)--nil
local pvalue = pvalue or 10
return function()
pvalue = pvalue + 10 --20, 31, 42, 53(53 never printed)
print(pvalue)
pvalue = pvalue + 1 --21, 32, 43(pvalue state saved through multiple calls)
return pvalue
end
end
x = myclosure() --x now references anonymous function inside myclosure()
x()--nil, 20
x() --21, 31
x() --32, 42
--43, 53 -- if we iterated x() again
結果:
nil
20
31
42
トリッキーになる可能性があり、おそらく言語によって異なりますが、Luaでは関数が実行されるたびに状態がリセットされるようです。これを言うのは、myclosure
関数/状態に(アクセスする匿名関数ではなく)直接アクセスすると、上記のコードの結果が異なるため、pvalue
10にリセットされるためです。しかし、x(匿名関数)を介してmyclosureの状態にアクセスするpvalue
と、メモリ内のどこかに存在していることがわかります。私はそれにもう少しあると思う、おそらく誰かが実装の性質をよりよく説明できるでしょう。
PS:C ++ 11(以前のバージョンのもの以外)をなめているのか分からないので、これはC ++ 11とLuaのクロージャーの比較ではないことに注意してください。また、LuaからC ++へのすべての「描画された線」は、静的変数とクロージャーが100%同じではないため、類似しています。同様の問題を解決するために時々使用される場合でも。
上記のコード例では、不明なことは、匿名関数と高次関数のどちらがクロージャーと見なされるかです。
クロージャは、状態が関連付けられている関数です。
perlでは、次のようなクロージャーを作成します。
#!/usr/bin/perl
# This function creates a closure.
sub getHelloPrint
{
# Bind state for the function we are returning.
my ($first) = @_;a
# The function returned will have access to the variable $first
return sub { my ($second) = @_; print "$first $second\n"; };
}
my $hw = getHelloPrint("Hello");
my $gw = getHelloPrint("Goodby");
&$hw("World"); // Print Hello World
&$gw("World"); // PRint Goodby World
C ++で提供される新しい機能を見ると。
また、現在の状態をオブジェクトにバインドすることもできます。
#include <string>
#include <iostream>
#include <functional>
std::function<void(std::string const&)> getLambda(std::string const& first)
{
// Here we bind `first` to the function
// The second parameter will be passed when we call the function
return [first](std::string const& second) -> void
{ std::cout << first << " " << second << "\n";
};
}
int main(int argc, char* argv[])
{
auto hw = getLambda("Hello");
auto gw = getLambda("GoodBye");
hw("World");
gw("World");
}
簡単な関数を考えてみましょう:
function f1(x) {
// ... something
}
この関数は、他の関数内にネストされていないため、トップレベル関数と呼ばれます。JavaScript関数はすべて、「スコープチェーン」と呼ばれるオブジェクトのリストに関連付けられます。このスコープチェーンは、オブジェクトの順序付きリストです。これらの各オブジェクトはいくつかの変数を定義します。
トップレベル関数では、スコープチェーンは単一のオブジェクト、グローバルオブジェクトで構成されます。たとえば、f1
上記の関数には、すべてのグローバル変数を定義する単一のオブジェクトを持つスコープチェーンがあります。(ここでの「オブジェクト」という用語はJavaScriptオブジェクトを意味するものではなく、JavaScriptが変数を「ルックアップ」できる変数コンテナーとして機能する実装定義オブジェクトにすぎないことに注意してください。)
この関数が呼び出されると、JavaScriptは「アクティベーションオブジェクト」と呼ばれるものを作成し、それをスコープチェーンの先頭に配置します。このオブジェクトには、すべてのローカル変数が含まれます(たとえば、x
ここ)。したがって、スコープチェーンには2つのオブジェクトがあります。1つはアクティベーションオブジェクトで、その下はグローバルオブジェクトです。
2つのオブジェクトが異なる時間にスコープチェーンに配置されることに注意してください。グローバルオブジェクトは、関数が定義されたとき(つまり、JavaScriptが関数を解析して関数オブジェクトを作成したとき)に配置され、関数が呼び出されるとアクティベーションオブジェクトに入ります。
だから、私たちは今これを知っています:
入れ子関数を扱うとき、状況は興味深いものになります。それでは、作成しましょう:
function f1(x) {
function f2(y) {
// ... something
}
}
ときにf1
定義されます、我々はそれが唯一のグローバルオブジェクトを収容するためのスコープチェーンを取得します。
これで、 f1
呼び出されると、スコープチェーンf1
がアクティベーションオブジェクトを取得します。このアクティベーションオブジェクトには、変数x
とf2
関数である変数が含まれています。そして、それf2
が定義されつつあることに注意してください。したがって、この時点で、JavaScriptはの新しいスコープチェーンも保存しf2
ます。この内部関数用に保存されたスコープチェーンは、現在有効なスコープチェーンです。現在有効なスコープチェーンは、f1
のものです。したがって、f2
のスコープチェーンはf1
の現在のスコープチェーンです。これには、のアクティベーションオブジェクトf1
とグローバルオブジェクトが含まれます。
ときにf2
呼ばれて、それはそれ自身のアクティベーションオブジェクトを含む取得だy
、すでにのアクティベーションオブジェクトが含まれ、そのスコープチェーンに追加f1
し、グローバルオブジェクトを。
内に別のネストされた関数が定義されている場合f2
、そのスコープチェーンには、定義時に3つのオブジェクト(2つの外部関数のアクティブ化オブジェクト2つ、グローバルオブジェクト)、呼び出し時に4つが含まれます。
そのため、スコープチェーンの仕組みは理解できましたが、クロージャについてはまだ説明していません。
関数の変数が解決される関数オブジェクトとスコープ(変数バインディングのセット)の組み合わせは、コンピューターサイエンスの文献ではクロージャーと呼ばれます-JavaScriptデイビッドフラナガンによる決定版ガイド
ほとんどの関数は、関数が定義されたときに有効だった同じスコープチェーンを使用して呼び出され、クロージャーが関係していることは実際には問題ではありません。クロージャは、定義されたときに有効だったものとは異なるスコープチェーンの下で呼び出されたときに興味深いものになります。これは、ネストされた関数オブジェクトが定義された関数から返されるときに最もよく起こります。
関数が戻ると、そのアクティベーションオブジェクトはスコープチェーンから削除されます。ネストされた関数がなかった場合、アクティベーションオブジェクトへの参照はなくなり、ガベージコレクションが行われます。ネストされた関数が定義されている場合、それらの各関数はスコープチェーンへの参照を持ち、そのスコープチェーンはアクティベーションオブジェクトを参照します。
ただし、これらのネストされた関数オブジェクトが外部関数内に残っている場合は、参照されているアクティベーションオブジェクトとともに、それら自体がガベージコレクションされます。ただし、関数がネストされた関数を定義してそれを返すか、それをどこかのプロパティに保存する場合、ネストされた関数への外部参照があります。ガベージコレクションされず、それが参照するアクティベーションオブジェクトもガベージコレクションされません。
私たちの上の例では、我々は返さないf2
からf1
にするときのコールしたがって、f1
戻り、そのアクティベーションオブジェクトは、そのスコープチェーンとごみ収集から削除されます。しかし、次のようなものがある場合:
function f1(x) {
function f2(y) {
// ... something
}
return f2;
}
ここで、返さf2
れるのは、のアクティベーションオブジェクトを含むスコープチェーンを持つため、f1
ガベージコレクションされません。この時点で、を呼び出すとf2
、から出ていてもf1
の変数にアクセスできx
ますf1
。
したがって、関数はスコープチェーンを保持し、スコープチェーンには外部関数のすべてのアクティベーションオブジェクトが含まれていることがわかります。これが閉鎖の本質です。JavaScriptの関数は「レキシカルスコープ」と呼ばれます。つまり、呼び出されたときにアクティブだったスコープではなく、定義されたときにアクティブだったスコープを保存します。
プライベート変数の近似、イベント駆動型プログラミング、部分的なアプリケーションなどのクロージャーを含む強力なプログラミング手法がいくつかあります。
また、これはすべて、クロージャーをサポートするすべての言語に適用されることに注意してください。たとえば、PHP(5.3 +)、Python、Rubyなど。
クロージャーは、コンパイラーの最適化です(別名、構文糖?)。一部の人々は、これを貧しい人のオブジェクトとも呼んでいます。
Eric Lippertの回答を参照してください:(以下の抜粋)
コンパイラは次のようなコードを生成します。
private class Locals
{
public int count;
public void Anonymous()
{
this.count++;
}
}
public Action Counter()
{
Locals locals = new Locals();
locals.count = 0;
Action counter = new Action(locals.Anonymous);
return counter;
}
理にかなっていますか?
また、あなたは比較を求めました。VBとJScriptはどちらも、ほぼ同じ方法でクロージャーを作成します。