JavaScript閉パッケージその1:閉パッケージ概論関数式プログラミングにおけるいくつかの基本定義
6990 ワード
http://www.nowamagic.net/librarys/veda/detail/1707前述したように,役割ドメインチェーンと変数オブジェクトについて説明したが,閉包といえば容易に理解できる.かばんを閉じるのは実はみんなもう話が腐っている.それでも,ここでは,ECMAScriptにおける閉パケット内部がどのように動作しているかを理論的に議論してみる.
ECMAScript閉パッケージを直接議論する前に、関数プログラミングの基本的な定義を見る必要があります.
関数言語(ECMAScriptもこのスタイルをサポートしている)では、関数はデータであることが知られています.たとえば、関数は変数に値を割り当てることができ、パラメータが他の関数に渡されたり、関数から返されたりすることができます.このような関数には特殊な名前と構造があります.
定義#テイギ#
A functional argument (“Funarg”) — is an argument which value is a function.
関数式パラメータ(「Funarg」)-関数の値を持つパラメータです.
例:
上記の例のfunargの実際のパラメータは、実はexampleFuncに渡される匿名関数である.
逆に,関数式パラメータを受け入れる関数を高次関数(high-order function略称:HOF)と呼ぶ.関数関数関数または偏数理またはオペレータとも呼ばれます.上記の例では、exampleFuncはこのような関数です.
前述したように、関数はパラメータとしてだけでなく、戻り値としても使用できます.このような関数を戻り値とする関数を関数値付き関数(functions with functional value or function valued functions)と呼ぶ.
通常のデータ形式で存在する関数(たとえば、パラメータが伝達されると、関数式パラメータが受け入れられるか、関数値で返される)は、第1クラス関数(一般的に第1クラスオブジェクト)と呼ばれます.ECMAScriptでは、すべての関数が第1クラスのオブジェクトです.
関数は、通常のデータとして存在し得る(例えば、パラメータが伝達されると、関数式パラメータが受け入れられるか、または関数値で返される)ことを、第1のクラス関数(一般的には第1のクラスオブジェクト)と呼ぶ.
ECMAScriptでは、すべての関数が第1クラスのオブジェクトです.
自己適用関数(auto-applicative functionまたはself-applicative function)と呼ばれるパラメータとしての関数を受け入れます.
自己を戻り値とする関数を自己複製関数(auto-replicative functionまたはself-replicative function)と呼ぶ.通常、「自己複製」という言葉は文学作品で使われています.
自己複製関数の1つの興味深いパターンは,集合自体を受け入れる代わりに集合の1つだけをパラメータとして受け入れることである.
しかし,直接ダイバーシティを併用すると相対的に有効で直感的である.
関数パラメータで定義された変数は、「funarg」がアクティブになるとアクセスできます(コンテキストデータを格納する変数オブジェクトは、コンテキストに入るたびに作成されるためです):
しかし、ECMAScriptでは、関数は親関数にカプセル化され、親関数コンテキストの変数を使用することができます.この特性はfunarg問題を引き起こす.
Funarg問題
スタック向けのプログラミング言語では、関数のローカル変数はスタックに保存され、関数がアクティブになるたびに、これらの変数と関数パラメータがスタックに押し込まれます.
関数が返されると、これらのパラメータはスタックから削除されます.このモデルは、関数を関数式値として使用する場合に大きな制限があります(たとえば、親関数から戻り値として返します).ほとんどの場合、問題は関数に自由変数がある場合に発生します.
フリー変数とは、関数で使用されるが、関数パラメータでも関数のローカル変数でもない変数のことです.
例:
上記の例ではinnerFn関数に対してlocalVarは自由変数に属する.
スタック向けモデルを使用してローカル変数を格納するシステムでは、testFn関数呼び出しが終了すると、そのローカル変数がスタックから削除されることを意味します.これによりinnerFnが外部から関数呼び出しされるとエラーが発生する(localVar変数は既に存在しないため).
また,上記の例ではスタック実装モデルではinnerfnを戻り値で返すことは不可能である.testFn関数のローカル変数でもあるため、testFnの戻りに伴って削除されます.
もう1つの問題は,システムが動的役割ドメインを採用し,関数が関数パラメータとして使用される場合に関係する.
次の例(疑似コード)を参照してください.
動的役割ドメインを用いて,変数(識別子)のシステムが変数動的スタックによって管理されることを示した.したがって、自由変数は、関数の作成時に保存された静的役割ドメインチェーンではなく、現在アクティブなダイナミックチェーンでクエリーされます.
これで衝突が発生します.たとえば、Zがまだ存在していても(スタックから変数を除去した例とは逆)、異なる関数呼び出しでZの値はいったいどれを取るのか(どのコンテキスト、どの役割ドメインからクエリーするのか)という問題があります.
上述した2つのfunarg問題は、関数を戻り値で返すかどうか(第1の問題)と、関数を関数パラメータとして使用するかどうか(第2の問題)に依存する.
上記の問題を解決するために,閉パケットの概念を導入した.
クローズドパッケージ
閉パケットは、コードブロックと、そのコードブロックを作成するコンテキスト内のデータの結合である.
次の例(疑似コード)を見てみましょう.
上記の例では、「fooClosure」部分は疑似コードである.これに対応して、ECMAScriptでは、「foo」関数には、関数コンテキストの役割ドメインチェーンを作成する内部属性があります.
「lexical」は通常省略されます.上記の例では,閉パケット作成と同時にコンテキストのデータが保存されることを強調するためである.次にこの関数を呼び出すと、フリー変数は保存された(閉パッケージ)コンテキストで見つけることができ、上記のコードに示すように、変数「z」の値は常に10である.
定義で使用する比較的一般的な語である「コードブロック」ですが、通常(ECMAScriptでは)よく使われる関数が使用されます.もちろん、すべての閉パケットの実装が閉パケットと関数を結合するわけではありません.例えば、Ruby言語では、閉パケットは、プロセスオブジェクト(procedure object)、lambda式、またはコードブロックである可能性があります.
コンテキスト破棄後もローカル変数を保存する実装では、スタックベースの実装は、スタックベースの構造と矛盾するため、実装には適用されません.したがって、この場合、上位の役割ドメインの閉パケットデータは、メモリを動的に割り当てることによって実現され(スタックベースの実装)、ゴミ回収器(garbage collector略称GC)、参照カウント(reference counting)が併用される.この実装はスタックベースの実装よりも性能が低いが、いずれの実装も常に最適化できる:関数が自由変数、関数パラメータ、または関数値を使用しているかどうかを分析し、状況に応じて決定することができる--スタックにデータを格納するか、スタックに格納するかを決定する.
拡張読書
この記事のトピックのリストは次のとおりです. JavaScriptエンジンの動作原理 をどのように理解すればいいか JavaScriptプローブ:メンテナンス可能なコードの作成の重要性 JavaScriptプローブ:グローバル変数 を慎重に使用 JavaScriptプローブ:var事前解析と副作用 JavaScriptプローブ:forループ(for Loops) JavaScriptプローブ:for-inループ(for-in Loops) JavaScriptプローブ:Prototypesは を強すぎる JavaScript探秘:eval()は「悪魔」 JavaScriptプローブ:parseInt()による数値変換 JavaScriptプローブ:基本符号化仕様 JavaScriptプローブ:関数宣言と関数式 JavaScriptプローブ:名前付き関数式 JavaScriptプローブ:デバッガの関数名 JavaScriptプローブ:JscriptのBug JavaScriptプローブ:Jscriptのメモリ管理 JavaScript探秘:SpiderMonkeyの変な癖 JavaScriptプローブ:命名関数式代替案 JavaScriptプローブ:オブジェクトObject JavaScriptプローブ:プロトタイプチェーンPrototype chain JavaScriptプローブ:コンストラクション関数Constructor JavaScriptプローブ:実行可能コンテキストスタック 実行コンテキストその1:変数オブジェクトとアクティブオブジェクト 実行コンテキストその2:役割ドメインチェーンScope Chains 実行コンテキストその3:閉パッケージClosures 実行コンテキストその4:Thisポインタ JavaScriptプローブ:強力なプロトタイプとプロトタイプチェーン JavaScript関数その1:関数宣言 JavaScript関数その2:関数式 JavaScript関数その3:グループ内の関数式 JavaScript関数その4:関数コンストラクタ JavaScript変数オブジェクトその1:VOの宣言 JavaScript変数オブジェクトその2:VOは異なる実行コンテキストで JavaScript変数オブジェクトその3:実行コンテキストの2つのフェーズ JavaScript変数オブジェクトその4:変数 について JavaScript変数オブジェクトその5:_parent__ 属性 JavaScript役割ドメインチェーンその1:役割ドメインチェーン定義 JavaScript役割ドメインチェーンその2:関数のライフサイクル JavaScript役割ドメインチェーンその3:役割ドメインチェーン特徴 JavaScript閉パッケージその1:閉パッケージ概論 JavaScript閉パッケージその2:閉パッケージの実装 JavaScript閉パッケージその3:閉パッケージの使い方
ECMAScript閉パッケージを直接議論する前に、関数プログラミングの基本的な定義を見る必要があります.
関数言語(ECMAScriptもこのスタイルをサポートしている)では、関数はデータであることが知られています.たとえば、関数は変数に値を割り当てることができ、パラメータが他の関数に渡されたり、関数から返されたりすることができます.このような関数には特殊な名前と構造があります.
定義#テイギ#
A functional argument (“Funarg”) — is an argument which value is a function.
関数式パラメータ(「Funarg」)-関数の値を持つパラメータです.
例:
function exampleFunc(funArg) {
funArg();
}
exampleFunc(function () {
alert('funArg');
});
上記の例のfunargの実際のパラメータは、実はexampleFuncに渡される匿名関数である.
逆に,関数式パラメータを受け入れる関数を高次関数(high-order function略称:HOF)と呼ぶ.関数関数関数または偏数理またはオペレータとも呼ばれます.上記の例では、exampleFuncはこのような関数です.
前述したように、関数はパラメータとしてだけでなく、戻り値としても使用できます.このような関数を戻り値とする関数を関数値付き関数(functions with functional value or function valued functions)と呼ぶ.
(function functionValued() {
return function () {
alert('returned function is called');
};
})()();
通常のデータ形式で存在する関数(たとえば、パラメータが伝達されると、関数式パラメータが受け入れられるか、関数値で返される)は、第1クラス関数(一般的に第1クラスオブジェクト)と呼ばれます.ECMAScriptでは、すべての関数が第1クラスのオブジェクトです.
関数は、通常のデータとして存在し得る(例えば、パラメータが伝達されると、関数式パラメータが受け入れられるか、または関数値で返される)ことを、第1のクラス関数(一般的には第1のクラスオブジェクト)と呼ぶ.
ECMAScriptでは、すべての関数が第1クラスのオブジェクトです.
自己適用関数(auto-applicative functionまたはself-applicative function)と呼ばれるパラメータとしての関数を受け入れます.
(function selfApplicative(funArg) {
if (funArg && funArg === selfApplicative) {
alert('self-applicative');
return;
}
selfApplicative(selfApplicative);
})();
自己を戻り値とする関数を自己複製関数(auto-replicative functionまたはself-replicative function)と呼ぶ.通常、「自己複製」という言葉は文学作品で使われています.
(function selfReplicative() {
return selfReplicative;
})();
自己複製関数の1つの興味深いパターンは,集合自体を受け入れる代わりに集合の1つだけをパラメータとして受け入れることである.
//
function registerModes(modes) {
modes.forEach(registerMode, modes);
}
//
registerModes(['roster', 'accounts', 'groups']);
//
function modes(mode) {
registerMode(mode); // mode
return modes; //
}
// ,modes
modes('roster')('accounts')('groups')
// :jQueryObject.addClass("a").toggle().removClass("b")
しかし,直接ダイバーシティを併用すると相対的に有効で直感的である.
関数パラメータで定義された変数は、「funarg」がアクティブになるとアクセスできます(コンテキストデータを格納する変数オブジェクトは、コンテキストに入るたびに作成されるためです):
function testFn(funArg) {
// funarg , localVar
funArg(10); // 20
funArg(20); // 30
}
testFn(function (arg) {
var localVar = 10;
alert(arg + localVar);
});
しかし、ECMAScriptでは、関数は親関数にカプセル化され、親関数コンテキストの変数を使用することができます.この特性はfunarg問題を引き起こす.
Funarg問題
スタック向けのプログラミング言語では、関数のローカル変数はスタックに保存され、関数がアクティブになるたびに、これらの変数と関数パラメータがスタックに押し込まれます.
関数が返されると、これらのパラメータはスタックから削除されます.このモデルは、関数を関数式値として使用する場合に大きな制限があります(たとえば、親関数から戻り値として返します).ほとんどの場合、問題は関数に自由変数がある場合に発生します.
フリー変数とは、関数で使用されるが、関数パラメータでも関数のローカル変数でもない変数のことです.
例:
function testFn() {
var localVar = 10;
function innerFn(innerParam) {
alert(innerParam + localVar);
}
return innerFn;
}
var someFn = testFn();
someFn(20); // 30
上記の例ではinnerFn関数に対してlocalVarは自由変数に属する.
スタック向けモデルを使用してローカル変数を格納するシステムでは、testFn関数呼び出しが終了すると、そのローカル変数がスタックから削除されることを意味します.これによりinnerFnが外部から関数呼び出しされるとエラーが発生する(localVar変数は既に存在しないため).
また,上記の例ではスタック実装モデルではinnerfnを戻り値で返すことは不可能である.testFn関数のローカル変数でもあるため、testFnの戻りに伴って削除されます.
もう1つの問題は,システムが動的役割ドメインを採用し,関数が関数パラメータとして使用される場合に関係する.
次の例(疑似コード)を参照してください.
var z = 10;
function foo() {
alert(z);
}
foo(); // 10 –
(function () {
var z = 20;
foo(); // 10 – , 20 –
})();
// foo
(function (funArg) {
var z = 30;
funArg(); // 10 – , 30 –
})(foo);
動的役割ドメインを用いて,変数(識別子)のシステムが変数動的スタックによって管理されることを示した.したがって、自由変数は、関数の作成時に保存された静的役割ドメインチェーンではなく、現在アクティブなダイナミックチェーンでクエリーされます.
これで衝突が発生します.たとえば、Zがまだ存在していても(スタックから変数を除去した例とは逆)、異なる関数呼び出しでZの値はいったいどれを取るのか(どのコンテキスト、どの役割ドメインからクエリーするのか)という問題があります.
上述した2つのfunarg問題は、関数を戻り値で返すかどうか(第1の問題)と、関数を関数パラメータとして使用するかどうか(第2の問題)に依存する.
上記の問題を解決するために,閉パケットの概念を導入した.
クローズドパッケージ
閉パケットは、コードブロックと、そのコードブロックを作成するコンテキスト内のデータの結合である.
次の例(疑似コード)を見てみましょう.
var x = 20;
function foo() {
alert(x); // "x" == 20
}
// foo
fooClosure = {
call: foo // function
lexicalEnvironment: {x: 20} //
};
上記の例では、「fooClosure」部分は疑似コードである.これに対応して、ECMAScriptでは、「foo」関数には、関数コンテキストの役割ドメインチェーンを作成する内部属性があります.
「lexical」は通常省略されます.上記の例では,閉パケット作成と同時にコンテキストのデータが保存されることを強調するためである.次にこの関数を呼び出すと、フリー変数は保存された(閉パッケージ)コンテキストで見つけることができ、上記のコードに示すように、変数「z」の値は常に10である.
定義で使用する比較的一般的な語である「コードブロック」ですが、通常(ECMAScriptでは)よく使われる関数が使用されます.もちろん、すべての閉パケットの実装が閉パケットと関数を結合するわけではありません.例えば、Ruby言語では、閉パケットは、プロセスオブジェクト(procedure object)、lambda式、またはコードブロックである可能性があります.
コンテキスト破棄後もローカル変数を保存する実装では、スタックベースの実装は、スタックベースの構造と矛盾するため、実装には適用されません.したがって、この場合、上位の役割ドメインの閉パケットデータは、メモリを動的に割り当てることによって実現され(スタックベースの実装)、ゴミ回収器(garbage collector略称GC)、参照カウント(reference counting)が併用される.この実装はスタックベースの実装よりも性能が低いが、いずれの実装も常に最適化できる:関数が自由変数、関数パラメータ、または関数値を使用しているかどうかを分析し、状況に応じて決定することができる--スタックにデータを格納するか、スタックに格納するかを決定する.
拡張読書
この記事のトピックのリストは次のとおりです.