私はErlangコンパイラソースを調べながらこれを実行しました。「ラムダリフティング」とは何ですか?
私は本当にそれを取得していません。 (私はちょうど5分前にそんなことがあることに気がついたと考えている)。
最初にその存在理由を理解することなく尋ねたことを私に許してください。
約wikipedia articleがありますが、かなりわかりにくいです。
私はErlangコンパイラソースを調べながらこれを実行しました。「ラムダリフティング」とは何ですか?
私は本当にそれを取得していません。 (私はちょうど5分前にそんなことがあることに気がついたと考えている)。
最初にその存在理由を理解することなく尋ねたことを私に許してください。
約wikipedia articleがありますが、かなりわかりにくいです。
ラムダリフティングは、クロージャを純粋な関数にするために使用されます。関数に余分な引数を渡すことによって、自由変数の数を減らすことができます。ラムダをより高いスコープと「高いスコープ」に「持ち上げる」ときに、そのスコープで宣言されたローカル変数に対応する引数を追加します(それ以外の場合はフリー変数です)。ラムダに自由変数がないと、それは純粋な「トップレベル」関数です。
もちろん、すべてのラムダのコールサイトを知っている場合にのみこれを行うことができます。言い換えれば、ラムダが脱出しない場合に限る。
コンパイラオプティマイザの利点は、クロージャ(関数環境)を排除できることです。これは、引数を自由変数としてスタック(またはヒープ)に割り当てるのではなく、レジスタに渡すことを可能にするかもしれません。
クロージャーセンスの自由変数は、そのレキシカルな環境から来ているものです(定義の範囲?)。 トップレベル関数は(erlangの場合)モジュール関数ですか? すでにモジュールレベルの関数にあるunbound変数はどうですか? 詳細な回答ありがとう – deepblue
はい、はい、多分。 –
ちょうどそれがスピードアップする理由を得て、単純なJSの例の後でそれを実現しました。あなたもそれを持ち出しました。 まだ、あなたが持ち上げを実行するためにラムダのコールサイトを知っていなければならない理由はまだありません...それは定義されたlex envだけです(これはフリーヴァルスがどこから来たからですか)。 – deepblue
ラムダリフティングは基本的に変数を排除し、それらを純関数に置き、実行を単純化します。
なぜそれが実行を簡素化するのですか?鈍い質問を申し訳ありません。ウィキペディアの記事は同じことを言いました。すなわち、持ち上げの目的は物事をスピードアップすることです。自由変数の値をホストする環境を維持する必要がないため、 – deepblue
@deepblue(+ 4年後)。すべての自由変数は引数に変換されるので、関数を評価/実行するためには、扱うべき引数だけです。クロージャが作成された後も、環境を維持し、クロージャが作成された後もそれを維持しておくことは、言語に応じて複雑な作業になる可能性があります。例えば。 Schemeでは非常に複雑です。 –
ラムダリフトは、(主にトップレベル)より高いレベルに `リフト」ラムダする技術です。
Doug Currieがなぜこれをやりたいのか説明しています。あなたは可能性 `リフト」それにaddFive
の定義内でこのaddX
機能を使用しない場合は今すぐ
function addFive(nr)
{
var x = 5;
function addX(y)
{
return x + y;
}
return addX(nr);
}
:ここ
は、あなたがこれを手動で行うことができる方法の(JavaScriptで)いくつかのサンプルコードですそのようなトップレベル:x
変数は
addX
機能のコンテキストではもう入手できないので
function addX(y)
{
return x + y;
}
function addFive(nr)
{
var x = 5;
return addX(nr);
}
しかし、これは、動作しません。この問題を解決する方法は、関数に余分な仮パラメータを追加することです :
function addX(y, x)
{
return x + y;
}
function addFive(nr)
{
var x = 5;
return addX(nr, x);
}
追加:はここにエスケープ `ラムダの非常に不自然な例です。私が説明したようにラムダを簡単に持ち上げることができない場所。誰かがgetAddFiveFunc
関数を呼び出す場合
function getAddFiveFunc()
{
var x = 5;
function addX(y)
{
return x + y;
}
return addX;
}
今、彼らが機能を取り戻すだろう。この機能はあらゆる場所で使用できます。addX
機能を持ち上げたい場合、これらのコールサイトをすべて更新する必要があります。
非常に簡単な例、私は今それを得る:)ありがとう。 フリー変数は、クロージャの周囲の語彙的環境から「引っ張られた」変数であると考えられますか?ああ、だから、物を持ち上げる方法は、もはやすべての閉鎖のために語彙的環境を運ぶ必要はないということですか? – deepblue
私はちょうど、クロージャがそのコンテナ関数から返されたときに何が起こるかについて尋ねるつもりでした。あなたはまだそれを持ち上げたいと思う、どうすればコンテナfuncのローカル変数をクロージャの外部呼び出し側に公開するのですか?それらをパラメータとして渡すために)? – deepblue
コンパイラがコンパイル時に完全なプログラムにアクセスできる場合は、すべての呼び出しサイトを更新することができます。しかし、ほとんどのコンパイラでは、各モジュールが別々にコンパイルされる別々のコンパイルが可能です。その場合、ラムダがモジュールの境界を横切る場合、ラムダリフティングは不可能です。 –
警告:私の答えは、ラムダ持ち上げとは異なるキャプチャされた変数について実際に説明しています。質問を誤解する(睡眠を必要とする)。しかし、私はこれを書いて少し時間を費やしたので、私はそれを削除するのは嫌です。コミュニティWIKIとして放棄してください。
ラムダリフティング(しばしばクロージャと呼ばれる)は、ネストされたラムダ式内からスコープ変数へのアクセスをシームレスに許可する方法です。
特定の言語を選択せずにクロージャーの細かい部分に入るのは難しいです。ラムダリフティングの副作用の1つは、変数の存続期間を、ローカルで短命のスコープから、より長く存続するスコープにまで拡張する傾向があることです。通常、これは、スタックからコンパイラ内のヒープに変数を転送する形式で行われます。これは言語特有の動作であり、言語に基づいて非常に異なる実装を生成します。
これはおそらくスタックオーバーフローの読者に最も一般的な言語なので、C#に焦点を当てます。以下のコードから始めましょう。我々は2つのラムダ式を作成した。この例では
public Func<int> GetAFunction() {
var x = 42;
Func<int> lambda1 =() => x;
Func<int> lambda2 =() => 42;
...
return lambda1;
}
。どちらの場合も、Func型のデリゲートインスタンスに割り当てられます。 .Netのすべての代議員は、実際の機能がどこかにそれらをバックアップすることを要求します。フードの下では、C#のすべてのラムダ式/無名関数がメソッド定義に変換されます。
lambda2の関数を生成するのはかなり簡単です。それは単なる値を返す孤立した関数です。
public static int RealLambda2() {
return 42;
}
ラムダ1の生成はかなり困難です。リテラルの定義は、xがアクセスできないので、これは明らかにコンパイルされません、次の
public static int RealLambda1() {
return x;
}
ようになります。この作業を行うために、C#コンパイラはをクロージャに持ち上げてにする必要があります。それから、これは非常に単純な例ですが、うまくいけば、詳細はリフティングの芸術べき
class Closure1 { int x; public int RealLambda1() { return x; } }
デリゲート式を満たすようにクロージャー内の関数へのポインタを返すことができます。残念ながら、悪魔は細部にいてシナリオの複雑さを増しています。
クロージャーの最初の記事 - http://en.wikipedia.org/wiki/Closure_(computer_science) – dirkgently