ウィキペディアによれば、クロージャーは、関数の外部で宣言され、変数にアクセスできる関数です。例さえあります:
function startAt(x)
function incrementBy(y)
return x + y
return incrementBy
variable closure1 = startAt(1)
variable closure2 = startAt(5)
しかし、ほとんどのプログラミング言語(python、javascript、Swiftなどを含む)によると、次の例は正しいです(pythonで書かれています)。
# Script starts
test = "check"
def func(test2):
def func2():
return test2 == test
return func2()
print func("check") // returns TRUE
# Script ends
基本的に、func
はクロージャーではありませんが、関数の外部で宣言された変数test
を使用することは明らかです。それはfunc
ISクロージャを意味しますか?
C++でもこれを実行できます:
std::string test = "check";
bool func(std::string test2) {
if (test2 == test)
return true;
else
return false;
}
int main() {
if (func("check"))
std::cout << "TRUE" << std::endl; // prints TRUE
}
最終的に、これによりすべての関数がクロージャになります。どこが間違っているのですか?
いいえ、すべての関数がクロージャーであるとは限りません。
ウィキペディアは言う:
...クロージャ...は、関数または関数への参照と参照環境を組み合わせたもので、各その関数の非ローカル変数(自由変数またはアップバリューとも呼ばれます)。
「非ローカルおよび非グローバル」を追加しますが、考え方は正しいです。
C++もPython=の例もクロージャを使用していません。どちらの場合も、スコープルールが関数に外部スコープとグローバルスコープの表示を許可するだけです。
「クロージャ」は最初の例で発生します-incrementBy
が作成され、その外部関数から返されますキャプチャ引数x
。 variable closure1 = startAt(1)
を割り当てると、_closure1
_ var内にクロージャー(関数)が含まれ、引数をキャプチャします。この値はたまたま_1
_なので、closure1(2)
結果は_3
_(_1
_ + _2
_)です。
クロージャの宣言スコープに関するいくつかの情報を記憶していると考えてください。incrementBy
は、startAt
の内部に関するメモリ、特にその引数のx
のメモリを保持します。
ラムダ計算では、私が知っているように、それらの「非ローカル」変数は「自由」と呼ばれ、自由変数を持つ関数は「オープン項」と呼ばれます。クロージャーは、前述の「環境テーブル」にあるそれらの自由変数の値を「固定」することにより、オープン用語を「クローズ」するプロセスです。したがって、名前。
PythonとJSのクロージャは暗黙的に発生しますが、PHPでは、閉じる(キャプチャ))変数を明示的に指定する必要があることに注意してください:- http://php.net/manual/en/functions.anonymous.php -宣言のuse
キーワードに注意してください:
_// equivalent to the 1st example
function startAt($x) { // vvvvvvvv vv
$incrementBy = function ($y) use ($x) { return $x + $y };
return $incrementBy;
}
_
クロージャーはimplement関数への効率的な方法です。
私は、すべての関数が概念的にクロージャであると主張します。
閉じた変数は、定数またはコード内の静的データになります。しかし、本格的な closures では(Ocaml、Scheme、Common LISP、またはC++ 11のように)閉じた変数とコードはクロージャー自体にあります。
たとえば、C(およびC++ 98)では関数はクロージャですが、それらのclosed(またはfree )変数はstatic
またはグローバル変数に制限されています。
クロージャは、匿名関数の可能性ももたらします。 new閉じた変数でクロージャを作成するラムダ(これは標準Cでは不可能です)。これは abstractionλ-calculus の演算です(これはCには存在しません、これが、すべてのコールバック関数(GTKなど)が追加のパラメーターとして「クライアントデータ」を受け取る理由です。
実装の観点から見ると、関数は常にコードに必要なデータを含むコードです。データは閉じた変数です。コンパイルされたCコードの場合、データは、コード内でwired固定グローバル変数または静的変数(およびこれらの変数を変更するには再コンパイルが必要です)、またはリテラル定数です。したがって、抽象化を行う唯一の方法は、新しい変数をいくつか含む新しいコードをregenerateすることです。
移植性のある新しい関数を生成する方法がないため、標準Cには抽象化は存在しません。しかし、奇妙な実装固有のトリックを使用することもできます。翻訳ジェネレータ関数を実装したいとします。 ocamlでは、翻訳者delta
関数を生成する関数は次のとおりです。
let transl delta = fun x -> x + delta
これにより、呼び出しごとに新しいクロージャーが生成されます。だから
let t3 = transl 3 in t3 5
新しいクロージャーt3
が生成され(ただし、オプティマイザが削除している可能性があります)、結果は8(3 + 5)です。
Cで非常識なトリックを行うことができます:Linuxでは、たとえば実行時に生成されます新しいテキストファイルt3mod.c
含む
int t3(int y) { return y+3; };
そして、あなたはそれをコンパイルしますt3mod.c
共有オブジェクト内のファイルt3mod.so
とdlopen
それからdlsym
を使用して"t3"
。この考案された方法(実行時にCコード生成を使用してから動的にロードする)は、Cで抽象化を実装する方法であり、動的に生成する方法です新しい関数ポインタ。同様に、 JIT compiling ライブラリを使用できます。 libjit 、 [〜#〜] llvm [〜#〜] (または libgccjit
今後 [〜 #〜] gcc [〜#〜] 5)。 部分評価 & eval & マルチステージプログラミング についてもお読みください(例 Meta OCaml ...)。 meta-combinatorial search & meta-bugs に関するJ.Pitratのブログエントリも参照してください。