それらは何であり、何に適していますか?
私はCSの学位を持っておらず、私の経歴はVB6-> ASP->ASP.NET/C#です。誰かがそれを明確かつ簡潔に説明できますか?
それらは何であり、何に適していますか?
私はCSの学位を持っておらず、私の経歴はVB6-> ASP->ASP.NET/C#です。誰かがそれを明確かつ簡潔に説明できますか?
プログラムのすべての行が個別の関数であると想像してみてください。それぞれがパラメータとして、実行する次の行/関数を受け入れます。
このモデルを使用すると、任意の行で実行を「一時停止」して、後で続行できます。また、実行スタックを一時的にホップして値を取得したり、現在の実行状態をデータベースに保存して後で取得したりするなど、独創的な方法を実行することもできます。
あなたはおそらく、自分が思っているよりもそれらをよく理解しています。
例外は、「上向きのみ」の継続の例です。スタックの奥深くにあるコードが例外ハンドラーを呼び出して、問題を示すことができます。
Python の例:
try:
broken_function()
except SomeException:
# jump to here
pass
def broken_function():
raise SomeException() # go back up the stack
# stuff that won't be evaluated
ジェネレーターは、「下方向のみ」の継続の例です。たとえば、新しい値を作成するために、コードがループに再び入ることができます。
Python の例:
def sequence_generator(i=1):
while True:
yield i # "return" this value, and come back here for the next
i = i + 1
g = sequence_generator()
while True:
print g.next()
どちらの場合も、これらは特に言語に追加する必要がありましたが、継続のある言語では、プログラマーはこれらが利用できない場所でこれらのものを作成できます。
注意してください、この例は簡潔でもなく、非常に明確でもありません。これは、継続の強力なアプリケーションのデモンストレーションです。VB/ASP/C# プログラマーとして、システム スタックや保存状態の概念に慣れていない可能性があるため、この回答の目的はデモンストレーションであり、説明ではありません。
継続は非常に用途が広く、実行状態を保存して後で再開する方法です。以下は、Scheme で継続を使用した協調マルチスレッド環境の小さな例です。
(操作エンキューおよびデキューが、ここで定義されていないグローバル キューで期待どおりに機能すると仮定します)
(define (fork)
(display "forking\n")
(call-with-current-continuation
(lambda (cc)
(enqueue (lambda ()
(cc #f)))
(cc #t))))
(define (context-switch)
(display "context switching\n")
(call-with-current-continuation
(lambda (cc)
(enqueue
(lambda ()
(cc 'nothing)))
((dequeue)))))
(define (end-process)
(display "ending process\n")
(let ((proc (dequeue)))
(if (eq? proc 'queue-empty)
(display "all processes terminated\n")
(proc))))
これにより、関数で使用できる 3 つの動詞 (fork、context-switch、および end-process) が提供されます。fork 操作はスレッドを fork し、あるインスタンスでは #t を返し、別のインスタンスでは #f を返します。context-switch 操作はスレッド間を切り替え、end-process はスレッドを終了します。
これらの使用例を次に示します。
(define (test-cs)
(display "entering test\n")
(cond
((fork) (cond
((fork) (display "process 1\n")
(context-switch)
(display "process 1 again\n"))
(else (display "process 2\n")
(end-process)
(display "you shouldn't see this (2)"))))
(else (cond ((fork) (display "process 3\n")
(display "process 3 again\n")
(context-switch))
(else (display "process 4\n")))))
(context-switch)
(display "ending process\n")
(end-process)
(display "process ended (should only see this once)\n"))
出力は
entering test
forking
forking
process 1
context switching
forking
process 3
process 3 again
context switching
process 2
ending process
process 1 again
context switching
process 4
context switching
context switching
ending process
ending process
ending process
ending process
ending process
ending process
all processes terminated
process ended (should only see this once)
クラスで forking と threading を学習したことがある人には、このような例がよく与えられます。この投稿の目的は、継続を使用して、その状態 (継続) を手動で保存および復元することにより、単一のスレッド内で同様の結果を達成できることを示すことです。
PS - On Lisp でこれと似たようなことを覚えていると思うので、専門的なコードを見たい場合は、この本をチェックしてください。
継続を考える 1 つの方法は、プロセッサ スタックです。「call-with-current-continuation c」を実行すると、関数「c」が呼び出され、「c」に渡されるパラメーターは、すべての自動変数を含む現在のスタックです(さらに別の関数として表され、「k」と呼びます」)。一方、プロセッサは新しいスタックの作成を開始します。"k" を呼び出すと、元のスタックで "return from subroutine" (RTS) 命令が実行され、元の "call-with-current-continuation" (今から "call-cc") のコンテキストに戻ります。 on)、プログラムを以前と同じように続行できるようにします。「k」にパラメータを渡した場合、これが「call-cc」の戻り値になります。
元のスタックから見ると、「call-cc」は通常の関数呼び出しのように見えます。「c」の観点からは、元のスタックは決して返されない関数のように見えます。
ある数学者がケージに登り、ロックをかけて、他のすべてのもの (ライオンを含む) がケージの中にいるのにケージの外にいると宣言して、ライオンをケージに閉じ込めたという古いジョークがあります。継続は檻に少し似ていて、"c" は数学者に少し似ています。メインプログラムは「c」がその中にあると考えていますが、「c」はメインプログラムが「k」の中にあると信じています。
継続を使用して、任意のフロー制御構造を作成できます。たとえば、スレッド ライブラリを作成できます。「yield」は「call-cc」を使用して現在の継続をキューに入れ、キューの先頭にあるものにジャンプします。セマフォには中断された継続の独自のキューもあり、スレッドはセマフォ キューから取り外されてメイン キューに置かれることによって再スケジュールされます。
基本的に、継続とは、関数が実行を停止し、後で中断したところから再開する機能です。C# では、yield キーワードを使用してこれを行うことができます。必要に応じてさらに詳しく説明できますが、簡潔な説明が必要でした。;-)
私はまだ継続に「慣れ」ていますが、継続について考える 1 つの方法は、プログラム カウンター (PC) の概念を抽象化することです。PC は、メモリ内で実行する次の命令を「ポイント」しますが、もちろん、その命令 (およびほとんどすべての命令) は、暗黙的または明示的に、後続の命令、および割り込みを処理する命令を指します。(NOOP 命令でさえ、メモリ内の次の命令への JUMP を暗黙的に実行します。ただし、割り込みが発生した場合、通常、メモリ内の他の命令への JUMP が含まれます。)
任意の時点で制御がジャンプする可能性がある、メモリ内のプログラム内の潜在的に「ライブ」な各ポイントは、ある意味で、アクティブな継続です。到達可能なその他のポイントは、潜在的にアクティブな継続ですが、より正確には、現在アクティブな継続の 1 つ以上に到達した結果として、(おそらく動的に) "計算" される可能性がある継続です。
これは、実行中のすべての保留中のスレッドが静的コードへの継続として明示的に表される従来の継続の導入では、少し場違いに思えます。ただし、汎用コンピュータでは、PC が、その命令シーケンスの一部を表すメモリの内容を潜在的に変更する可能性のある命令シーケンスを指しているという事実を考慮に入れています。 ) その場での継続、その作成/変更に先立つ継続のアクティブ化の時点では実際には存在しないもの。
したがって、コンティニュエーションは PC の高レベル モデルと見なすことができます。これが、概念的に通常のプロシージャ コール/リターンを包含する理由です (古代の鉄が低レベル JUMP、別名 GOTO、命令と呼び出し時の PC と戻り時の復元)、例外、スレッド、コルーチンなど。
したがって、PC が「将来」に発生する計算を指すように、継続は同じことを行いますが、より高く、より抽象的なレベルで行われます。PC は、メモリに加えてすべてのメモリ ロケーションとレジスタを暗黙的に参照し、任意の値に「バインド」されますが、継続は言語に適した抽象化を介して未来を表します。
もちろん、通常はコンピュータ (コア プロセッサ) ごとに 1 台の PC しか存在しない可能性がありますが、実際には、上記で示唆したように、多くの "アクティブな" PC っぽいエンティティが存在します。割り込みベクトルには束が含まれ、スタックにはさらに束が含まれ、特定のレジスタにはいくつかが含まれる場合があります。これらは、値がハードウェア PC にロードされると「アクティブ化」されますが、継続は概念の抽象化であり、PC やその正確な等価物ではありません。 (「マスター」継続という固有の概念はありませんが、物事をかなりシンプルに保つために、これらの用語で考えたりコーディングしたりすることがよくあります)。
本質的に、継続は「呼び出されたときに次に何をすべきか」の表現であり、そのため、(一部の言語や継続渡しスタイルのプログラムでは、多くの場合)ファーストクラスのオブジェクトである可能性があります。他のほとんどのデータ型と同じように、インスタンス化され、渡され、破棄されます。また、従来のコンピューターが PCに対してメモリ位置を処理する方法と同様に、通常の整数とほとんど交換可能です。
C# では、2 つの継続にアクセスできます。1 つは、 を介してアクセスされreturn
、メソッドが呼び出された場所から続行できるようにします。もう 1 つは、 からアクセスしthrow
、最も近い一致する でメソッドを継続させcatch
ます。
一部の言語では、これらのステートメントをファーストクラスの値として扱うことができるため、それらを割り当てて変数で渡すことができます。return
これが意味することは、または の値を隠しておき、本当に戻るか投げる準備ができthrow
たときに後で呼び出すことができるということです。
Continuation callback = return;
callMeLater(callback);
これは多くの状況で便利です。1 つの例は上記のようなもので、実行中の作業を一時停止し、後で何かが発生したときに再開したい場合 (Web リクエストの取得など) があります。
私が取り組んでいるいくつかのプロジェクトでそれらを使用しています。1 つは、ネットワーク経由で IO を待機している間にプログラムを一時停止し、後で再開できるようにするためです。もう1つは、ユーザーが値としての継続にアクセスできるようにするプログラミング言語を作成しているため、ユーザーreturn
はthrow
自分自身のために、またはループなどの他の制御フローを、while
私がそれらを実行する必要なく記述できます。
スレッドについて考えてみましょう。スレッドを実行して、その計算結果を取得できます。継続はコピーできるスレッドなので、同じ計算を 2 回実行できます。
継続は、Web 要求の一時停止/再開の特性をうまく反映しているため、Web プログラミングに新たな関心が寄せられています。サーバーは、ユーザー セッションを表す継続を構築し、ユーザーがセッションを継続した場合に再開できます。