コンピュータサイエンスにおいて、継続とはコンピュータプログラムの制御状態を抽象的に表現したものです。継続はプログラムの制御状態を実装(具体化)します。つまり、継続はプロセスの実行における特定の時点での計算プロセスを表すデータ構造です。作成されたデータ構造は、実行環境内で隠蔽されるのではなく、プログラミング言語からアクセスできます。継続は、例外、ジェネレータ、コルーチンなど、プログラミング言語における他の制御メカニズムをエンコードするのに役立ちます。
「現在の継続」または「計算ステップの継続」とは、実行中のコードの観点から見て、プログラムの実行における現在の時点から派生する継続のことです。継続という用語は、第一級継続を指す場合にも使用されます。第一級継続とは、プログラミング言語が任意の時点で実行状態を保存し、プログラムの後の時点でその時点に戻る(場合によっては複数回)ことを可能にする構成要素です。
継続に関する最初の記述は、1964 年 9 月にAdriaan van Wijngaardenによって行われました。Wijngaarden は、オーストリアのバーデン・バイ・ウィーンで開催された IFIP 形式言語記述言語に関するワーキング カンファレンスで講演しました。Algol 60プリプロセッサの定式化の一部として、彼は適切な手続きを継続渡しスタイルに変換することを提唱しましたが[ 1 ]、この名前は使用せず、プログラムを簡素化して結果をより明確にすることを意図していました。
クリストファー・ストラッチー、クリストファー・P・ワズワース、ジョン・C・レイノルズは、関数型プログラミングの意味論の観点から逐次プログラムを分析できるようにするために継続を多用する指示的意味論の分野での研究において、継続という用語を広く普及させた。[ 1 ]
スティーブ・ラッセル[ 2 ]は、IBM 704向けの2番目のLisp実装で継続を発明したが、それに名前を付けなかった[ 3 ] 。
レイノルズ(1993)は、継続の発見に関する完全な歴史を述べている。
第一級継続とは、命令の実行順序を完全に制御できる言語の機能です。これを使うと、現在の関数を呼び出した関数、あるいは既に終了した関数にジャンプできます。第一級継続は、プログラムの実行状態を保存するものと考えることができます。真の第一級継続は、プロセスイメージとは異なり、プログラムデータではなく実行コンテキストのみを保存します。これは、「継続サンドイッチ」という説明で説明できます。
仮にあなたがキッチンで冷蔵庫の前に立ち、サンドイッチのことを考えているとしましょう。あなたはそこで続きを取り出してポケットに入れます。それから冷蔵庫から七面鳥とパンを取り出してサンドイッチを作り、それがカウンターの上に置かれます。ポケットの中の続きを呼び出すと、あなたは再び冷蔵庫の前に立ち、サンドイッチのことを考えていることに気づきます。しかし幸運なことに、カウンターの上にはサンドイッチがあり、それを作るのに使った材料はすべてなくなっています。だからあなたはそれを食べます。 :-) [ 4 ]
この説明では、サンドイッチはプログラムデータの一部(例えば、ヒープ上のオブジェクト)であり、「サンドイッチを作成する」ルーチンを呼び出して戻るのではなく、「現在の継続でサンドイッチを作成する」ルーチンを呼び出し、サンドイッチを作成してから、実行が中断された場所から実行を再開します。
Schemeは、最初に「catch」 [ 1 ]を提供し、次にcall/ccを提供する最初の完全な生産システムでした。Bruce DubaはSMLにcall/ccを導入しました。
継続は、表示的意味論、アクターモデル、プロセス計算、ラムダ計算などの計算モデルでも使用されます。これらのモデルでは、プログラマーまたは意味論エンジニアが、いわゆる継続渡しスタイルで数学関数を記述する必要があります。これは、各関数が、その関数呼び出しに関連する残りの計算を表す関数を消費することを意味します。値を返すには、関数はこの「継続関数」を戻り値とともに呼び出します。計算を中止するには、値を返します。
継続渡しスタイルでプログラムを書く関数型プログラマーは、制御の流れを自由に操作できる表現力を得られます。しかし、その代償として、制御と継続の不変条件を手動で維持する必要があり、これは非常に複雑な作業となる可能性があります(ただし、後述の「継続渡しスタイル」を参照してください)。
継続は、コルーチン/グリーンスレッドや例外処理など、いくつかの一般的なデザインパターンの実装を簡素化し、明確化します。継続は、一見無関係に見えるこれらのパターンを統合する基本的な低レベルのプリミティブを提供します。継続は、進むボタンや戻るボタン、リンクをたどることでアクセスできる複数のページをサポートするWebサーバーのプログラミングなど、いくつかの難しい高レベルの問題に対して、洗練された解決策を提供できます。Smalltalk Seaside Webフレームワークは継続を効果的に活用しており、ページ切り替え時に継続を切り替えることで、手続き型スタイルでWebサーバーをプログラミングできます。
「継続がエレガントな記述を提供する」[ 1 ]より複雑な構造も存在します。たとえば、Cでは、longjmp を使用して、ある関数の途中から別の関数にジャンプできます。ただし、2 番目の関数がスタックのより深い位置にある場合 (最初の関数の戻りを待っている場合、おそらく他の関数も含む) に限ります。その他のより複雑な例としては、Simula 67、Lua、Perlのコルーチン、 Stackless Pythonのタスクレット、IconおよびPythonのジェネレータ、 Scala (2.8 以降)の継続、 Ruby (1.9.1 以降)のファイバー、 Prologのバックトラッキング機構、関数型プログラミングのモナド、スレッドなどがあります。
Schemeプログラミング言語には、制御演算子call-with-current-continuation(略称:call/cc)が含まれており、Schemeプログラムはこれを用いて制御の流れを操作できます。
(継続#fを定義する)( define ( test ) ( let (( i 0 )) ; call/cc は最初の関数引数を呼び出し、; プログラム内のこの時点での継続変数をその関数の引数として渡します。; ; この場合、関数引数はその継続を変数 the-continuation に代入します。; ( call/cc ( lambda ( k ) ( set! the-continuation k ))) ; ; 次に the-continuation が呼び出されるときは、ここから開始します。( set! i ( + i 1 )) i ))上記を用いて、以下のコードブロックは、自身の将来の実行状態をtest設定する関数を定義します。the-continuation
> (テスト) 1 > (継続) 2 > (継続) 3 > ; 現在の継続 (次に 4 を出力する) を保存します> (別の継続を定義) the-continuation ) > (テスト) ; the-continuation 1をリセットします> (継続) 2 > (別の継続) ; 以前に保存された継続4を使用しますこのメカニズムについてより分かりやすく解説した説明については、「call-with-current-continuation」を参照してください。
この例は、コルーチンを別スレッドとして実装するための継続の使用例を示しています。 [ 5 ]
;;; スレッドスケジューリングのための素朴なキュー。;;; 実行待ちの継続のリストを保持します。( *queue* 'を定義())( define ( empty-queue? ) ( null? *queue* ))( define ( enqueue x ) ( set! *queue* ( append *queue* ( list x ))))( define ( dequeue ) ( let (( x ( car *queue* ))) ( set! *queue* ( cdr *queue* )) x ));;; これは新しいスレッド (proc) の実行を開始します。( define ( fork proc ) ( call/cc ( lambda ( k ) ( enqueue k ) ( proc ))));;; これは、別のスレッドが存在する場合、プロセッサを別のスレッドに譲ります。( define ( yield ) ( call/cc ( lambda ( k ) ( enqueue k ) (( dequeue )))));;; これは現在のスレッドを終了させます。;;; 他にスレッドが残っていない場合はプログラム全体を終了します。( define ( thread-exit ) ( if ( empty-queue? ) ( exit ) (( dequeue ))))上記で定義した関数により、協調型マルチタスク、つまりキュー内の次のスレッドに制御を譲るスレッドを定義および実行することが可能になります。
;;; 何らかの処理を行う典型的な Scheme スレッドの本体:( define ( do-stuff-n-print str ) ( lambda () ( let loop (( n 0 )) ( format #t "~A ~A \n " str n ) ( yield ) ( loop ( + n 1 )))));;; 2 つのスレッドを作成し、実行を開始します。( fork ( do-stuff-n-print "This is AAA" )) ( fork ( do-stuff-n-print "Hello from BBB" )) ( thread-exit )上記のコードを実行すると、以下の出力が得られます。
これはAAA 0です BBB 0 からこんにちは これはAAA 1です BBB 1 からこんにちは これはAAA 2です BBB 2 からこんにちは ...
プログラムは、関数が使用する変数のためにメモリ内に領域を割り当てる必要があります。ほとんどのプログラミング言語は、必要な変数を格納するためにコールスタックを使用します。これは、メモリの割り当てと解放を高速かつ簡単に行えるためです。他のプログラミング言語では、メモリの割り当てと解放に高いコストがかかるものの、柔軟性が得られるヒープを使用します。継続のコンテキストでは、これらの実装にはそれぞれ利点と欠点があります。[ 6 ]
多くのプログラミング言語は、さまざまな名称で第一級継続性を備えています。具体的には以下のとおりです。
co_await : , co_return,を使用して大まかに実装co_yieldasyncおよびawait: 「メソッドの残りの部分を継続として登録し、すぐに呼び出し元に戻ります。タスクが完了すると、継続が呼び出されます。」C# の非同期プログラミングcallcc0およびcallcc1Control.Monad.Contcreate, suspend, @演算子:共表現kotlin.coroutines.ContinuationContinuationContinuationPMC;すべての制御フローに継続渡しスタイルを使用call(exp())そしてcontinue(aContinuation, anyValue)_continuation.continuletscall-with-current-continuation一般的には と略されるcall/cc)callccscala.util.continuations提供shift/resetcall-with-current-continuation一般的には と略されるcall/cc)Continuation currentDo:; ほとんどの最新のSmalltalk環境では、追加のVMサポートなしで継続を実装できます。SMLofNJ.Cont.callccc現在の継続を伴う呼び出しのフロー制御操作クロージャと適切な末尾呼び出しをサポートする言語であれば、継続渡しスタイルでプログラムを記述し、call/cc を手動で実装することが可能です。(継続渡しスタイルでは、call/cc はラムダ式で記述できる単純な関数になります。)これは特にHaskell でよく用いられる手法で、Haskellでは「継続渡しモナド」を簡単に構築できます(例えば、ライブラリのContモナドとContTモナドトランスフォーマーなど)。適切な末尾呼び出しmtlのサポートが必要なのは、継続渡しスタイルではどの関数も戻り値を返さず、すべての呼び出しが末尾呼び出しとなるためです。
継続が実際に使用されている分野の一つに、Web プログラミングがあります。[ 7 ] [ 8 ]継続を使用することで、プログラマはHTTPプロトコルのステートレスな性質から保護されます。従来の Web プログラミング モデルでは、状態の欠如がプログラム構造に反映され、計算上の問題を表現するのに非常に不向きなモデルに基づいてコードが構築されます。したがって、継続は、制御の反転に関連する有用な特性を持ちながら、その問題を回避できるコードを可能にします。「制御の反転を逆転させる、または継続とページ中心のプログラミング」[ 9 ]は、Web プログラミングに適用される継続の良い入門書です。
継続のサポートは言語によって大きく異なります。継続が(既に戻り値を返した後でも)繰り返し呼び出される可能性がある場合、そのプログラミング言語は再呼び出し可能な継続をサポートしています。再呼び出し可能な継続は、Peter J. Landinがプロシージャ呼び出しの途中に制御フローを戻すことができるJ (ジャンプ) 演算子を使用して導入しました。再呼び出し可能な継続は、 Racket言語では「再入可能」とも呼ばれています。ただし、この「再入可能」という用語は、マルチスレッドの議論での使用と混同されやすいので注意が必要です。
より限定的な種類としては、現在のコンテキストから周囲のコンテキストへ脱出するために使用できるエスケープ継続があります。継続を明示的にサポートしていない多くの言語は例外処理をサポートしており、これはエスケープ継続と同等で、同じ目的で使用できます。C言語も同様で、スタックの巻き戻しsetjmp/longjmpにのみ使用できます。エスケープ継続は、末尾呼び出しの削除を実装するためにも使用できます。
継続の一般化の一つに、区切り継続があります。 のような継続演算子は、プログラムの特定の時点で残りの計算全体call/ccをキャプチャしますが、このキャプチャを区切る方法はありません。区切り継続演算子は、継続操作を区切るプロンプトと、や のような具体化演算子という、2つの独立した制御メカニズムを提供することで、この問題を解決します。したがって、区切り演算子を使用してキャプチャされた継続は、プログラムコンテキストの一部のみを表します。shiftcontrol
継続はGOTO文の機能表現であり、同じ注意点が適用されます。[ 10 ] Web プログラミングなどの特殊なケースでは理にかなった選択肢ですが、継続を使用すると、理解しにくいコードになる可能性があります。実際、難解なプログラミング言語Unlambdaには、現在の継続を使用した呼び出しが「追跡するのが非常に困難になる傾向がある」ためだけに、その機能の 1 つとして含まれています。[ 11 ]以下の外部リンクは、この概念をより詳細に説明しています。
クリス・バーカーは「継続性と量化の性質」の中で、「継続仮説」を導入し、
一部の言語表現(特にQNP[数量名詞句])は、自身の継続を操作する指示的意味を持つ。[ 12 ]
バーカーは、この仮説はNPの意味の二重性(例えば、QNP「everyone」が「Alice sees [Bob/everyone]」のような文の意味に寄与する際に、非数量名詞句「Bob」とは全く異なる振る舞いをするという事実)、スコープのずれ(例えば、「a raindrop fell on every car」が通常、次のように解釈される)などの現象を説明するために使用できると主張した。代わりに)、および範囲の曖昧さ(「誰かがみんなを見た」のような文は、そしてまた、この考え方はある意味でリチャード・モンタギューの「日常英語における数量化の適切な扱い」(PTQ)のアプローチの自然な拡張であると指摘し、「後から考えると、モンタギュー(1973)のPTQにおけるNPを一般化された数量詞として扱うことの中核には、限定的な継続伝達の形態が明確に見て取れる」と述べている。
自然言語における他の一般的な現象を説明するために継続をどの程度使用できるかは、現在研究されているトピックである。[ 13 ]