継続

提供: testwiki
ナビゲーションに移動 検索に移動

テンプレート:See Wiktionaryテンプレート:参照方法 計算機科学における継続(けいぞく、continuation)とは、プログラムを実行中のある時点において、評価されていない残りのプログラム(the rest of the program)を表現するものであり、手続き(procedure)あるいは関数(function)として表現されるものである[1]

継続に相当する概念は1960年代初頭から存在しており、Algol 60のコンパイラの実装[2]などの文献にたびたび登場していたが、継続の利用に関する最も早い記述は、1964年のアドリアン・ファン・ワインハールデンテンプレート:Enlinkによるものである[1]

概要

計算一般における継続

Schemeによる次の式を考える:

(+ 4 (+ 1 2))

この式を評価する際、まず テンプレート:Code が計算され、すなわち 1+2 が計算され、次にその結果に4を足して全体の計算結果が求められる。テンプレート:Code の評価が行われた段階での「残りの計算」を表現すると、

(lambda (v) (+ 4 v))

のようになる[3]。この式はすなわち、値 テンプレート:Mvar を引数に取り、それに4を足した値を返す関数である。実際、この後 テンプレート:Code の計算結果が テンプレート:Mvar に代入されて、4を足した値が最終的に計算結果が求められるため、この関数は確かに テンプレート:Code を評価する段階での「残りの計算」の表現である。

call/cc

Schemeの テンプレート:Code (テンプレート:Code と省略される) は、その時点での継続を引数として関数を呼び出す手続きである。Schemeの言語仕様書(R7RS[4])には「もっとも単純な例」として次のコードが載っている:

