[解決済み] 継続とコールバックの違いは何ですか?
質問
継続についての知識を得るためにウェブを見て回っていますが、最も簡単な説明が、私のようなJavaScriptプログラマを全く困惑させるというのは、呆れるばかりです。特に、Schemeのコードやモナドを使って連続性を説明している記事が多いので、そう思います。
ようやく連続性の本質を理解できたと思ったところで、自分が知っていることが本当に真実なのかどうかを知りたくなった。もし、私が真実だと思っていることが、実は真実でないとしたら、それは無知であり、悟りではない。
そこで、私が知っていることを紹介します。
ほとんどすべての言語において、関数は呼び出し側に明示的に値(と制御)を返します。例えば
var sum = add(2, 3);
console.log(sum);
function add(x, y) {
return x + y;
}
第一級関数を持つ言語では、呼び出し元に明示的に返す代わりに、コントロールと戻り値をコールバックに渡すことができる。
add(2, 3, function (sum) {
console.log(sum);
});
function add(x, y, cont) {
cont(x + y);
}
このように、ある関数から値を返す代わりに、別の関数を続行させているのです。したがって、この関数は最初の関数の継続と呼ばれます。
では、コンティニュアスとコールバックの違いは何でしょうか?
どのように解決するのですか?
私は、継続はコールバックの特殊なケースであると考えています。ある関数は、任意の数の関数を、任意の回数、コールバックすることができます。例えば
var array = [1, 2, 3];
forEach(array, function (element, array, index) {
array[index] = 2 * element;
});
console.log(array);
function forEach(array, callback) {
var length = array.length;
for (var i = 0; i < length; i++)
callback(array[i], array, i);
}
しかし、ある関数が最後に別の関数を呼び出す場合、その2番目の関数は最初の関数の継続と呼ばれます。例えば
var array = [1, 2, 3];
forEach(array, function (element, array, index) {
array[index] = 2 * element;
});
console.log(array);
function forEach(array, callback) {
var length = array.length;
// This is the last thing forEach does
// cont is a continuation of forEach
cont(0);
function cont(index) {
if (index < length) {
callback(array[index], array, index);
// This is the last thing cont does
// cont is a continuation of itself
cont(++index);
}
}
}
ある関数が最後に別の関数を呼び出すことをテールコールという。Schemeのようにテールコールを最適化する言語もある。つまり、テールコールは関数呼び出しのようなオーバーヘッドを完全に発生させないということだ。その代わり、単純なgotoとして実装される(呼び出し元の関数のスタックフレームはテールコールのスタックフレームに置き換えられる)。
ボーナス : 継続渡しスタイルに進む。次のようなプログラムを考えてみましょう。
console.log(pythagoras(3, 4));
function pythagoras(x, y) {
return x * x + y * y;
}
さて、もしすべての演算(足し算、掛け算など)を関数の形で書いたとしたら、次のようになる。
console.log(pythagoras(3, 4));
function pythagoras(x, y) {
return add(square(x), square(y));
}
function square(x) {
return multiply(x, x);
}
function multiply(x, y) {
return x * y;
}
function add(x, y) {
return x + y;
}
さらに、もし値を返すことが許されないのであれば、以下のようにコンティニュエーションを使用しなければならない。
pythagoras(3, 4, console.log);
function pythagoras(x, y, cont) {
square(x, function (x_squared) {
square(y, function (y_squared) {
add(x_squared, y_squared, cont);
});
});
}
function square(x, cont) {
multiply(x, x, cont);
}
function multiply(x, y, cont) {
cont(x * y);
}
function add(x, y, cont) {
cont(x + y);
}
このように、値を返すことが許されない(したがって、継続を渡すことに頼らざるを得ない)プログラミングのスタイルを、継続渡しスタイルと呼びます。
しかし、継続渡しスタイルには2つの問題がある。
- 継続を渡すと、コールスタックのサイズが大きくなる。Schemeのように末尾の呼び出しを排除した言語でない限り、スタック容量が不足する危険性がある。
- ネストした関数を書くのが面倒。
最初の問題は、JavaScriptでは継続を非同期で呼び出すことで簡単に解決できる。継続を非同期に呼び出すことで、継続が呼び出される前に関数が返されます。そのため、コールスタックのサイズが大きくなることはありません。
Function.prototype.async = async;
pythagoras.async(3, 4, console.log);
function pythagoras(x, y, cont) {
square.async(x, function (x_squared) {
square.async(y, function (y_squared) {
add.async(x_squared, y_squared, cont);
});
});
}
function square(x, cont) {
multiply.async(x, x, cont);
}
function multiply(x, y, cont) {
cont.async(x * y);
}
function add(x, y, cont) {
cont.async(x + y);
}
function async() {
setTimeout.bind(null, this, 0).apply(null, arguments);
}
2つ目の問題は、通常、次のような関数を使用して解決されます。
call-with-current-continuation
と略されることが多いのですが
callcc
. 残念ながら
callcc
はJavaScriptで完全に実装することはできませんが、ほとんどの使用例について代替関数を書くことができます。
pythagoras(3, 4, console.log);
function pythagoras(x, y, cont) {
var x_squared = callcc(square.bind(null, x));
var y_squared = callcc(square.bind(null, y));
add(x_squared, y_squared, cont);
}
function square(x, cont) {
multiply(x, x, cont);
}
function multiply(x, y, cont) {
cont(x * y);
}
function add(x, y, cont) {
cont(x + y);
}
function callcc(f) {
var cc = function (x) {
cc = x;
};
f(cc);
return cc;
}
は
callcc
関数は関数
f
に適用し、それを
current-continuation
(と略称される)。
cc
). その
current-continuation
を呼び出した後に、関数本体の残りの部分をラップする継続関数です。
callcc
.
関数の本体を考える
pythagoras
:
var x_squared = callcc(square.bind(null, x));
var y_squared = callcc(square.bind(null, y));
add(x_squared, y_squared, cont);
は
current-continuation
の2番目の
callcc
があります。
function cc(y_squared) {
add(x_squared, y_squared, cont);
}
同様に
current-continuation
の最初の
callcc
があります。
function cc(x_squared) {
var y_squared = callcc(square.bind(null, y));
add(x_squared, y_squared, cont);
}
というのは
current-continuation
の最初の
callcc
には、別の
callcc
の場合は、継続渡しスタイルに変換する必要があります。
function cc(x_squared) {
square(y, function cc(y_squared) {
add(x_squared, y_squared, cont);
});
}
そのため、基本的には
callcc
は、関数本体全体を元の状態に論理的に変換します (そして、これらの無名関数には
cc
). このcallccの実装を使用したpythagoras関数は、次のようになります。
function pythagoras(x, y, cont) {
callcc(function(cc) {
square(x, function (x_squared) {
square(y, function (y_squared) {
add(x_squared, y_squared, cont);
});
});
});
}
ここでも
callcc
はJavaScriptで実装できますが、以下のようにJavaScriptで継続渡しスタイルで実装します。
Function.prototype.async = async;
pythagoras.async(3, 4, console.log);
function pythagoras(x, y, cont) {
callcc.async(square.bind(null, x), function cc(x_squared) {
callcc.async(square.bind(null, y), function cc(y_squared) {
add.async(x_squared, y_squared, cont);
});
});
}
function square(x, cont) {
multiply.async(x, x, cont);
}
function multiply(x, y, cont) {
cont.async(x * y);
}
function add(x, y, cont) {
cont.async(x + y);
}
function async() {
setTimeout.bind(null, this, 0).apply(null, arguments);
}
function callcc(f, cc) {
f.async(cc);
}
機能
callcc
は、try-catch ブロック、コルーチン、ジェネレータなどの複雑な制御フロー構造を実装するために使用することができます。
ファイバー
など。
関連
-
JSアレイループと効率解析の比較
-
VUEグローバルフィルターの概念と留意点、基本的な使い方
-
[解決済み】event.stopPropagationとevent.preventDefaultの違いは何ですか?
-
[解決済み] JavaScriptで "use strict "は何をするのか、その根拠は?
-
[解決済み] let "と "var "の使い分けは?
-
[解決済み] callとapplyの違いは何ですか?
-
[解決済み] Bowerとnpmの違いは何ですか?
-
[解決済み] コールバック内で正しい `this` にアクセスする方法
-
[解決済み] JavaScriptのnullとundefinedの違いは何ですか?
-
[解決済み] nullはなぜオブジェクトなのか、nullとundefinedの違いは何ですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
親子コンポーネント通信を解決する3つのVueスロット
-
要素ツリー制御によるvueTreeテーブル
-
Javascript Bootstrapのグリッドシステム、ナビゲーションバー、ローテーションの説明
-
Vueのクラススタイルの使い方の詳細
-
Vueのイベント処理とイベントモディファイアの解説
-
[解決済み] テスト
-
[解決済み】Node.js getaddrinfo ENOTFOUND
-
[解決済み】"フォームが接続されていないため、フォームの送信がキャンセルされました "というエラーの取得について
-
[解決済み】TypeErrorの解決方法。未定義またはヌルをオブジェクトに変換できない
-
[解決済み】ExpressJS - throw er Unhandled errorイベント