「継続」の版間の差分
編集の要約なし |
|||
6行目: | 6行目: | ||
=== 手続き型(命令型)プログラミング言語と継続 === |
=== 手続き型(命令型)プログラミング言語と継続 === |
||
次のようなBASICプログラムについて考える。 |
次のようなBASICプログラムについて考える。 |
||
< |
<syntaxhighlight lang=freebasic> |
||
10 GOTO 40 |
10 GOTO 40 |
||
20 PRINT "WORLD" |
20 PRINT "WORLD" |
||
13行目: | 13行目: | ||
50 GOTO 20 |
50 GOTO 20 |
||
60 END |
60 END |
||
</syntaxhighlight> |
|||
</source> |
|||
このgoto文を用いたBASICプログラムは以下のように手続きを用いて書きなおすことができる。 |
このgoto文を用いたBASICプログラムは以下のように手続きを用いて書きなおすことができる。 |
||
< |
<syntaxhighlight lang=freebasic> |
||
100 SUB L1 |
100 SUB L1 |
||
110 PRINT "WORLD" |
110 PRINT "WORLD" |
||
26行目: | 26行目: | ||
180 CALL L2 |
180 CALL L2 |
||
190 END |
190 END |
||
</syntaxhighlight> |
|||
</source> |
|||
継続の骨子は、このようにgoto文などの制御構文を手続きの呼び出しとして解釈することである。例えば、書きなおす前のBASICプログラムの50行を評価した時点における継続は具体的に表現する事は困難であるが、下のBASICプログラムであれば、単純に"HELLO"を表示した後に呼び出される手続き L1 と具体的に表現することができるようになる。 |
継続の骨子は、このようにgoto文などの制御構文を手続きの呼び出しとして解釈することである。例えば、書きなおす前のBASICプログラムの50行を評価した時点における継続は具体的に表現する事は困難であるが、下のBASICプログラムであれば、単純に"HELLO"を表示した後に呼び出される手続き L1 と具体的に表現することができるようになる。 |
||
==== Schemeマクロによる手続き言語的BEGIN文における限定的な継続の実装 ==== |
==== Schemeマクロによる手続き言語的BEGIN文における限定的な継続の実装 ==== |
||
32行目: | 32行目: | ||
次に示すreset-beginマクロはbegin文と基本的挙動は同一であるが、shift-begin文を用いることによって、shift-begin行以下の継続を扱うことができる。 |
次に示すreset-beginマクロはbegin文と基本的挙動は同一であるが、shift-begin文を用いることによって、shift-begin行以下の継続を扱うことができる。 |
||
< |
<syntaxhighlight lang=scheme> |
||
(define-syntax reset-begin |
(define-syntax reset-begin |
||
(syntax-rules (shift-begin) |
(syntax-rules (shift-begin) |
||
46行目: | 46行目: | ||
(let ((res exp1)) |
(let ((res exp1)) |
||
(reset-begin exp2 ...))))) |
(reset-begin exp2 ...))))) |
||
</syntaxhighlight> |
|||
</source> |
|||
上記マクロを用いた例 |
上記マクロを用いた例 |
||
< |
<syntaxhighlight lang=scheme> |
||
> (reset-begin |
> (reset-begin |
||
(display "1\n") |
(display "1\n") |
||
62行目: | 62行目: | ||
5 exit |
5 exit |
||
3 shifted |
3 shifted |
||
</syntaxhighlight> |
|||
</source> |
|||
=== 計算一般における継続 === |
=== 計算一般における継続 === |
2020年7月5日 (日) 22:41時点における版
継続は、前の状態を引き継ぐこと。持続、保持。 計算機科学における継続(けいぞく、continuation)とは、プログラムの実行においてある時点において評価されていない残りのプログラム(the rest of the program)を意味するものであり、手続き(procedure)として表現されるものである。
概要
手続き型(命令型)プログラミング言語と継続
次のようなBASICプログラムについて考える。
10 GOTO 40
20 PRINT "WORLD"
30 STOP
40 PRINT "HELLO"
50 GOTO 20
60 END
このgoto文を用いたBASICプログラムは以下のように手続きを用いて書きなおすことができる。
100 SUB L1
110 PRINT "WORLD"
120 STOP
130 END SUB
140 SUB L2
150 PRINT "HELLO"
160 CALL L1
170 END SUB
180 CALL L2
190 END
継続の骨子は、このようにgoto文などの制御構文を手続きの呼び出しとして解釈することである。例えば、書きなおす前のBASICプログラムの50行を評価した時点における継続は具体的に表現する事は困難であるが、下のBASICプログラムであれば、単純に"HELLO"を表示した後に呼び出される手続き L1 と具体的に表現することができるようになる。
Schemeマクロによる手続き言語的BEGIN文における限定的な継続の実装
上記説明における制御構文を手続き(procedure)として表現するという手法を用いると、begin文に限定的な継続を導入することが容易に可能となる。
次に示すreset-beginマクロはbegin文と基本的挙動は同一であるが、shift-begin文を用いることによって、shift-begin行以下の継続を扱うことができる。
(define-syntax reset-begin
(syntax-rules (shift-begin)
((_)
#f)
((_ exp)
exp)
((_ (shift-begin k body ...) exp2 ...)
(let ((programpoint (lambda (res)
(reset-begin exp2 ...))))
((lambda (k) body ...) programpoint)))
((_ exp1 exp2 ...)
(let ((res exp1))
(reset-begin exp2 ...)))))
上記マクロを用いた例
> (reset-begin
(display "1\n")
(display "2\n")
(shift-begin cont (cont 0) (display "3 shifted\n"))
(display "4\n")
(shift-begin cont (display "5 exit\n"))
(display "6\n")
(display "END\n"))
1
2
4
5 exit
3 shifted
計算一般における継続
複数の段階から成るあらゆる計算において、「継続」は存在している。まず、簡単な例として、算術の計算を考えてみよう[注釈 1]。
以下のような式を評価して、値を得るという計算を考える。
(((((1 + 2) + 3) + 4) + 5) + 6)
ちょうど「3を足す」という計算が終わったところだとする。
(((6 + 4) + 5) + 6)
この時、「4を足す」というのが次にするべき計算で、さらにその継続は「5を足して、さらに6を足す」というものである。
このように、コンピュータプログラムに限らず、あらゆる計算に継続は存在している。
その他の、プログラミング言語やプログラミングと継続
サブルーチンの呼び出しや割り込みの際には、プログラムの元の部分から実行を再開するための情報がスタックに積まれ、必要な処理が終わるとスタックからそれを戻して実行を再開するが、この際の「実行を再開するための情報」も継続の一種である。
C言語では、setjmp関数で継続を保存し、longjmp関数でその継続に飛ぶことができるが、標準では関数呼び出しの入れ子が浅くなる側に飛ぶ場合しか保証されておらず、そのようなジャンプしか出来ないのが一般的である。
Schemeでは、call-with-current-continuation(en:call-with-current-continuation、しばしばcall/ccと略される)という関数により、「現在の継続」を明示的に取り出し、プログラムで扱うことができる(継続が第一級オブジェクトである、という)。継続を、関数のようにして呼び出すと、その引数を返戻値として、call-with-current-continuationから戻る。
Rubyでは、C言語による実装(MRIとも呼ばれる)にも、同様のcallccというメソッドがある(バージョン1.9ではcontinuationライブラリをrequireする必要がある)。
JavaScriptでは、MozillaによるJavaScript処理系の実装であるRhinoにも継続のサポートがある[1]。
応用
実用
例外的な処理の扱い
たとえばC言語ではsetjmp/longjmpで実装するような、あるいはAda、C++、Javaなどの高水準プログラミング言語では言語機能として例外処理をサポートしているが、継続を扱うことができれば、同様の「今やっている計算を打ち切り、ネストの外側に値と処理を返す」、というふるまいをプログラムできる。
Web アプリケーション
Webアプリケーションにおいて、継続の利用が開発効率を上げるとして、継続を利用するスタイルでのWebアプリケーションの開発がおこなえるフレームワークが開発されており、Kahuaなどの実装例がある。通常、Webサーバではユーザからの HTTP リクエストは完全に独立したものとして扱われており、したがってサーバ上で走っているプログラムは個々のリクエストを独立した計算過程として完了しなければならなかった。しかし、多くの Web アプリケーションでは『ログイン』や『買い物カゴへの追加』など、あたかもサーバ上で連続した状態を保持しているかのような機能をユーザに対して提供する必要がある。従来のWebプログラミングでは、これは一連のリクエストをいくつかの状態に分割してサーバ上に (あるいはクッキーなどで)保存しておき、各リクエストごとにそこから復帰するという手法が一般的だったが、このようなプログラミングは複雑になりがちで、バグも起こりやすかった。しかし継続をサーバ上に保持できれば、プログラマは状態の分割をなにも考えずにあたかもユーザと 1対1で通信しているかのようなコードを書くことができる。これにより複雑な Web アプリケーションがより簡単に(バグも少なく)書けるようになると、それらのフレームワークの開発者は主張している。
理論
コルーチン
例外のようなスタックの巻き戻しのような処理ばかりではなく、コルーチンのように相互に呼び出し合うような機構も継続を使って実装できる(なお、コルーチンの実装には継続の持つ能力の全ては必要ではなく、また性能や機能の理由から、スレッドやファイバーで実装されるほうが多い)。
継続渡しスタイル
現在主流である、LIFOの関数呼び出しではなく、全ての関数呼び出しに、その関数が終わった後実行されるべき継続を、明示的に渡す、というプログラムのスタイルがあり、プログラマが書くコードとしてだけではなく、プログラミング言語処理系の中間表現としても使われており研究されている。
Schemeにおける継続
Schemeでは前述のように、継続が第一級オブジェクトであり、また簡単に実行中のコンテキストから継続を取り出して使うことができる。そればかりではなく、Schemeは仕様においてプログラム意味論が与えられているが、そこでも継続を利用して定義がおこなわれている。
プログラムの理論と継続
Scheme以前から、プログラムの理論として継続は意識されており[2]、SECDマシンの「D」は継続そのものである。また、手続き型(命令型)プログラミング言語の表示的意味論でも、gotoなどの意味を定めるために継続を使う。
限定継続
計算の残り全体を扱う継続は、扱いづらいのみならず、一種の副作用のようなふるまいをする[3]。
これに対し、限定継続(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のサーベイ中に示されている。
脚注
注釈
出典
参考文献
- Guy Lewis Steele, Jr. (1978), Rabbit: A Compiler for Scheme
- Guy Lewis Steele, Jr. and Gerald Jay Sussman (1976), Lambda: The Ultimate Imperative
- Peter J. Landin (1965), A Generalization of Jumps and Labels
- Christopher Strachey; Christopher P. Wadsworth (1974), Continuations: A Mathematical Semantics for Handling Full Jumps
- Michael J. Fischer (1972), Lambda-Calculus Schemata
- Gordon Plotkin (1975), Call-by-Name, Call-by-Value and the Lambda Calculus
- Olivier Danvy , Andrzej Filinski (1990), Abstracting Control
- Olivier Danvy , Andrzej Filinski (1992), Representing control: a study of the CPS transformation
- Andrzej Filinski (1994), Representing Monads
- Martin Gasbichler. Michael Sperber (2002), Final Shift for Call/cc: Direct Implementation of Shift and Reset
- Timothy G. Griffin (1990), A Formulae-as-Types Notion of Control
- 廣川 佐千男, 亀山 幸義, 馬場 謙介 (1997), λC計算とλP計算との対応
- 浅井 健一, shift/reset プログラミング入門, ACM SIGPLAN Continuation Workshop 2011 チュートリアルセッション資料
- John C.Reynolds (1993), The Discoveries of Continuations