web-dev-qa-db-ja.com

すべての関数はクロージャーですか?

ウィキペディアによれば、クロージャーは、関数の外部で宣言され、変数にアクセスできる関数です。例さえあります:

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
}

最終的に、これによりすべての関数がクロージャになります。どこが間違っているのですか?

5
Hast

いいえ、すべての関数がクロージャーであるとは限りません。

ウィキペディアは言う:

...クロージャ...は、関数または関数への参照と参照環境を組み合わせたもので、各その関数の非ローカル変数(自由変数またはアップバリューとも呼ばれます)。

「非ローカルおよび非グローバル」を追加しますが、考え方は正しいです。


C++もPython=の例もクロージャを使用していません。どちらの場合も、スコープルールが関数に外部スコープとグローバルスコープの表示を許可するだけです。


「クロージャ」は最初の例で発生します-incrementByが作成され、その外部関数から返されますキャプチャ引数xvariable 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;
}
_
25
scriptin

クロージャーは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.sodlopenそれからdlsymを使用して"t3"。この考案された方法(実行時にCコード生成を使用してから動的にロードする)は、Cで抽象化を実装する方法であり、動的に生成する方法です新しい関数ポインタ。同様に、 JIT compiling ライブラリを使用できます。 libjit[〜#〜] llvm [〜#〜] (または libgccjit 今後 [〜 #〜] gcc [〜#〜] 5)。 部分評価evalマルチステージプログラミング についてもお読みください(例 Meta OCaml ...)。 meta-combinatorial searchmeta-bugs に関するJ.Pitratのブログエントリも参照してください。