継続とcall/ccの概念を把握するために何度か試みました。すべての試みは失敗でした。理想的にはウィキペディアや他のSOの投稿よりも現実的な例で、これらの概念を説明してください。
私は Web プログラミングと OOP のバックグラウンドを持っています。私は 6502 アセンブリも理解しており、Erlang とちょっとしたランデブーをしました。それでも、call/cc について頭を悩ませることはできません。
C と比較すると、現在の継続はスタックの現在の状態のようなものです。現在の関数の結果が終了するのを待っているすべての関数があり、実行を再開できます。現在の継続としてキャプチャされた変数は、関数のように使用されますが、提供された値を受け取り、それを待機中のスタックに返す点が異なります。この動作は、スタックの下位部分にすぐに戻ることができるC 関数longjmpに似ています。
説明するためのスキーム REPL 相互作用を次に示します。
> (define x 0) ; dummy value - will be used to store continuation later
> (+ 2 (call/cc
(lambda (cc)
(set! x cc) ; set x to the continuation cc; namely, (+ 2 _)
3))) ; returns 5
5
> (x 4) ; returns 6
6
C スタックと継続の重要な違いの 1 つは、スタックの状態が変化した場合でも、プログラムの任意の時点で継続を使用できることです。これは、基本的にスタックの以前のバージョンを復元して何度も使用できることを意味し、独自のプログラム フローにつながります。
(* 123 (+ 345 (* 789 (x 5)))) ; returns 7
reason: it is because (x 5) replaces the existing continuation,
(* 123 (+ 345 (* 789 _))), with x, (+ 2 _), and returns
5 to x, creating (+ 2 5), or 7.
プログラムの状態を保存および復元する機能は、マルチスレッドと多くの共通点があります。実際、ここで説明しようとしたように、継続を使用して独自のスレッド スケジューラを実装できます。
スクリプトがビデオ ゲームのステージであると想像してください。Call/ccはボーナスステージのようなものです。
それに触れるとすぐに、ボーナス ステージ (つまり、call/cc [この場合は f] に引数として渡される関数の定義) に転送されます。
ボーナス ステージは通常のステージとは異なり、通常、要素 (つまり、call/cc に渡される関数の引数) を持っているため、それに触れると負けて通常のステージに戻されます。
たくさんあっても構いargs
ません。したがって、私たちの実行は到達(arg 42)
し、それを sum に返します(+ 42 10)
。
また、注目に値するいくつかの発言があります。
(define f (lambda (k) (+ k 42))
は使用できないためですsum
。(define f (lambda (k) (f 42 10)))
また、継続は1つの引数しか期待しないため、持つことはできません。touching
場合、関数は通常の関数と同じように処理されます (たとえば(define f (lambda (k) 42)
、終了して 42 が返されます)。call/cc を理解しようとしていたとき、このcall-with-current-continuation-for-C-programmersページが役に立ちました。
私を助けたのは、関数呼び出しを行う従来の言語では、関数呼び出しを行うたびに暗黙的に継続を渡すという考えです。
関数のコードにジャンプする前に、いくつかの状態をスタックに保存します (つまり、戻りアドレスをプッシュし、スタックには既にローカルが含まれています)。これは基本的に続きです。関数が終了したら、実行フローの送信先を決定する必要があります。スタックに格納された継続を使用し、戻りアドレスをポップしてそこにジャンプします。
他の言語では、この継続の考え方を一般化して、関数呼び出しが行われた場所から暗黙的に続行するのではなく、コードの実行を継続する場所を明示的に指定できるようにしています。
コメントに基づいて編集:
継続は完全な実行状態です。実行のどの時点でも、プログラムを 2 つの部分 (空間ではなく時間で) に分割できます。つまり、この時点まで実行された部分と、ここから実行されるすべての部分です。「現在の継続」とは、「ここから実行されるすべてのもの」です (プログラムの残りの部分で実行されるすべてのことを実行する関数のようなものと考えることができます)。したがって、指定した関数には、呼び出されたcall/cc
ときに現在の継続が渡されます。call/cc
関数は、継続を使用して実行をcall/cc
ステートメントに戻すことができます (ただし、継続を直接使用すると、代わりに単純な戻りができるため、継続を別のものに渡す可能性が高くなります)。
FScheme の call/ccの説明と実装を見てみましょう
call/cc を理解するには複数のレベルがあります。まず、用語とメカニズムがどのように機能するかを理解する必要があります。次に、「実際の」プログラミングで call/cc がいつ、どのように使用されるかを理解する必要があります。
最初のレベルは CPS を学習することで到達できますが、別の方法があります。
第 2 レベルでは、フリードマンの次の古典をお勧めします。
ダニエル・P・フリードマン。 「継続の適用: 招待チュートリアル」 . 1988 プログラミング言語の原則 (POPL88)。1988 年 1 月。
命令的な観点から継続を理解するために私が使用したモデルは、それが次の命令へのポインターと結合された呼び出しスタックのコピーであるというものです。
Call/cc は、継続を引数として (引数として渡された) 関数を呼び出します。