継続の実装は、何を重視するかでかなり違ってくるせいでしょうね。
full continuationの実装は、スタックエリアのコピーという観点
から見ると、次のように分類されます。
(1) 継続作成時にスタックからヒープへのコピーを行い、
継続呼び出し時にヒープからスタックへのコピーを行う
(2) 継続作成時にスタックからヒープへのコピーを行うが、
継続呼び出し時にはコピーを行わない
(3) 継続作成時にはコピーを行わず、
継続呼び出し時にコピーを行う
(4) 継続作成時にも継続呼び出し時にもコピーは行わない
guileやSCMは(1)のタイプです。このタイプは、CとSchemeの
スタックが自由にインターリーブできるのが特徴です(Scheme
ルーチンから呼んだCルーチンからさらにSchemeルーチンを
簡単に呼べる)。したがってC APIが単純になります。また、
スタックエリアのGCを必要としません。しかしコピーの回数が
多いため、継続は非常に重くなります。
SCMよりguileがかなり重いのは何故だかわかりません。
Gaucheは(2)の方法を利用しています。(1)と同様にスタック
エリアのGCが不要で、コピーの回数が少ないのが特徴です。
しかし、呼ばれない継続でも作られる時点でコピーする必要が
あるのであまり良い方法ではないです。将来、スタックエリア
のGCを実装できたら(3)か(4)に移りたいと考えています。
(3)はChez Schemeへの実装の論文があります。Petite Chez
Schemeで使われているかどうかは知りませんが、おそらく
使われているんではないでしょうか。スタックエリアのGCと、
スタックアンダーフローを検出する一種のバリアが必要です。
継続の作成はほぼノーコストです。継続の呼び出しには若干の
オーバヘッドがあります。
(4)は実装技術的にはさらに2つに分かれます。継続を全て
ヒープアロケートする方法と、スタックフレームをポップしない
方法です。完全なcontinuation passing styleなので、
継続の作成、起動ともに理論上ノーコストでできる方法です。
しかし、継続を全てヒープアロケートするのはメモリアクセスの
ローカリティが悪く、最近のCPUでは不利になります。
Chickenは後者の方法 (ポップしないスタックフレーム)を使って
います。理屈の上ではこれが一番良い方法のはずですが、スタック
エリアのGC回数が(3)よりずっと多くなるため、総合的な性能で
比較すると微妙なところです。
--shiro
2011/01/11
継続の実装方針
2010/12/20
あとで読む部分継続(限定継続)(と、継続)
- Gauche ユーザリファレンス: 9.17 gauche.partcont - 部分継続
- Scheme:部分継続:イテレータの反転
- 部分継続について本気出して考えてみた - (new Hatena).blog()
- 部分継続便利だなー。 - podhmoの日記
- composable-continuations-tutorial
- delimited continuation - ocaml-nagoya
- Paste number 8952: SHIFT/RESET 'generator' example
- 星の贈り物(2007年11月の日記)
- 星の贈り物(2006年10月の日記)
- d.y.d.
- w.l.o.g.
余談
面白かったのが経由で読んだこれ
「難しいのは継続ではないcallccだ…とすると、 難しいのは限定継続ではなくshift/resetなのでは?」これわかるような気がします。継続自体も難しいけど、call/cc がまた難しい。以前 let/cc はわかるけど call/cc がわからないという状態がありました。。今でも let/cc ベースで call/cc を認識しているわけですが・・・。(The Seasoned Schemer の影響もあり)
こんな感じで・・・
(let1 loop #f (let1 i 0 ;; (let/cc c (set! loop c)) (call/cc (^c (set! loop c))) (print i) (inc! i) (when (< i 10) (loop))))
余談2
そういえば、継続といえば、amb カッチョイイですよね。(return の記事おもしろいなあ。。)
ついでみたいだけど
2010/05/26
syntax-rules: amb
確か、On Lisp や SICP (計算機プログラムの構造と解釈)にも出てくるらしいですね。
わけわかめ。
追記
syntax-rules: try
英語の方は、ほとんどわかりません。The Seasoned SchemerやThe Little Schemer, 4th Editionは雰囲気と勢いで読みました。
The Seasoned Schemerに出てくる try のことを思い出しました。
2010/05/24
2010/04/02
継続 continuation
ごく簡単なコードについては
- 継続が見えるようになってきた
- CPS(継続渡しスタイル)に書き換えることができるようになってきた
- call/ccによる脱出が書けるようになってきた
- 継続呼び出しによる「脱出」が結果的にそう見えるだけで、厳密には「脱出」という表現は相応しくないのではないか、と思うようになった
- call/ccが使われているコードに対する抵抗が減った
- 少し複雑になるだけで継続なんてちっとも見えやしない
- 厳密なCPSなんて全く書けやしない
- call/ccが散在するコードを見ると目が回る
2010/03/25
2010/03/24
2010/03/23
よくわかるcall/cc
継続あれこれ
「ある時点での状態」を変数に束縛して、後からそこへ戻るといったことも可能になる。Schemeの継続は関数の形をとっており、これは「その関数を呼ぶことにより、そこに渡された引数をcall/ccで囲まれている式全体の値として継続に“注入”できる」と定義されている。
プログラムの実行順序を制御する概念
既に終了したスタックフレームを黄泉の国か ら引きずり出してスタックにぶちこめるわけである。
普通の人間が「おっ、ここはCall/CCを使えば カッコ良く実装できるね!」などと思いついたりすることはまずありえない
継続を用いれば大域脱出、コルーチン、疑似マルチタスク、バックトラックといった特殊な制御を必要とするプログラムを効率的に記述することができる
「分かれば分かる」
継続は現在の計算を続行するための情報
どんなコードでも継続渡し形式として「見る」ことができる
現在の継続を手続き (継続手続き) にパッケージ化して、それを引数として procを呼び出します。procが戻ったら、その返り値がcall/cc
の 値となります。
言語によっては、呼び出し元の関数が分からず呼び出したらもう戻ってこ ないものもあります。呼び出し元の関数に値が返ってくるのは、どこから自分を呼び出したのかという情報をどこかで管理し、呼び出された側はその 情報を元に値を返すという決まりがあるからです。
保存した継続を動かしてみましょう。
(* 2 (fact 10))という計算は、fact/cpsを使えば次の式と等価です。(fact/cps 10 (lambda (a) (* 2 a)))
継続渡し形式で書けば、現在実装中の関数が、 どういう形で結果を返すのが良いかの設計を遅らせることが出来ます。
継続は保存してから呼び出されるまでの間、完全に休眠しており、 その間にいかなる式の評価でもはさむことが出来ます。
(call/cc (lambda (return) ...)) は説明すると長くなるけど、まあ、 returnってラベルを定義してるもんだと思ってくれればいい。
2010/03/12
"再帰も"ループも使わずに配列を逆順にする:継続呼び出し編
ヒネリたくてもヒネれず、Y combinator で書くなどしました。
今回は、The Seasoned Schemer で出てきた let/cc を用いてやってみました。
(やっぱこれって「再帰じゃない」とは言い張れないような気がしてきました。うまいこと書けてないだけかな。)
取り合えず以下コード
ちなみに先日のものはこちら
追記
いやー読んでもよくわからないっす・・・。力不足ですはい^^;(define (my-reverse ls) (let/cc return (let ((r #f)) (let ((l ls) (acc '())) (let/cc continue (set! r continue)) (and (null? l) (return acc)) (set! acc (cons (car l) acc)) (set! l (cdr l)) (r '()))))) |
追記2
擬似コードも説明もわかりやすかったです。追記で書いたように、だいたい思った通りの動きのようで安心しました。でもやっぱりなんか異物感のようなものがありますねー・・・。@cametan_001さんの仰るように関数型的でないからなのでしょうか。
兎に角もっと書いてみる方が良さそうだなーという印象でした。
2010/03/09
TSS intersectall (letcc, call/cc)
未だに継続周り(呼び出しとか渡しとか)が、いまいちピンとこない。
説明やコードを読めば、その時は動きがわかるし、何をしてるかもだいたいわかる(つもり)。
見よう見まねで書ける。
でも消化不良(?)というか、ものにならないというか。
パラダイムシフトが起きてないってことなのかな。
で、それが起きないのはそれが起きるほどコードを書いてないから、だと思う。
「悟り体験」ってきっとパラダイムシフト。
この例の場合だと、最後のコードでも同様の結果・・・ですよね?
ネストが深い場合に、深部から一気に脱出するとなると継続呼び出しの方でないとできない・・・んだよねきっと。
2009/10/19
継続渡しスタイルCPS
1年くらいSchemeやってて継続(渡し、呼び出し)についても何度か読んだり書いたりしてるはずなのに未だに「どんなんだっけ?」となってしまう私です。
「なんでも継続」とか読んで少しだけ書いてみた。
普通の階乗
;; factorial |
継続渡し階乗(Continuation Passing Style -> CPS)
(define fact/cps |
やっぱりイメージが沸かないので展開してみた
(fact/cps 5 (lambda (x) x))
(fact/cps (- (- (- 5 1) 1) 1) (fact/cps (- (- (- (- 5 1) 1) 1) 1) (fact/cps (- (- (- (- (- 5 1) 1) 1) 1) 1) |
ということですか。わかりました。わかりません。毎度理解できたつもりですが、数日すると忘れるんだろうな。ということはつまりわかってないのな。
実行時のキャプチャーとか言いますよね、わかります。わかりません。セーブポイントみたいなもんだということですね?現在の環境ごとクロージャにぶち込んでほいほい渡していって必要になってから使うし使わなくても良いし、と。
超てきとうとは書いてありますがわかりややすかったです。「なんでも継続」の最初の方もわかりやすかった。
The Little Schemer のrember&coのところをもっかいやってみよう。というか丸ごと読み直そう。んでThe Seasoned Schemerもちゃんと読もう。
なんかピンとこないんですよね・・・。
あ、ついでにJavaScriptとC#も。改めて書く必要もなく同じコードなんですけどね。なんとなく。C#は気分でgoto使ってみた。
JS
function factCps (n, cont) factCps(5, function(x){ return x;}); |
C#
using System; using System.Diagnostics; namespace SampleCallPassingStyle restart: try public static decimal FactCps(decimal n, decimal nest, Func<decimal, decimal> cps) //return n == 0 |
posted with amazlet at 09.03.30 Daniel P. Friedman Matthias Felleisen おすすめ度の平均: 小さなScheme処理系で学ぶ数学基礎理論 |
posted with amazlet at 09.03.30 Daniel P. Friedman Matthias Felleisen おすすめ度の平均: Little Schemer とセットで |
2009/10/17
[メモ]Nemerle, Scheme, Continuation, Factor
[Nemerle]
マクロに興味沸いた。仕事で C# だし IronScheme の方が気になるし .NET 系言語はこれ以上さわる気になれないけど気になる。[Factor]
Factor に乗り換えるプログラマーは、Factor のより高位なプログラミング力を手に入れるために、ローカル変数を放棄しなくてはなりません。PostScript にはまっているので気になります。
[Scheme]
- About Scheme
- WHY Common Lisp?
- WHY Allegro CL?
- Three Dogmas of Scheme (Recursion, Single name space and Continuation)
- Call with current continuation patterns -> call/cc
- 使いたい人のための継続入門
- 継続 : Wikipedia
- 継続
CPS - continuation passing style - 継続渡しスタイル
継続もマクロもまともに書けません。これじゃダメですね。勉強しよー。