(define list-length
  (lambda (obj)
    (call-with-current-continuation
      (lambda (return)
        (letrec ((r
              (lambda (obj)
                (cond ((null? obj) 0)
                  ((pair? obj)
                    (+ (r (cdr obj)) 1))
                  (else (return #f))))))
          (r obj))))))

このコードは、真正な(終端が空リストである)リストが渡された際にはそのリストの要素数を数えて返し、そうでない場合はfalse値を返す。

goto文を持つ言語の意味論

継続の概念はgoto文を持つ言語に意味論を与える。goto文を持たない場合、意味論は例えば命令文 テンプレート:Mvar、変数に対する値の割り当て テンプレート:Mvar、抽象機械の状態遷移 テンプレート:Math (テンプレート:Mvar は機械の状態空間を表す) を用いて𝒞[γ](ρ)=θのように与えられる[5][6]。この意味論において、2つの命令の逐次実行は2つの状態遷移の合成として表すことができるが、goto文が存在する言語の場合、goto文の直後の命令は、goto文を実行した直後に実行されるわけではないため、少なくともそのように意味論を与えることはできない。


goto文を持つような言語において、意味論を与える写像 𝒫 は例えば、命令文の集合 テンプレート:Mvar、環境の集合 テンプレート:Mvar、継続の集合 テンプレート:Mvar、機械の状態の集合 テンプレート:Mvar を用いて次のような型を持つ:𝒫:[𝐶𝑚𝑑[𝐸𝑛𝑣[C[SS]]]]ここで継続は テンプレート:Math すなわち状態の遷移として、また環境 テンプレート:MvarDC を満たすようなラベルの集合 テンプレート:Mvar と識別子の集合 テンプレート:Mvar を用いて テンプレート:Math と表される。命令文 テンプレート:Mvar と環境 テンプレート:Mvar に対して、𝒫[γ]ρ は継続を受け取って継続を返す関数の形となる。


この設定の下でgoto文はラベルの識別子 ξ𝐼𝑑 に対して𝒫[𝐠𝐨𝐭𝐨ξ]ρθσ=(ρ[ξ]|C)σのように実装される。ここで ρ[ξ]|C とは ρ[ξ]Dテンプレート:Mvar への射影であり、動的な型検査としての働きを持つものである[5]

応用

実用

例外的な処理の扱い

たとえばC言語ではsetjmp/longjmpで実装するような、あるいはAdaC++Javaなどの高水準プログラミング言語では言語機能として例外処理をサポートしているが、継続を扱うことができれば、同様の「今やっている計算を打ち切り、ネストの外側に値と処理を返す」、というふるまいをプログラムできる。

Web アプリケーション

Webアプリケーションにおいて、継続の利用が開発効率を上げるとして、継続を利用するスタイルでのWebアプリケーションの開発がおこなえるフレームワークが開発されており、Kahuaなどの実装例がある。通常、Webサーバではユーザからの HTTP リクエストは完全に独立したものとして扱われており、したがってサーバ上で走っているプログラムは個々のリクエストを独立した計算過程として完了しなければならなかった。しかし、多くの Web アプリケーションでは『ログイン』や『買い物カゴへの追加』など、あたかもサーバ上で連続した状態を保持しているかのような機能をユーザに対して提供する必要がある。従来のWebプログラミングでは、これは一連のリクエストをいくつかの状態に分割してサーバ上に (あるいはクッキーなどで)保存しておき、各リクエストごとにそこから復帰するという手法が一般的だったが、このようなプログラミングは複雑になりがちで、バグも起こりやすかった。しかし継続をサーバ上に保持できれば、プログラマは状態の分割をなにも考えずにあたかもユーザと 1対1で通信しているかのようなコードを書くことができる。これにより複雑な Web アプリケーションがより簡単に(バグも少なく)書けるようになると、それらのフレームワークの開発者は主張している。

マイクロカーネル(Mach 3.0)

Mach 3.0では、あるタスクがスリープ中の別のタスクを実行可能とする一方、自分自身はその処理待ちでスリープする状況において継続をサポートしている。

Mach 3.0は当時既存のUnix系OSとの互換性や応用を維持しつつ本格的なマイクロカーネルを実装したが、それに伴いCPUのスレッド数に比べてタスクのスレッド数がはるかに多くなった。これらのタスクはカーネル内でスリープする際にカーネルスタックを必要とし、そのためのメモリ消費がシステム全体を圧迫する問題が生じた。この問題を軽減するため、Mach 3.0ではタスクがスリープする際にオプションとして継続の規約をサポートし、保存が必要なデータをタスクに紐付いたメモリに退避した上でカーネルスタックを回収できるようにした。継続を使用してスリープしているタスクが実行可能となった場合は、カーネルから新たにカーネルスタックを受け取った上でタスクのメモリからデータを復元し、実行を再開するようにした。実行再開時のエントリポイントはスリープ時の処理とは別の箇所でも問題なく、これにより継続のセマンティクスを実装している。

回収されたカーネルスタックは通常は実行可能になった別のタスクへそのまま渡し、直ちに再利用するようにしている。これにより、カーネルスタックは原則として各タスクではなくCPUのスレッドに紐付いたリソースとしている。

理論

コルーチン

例外のようなスタックの巻き戻しのような処理ばかりではなく、コルーチンのように相互に呼び出し合うような機構も継続を使って実装できる(なお、コルーチンの実装には継続の持つ能力の全ては必要ではなく、また性能や機能の理由から、スレッドファイバーで実装されるほうが多い)。

継続渡しスタイル

テンプレート:Main 現在主流である、LIFOの関数呼び出しではなく、全ての関数呼び出しに、その関数が終わった後実行されるべき継続を、明示的に渡す、というプログラムのスタイルがあり、プログラマが書くコードとしてだけではなく、プログラミング言語処理系の中間表現としても使われており研究されている。

Schemeにおける継続

Schemeでは前述のように、継続が第一級オブジェクトであり、また簡単に実行中のコンテキストから継続を取り出して使うことができる。そればかりではなく、Schemeは仕様においてプログラム意味論が与えられているが、そこでも継続を利用して定義がおこなわれている。

プログラムの理論と継続

Scheme以前から、プログラムの理論として継続は意識されており[7]SECDマシンの「D」は継続そのものである。また、手続き型(命令型)プログラミング言語の表示的意味論でも、gotoなどの意味を定めるために継続を使う。

限定継続

計算の残り全体を扱う継続は、扱いづらいのみならず、一種の副作用のようなふるまいをする[8]

これに対し、限定継続(en:delimited continuation・restricted ~、partial continuation 部分継続などとも)は、継続のうち、ある所までの計算を区切って取り出すことができる、というもので、近年研究や実装が進められている。限定継続に対し(ふつうの)継続をundelimited continuationなどと言う。

shiftとresetと呼ばれるプリミティブにより、限定された継続が作られる。Schemeでcall/ccを使った実装例が、論文 "Final shift for call/cc:: direct implementation of shift and reset" 中の§2のサーベイ中に示されている。

脚注

テンプレート:脚注ヘルプ

出典

テンプレート:Reflist

参考文献