(top)  (memo)  (rss)
普段は Common Lisp ばっかりですが,ネタがないので planet-scheme で見か けたネタより.今 planet-scheme につながらないので細部は微妙にちがうかも しれないです.
(define (solve)
(let* ((x (in-range 0 100))
(y (in-range 0 100))
(z (in-range 0 100)))
(if (= (* x x) (+ (* 2 y) z 500))
(list x y z)
(fail "no solution"))))
(display (solve))
のような感じでした.要するに if の条件を見たす x y z の組み合わせを返す という処理ですね.組み合わせが存在しない場合には no solution を返すので しょう.x, y, z はそれぞれ 0 から 100 までの範囲の変数です.
これはどう見ても継続です.本当にありがとうございます.というわけで挑戦 してみました.
クロージャで値を覚える簡単なジェネレータを考えてみます.
(define gen/closure
(lambda ()
(let ((n -1))
(lambda ()
(set! n (+ n 1))
n))))
これを使うと
> (define f (gen/closure)) > (f) 0 > (f) 1 > (f) 2
のようにジェネレータが実現できます.これは状態 n を書きかえる事でジェネ レータを実現しています.これを継続で表現してみましょう.まず,
(define f
(lambda (n)
(f (+ n 1))))
という再帰関数を考えます.
この関数は次々と (f 0) -> (f 1) -> (f 2) -> と再帰してしまいますが, 再帰する直前の継続を考えてみましょう.(f 0) -> [継続1] -> (f 1) -> [継続2] -> (f 2) -> [継続3] と考えます.ここで,継続を覚えておいて処 理を中断できれば,ジェネレータを実現できそうですね.
;; 0 を返す 1. (f 0) 2. 継続を覚える 3. 値 0 を返す ;; 1 を返す 2. で覚えた継続を呼びだす 3. (f 1) 4. 継続を覚える 5. 値 1 を返す ;; 2 を返す 6. で覚えた継続を呼びだす 7. (f 2) 8. 継続を覚える 9. 値 3 を返す
これをプログラムにしようとすると,「値 0 を返す」といった処理が困ってい まいます.これはどうやって実現すれば良いでしょうか?そう,答えは「値を 返す直前の継続」を使うのです.
;; ただ値を返す
(defun f1
(lambda ()
'undefined)) ;; 返したい値
(define cc #f) ;; 継続
;; 値を返す直前の継続を覚える
(defun f2
(lambda ()
(call/cc
(lambda (k)
(set! cc k)) ;; この K が値を返す
'undefined)))
> (f1) 'undefined > (f2) 'undefined > (cc 1) 1 > (cc 2) 2
となります.ここで継続 cc に保存されている継続が値を返す継続です. ここまでくれば
(define gen/cc
(let ((*値を返す継続* #f))
(letrec ((next (lambda () (step 0)))
(step (lambda (n)
[継続を覚える]
[値を返す]
(step (+ n 1)))))
(lambda ()
(lambda ()
[値を返す継続を覚える]
[継続を呼びだす])))))
上記のような関数が理解できると思います.これを Scheme のコードで実現すると
(define gen/cc
(let ((success #f))
(letrec ((next (lambda () (step 0)))
(step (lambda (n)
(call/cc
(lambda (k)
(set! next k)
(success n)))
(step (+ n 1)))))
(lambda ()
(lambda ()
(call/cc
(lambda (k)
(set! success k)
(next))))))))
となります.同様の方法で,任意の再帰関数の継続を取り出してジェネレータ にできます.
今回は指定された範囲の値を返しつづける関数を考えました. とりあえず正の整数の範囲で動けばいいや,という版.
(define range
(lambda (beg end)
(cond ((< beg end)
(range (+ beg 1) end))
(else
end))))
(use srfi-1)
;; 継続を保持するためのリスト
(define all-ranges ())
;; 継続が呼ばれるたびに順番に値を生成する
(define (in-range beg end)
(let ((id (gensym))
(success '?))
(letrec ((cont-success
(lambda (x) (gen beg end)))
(gen
(lambda (beg end)
(cond ((< beg end)
(call/cc
(lambda (k)
(set! cont-success k)
(success beg)))
(gen (+ beg 1) end))
((= beg end)
(set! all-ranges
(remove (lambda (x) (eq? (car x) id))
all-ranges))
end)))))
(call/cc
(lambda (k)
(if (assoc id all-ranges)
#f
(set! all-ranges (cons (cons id k) all-ranges)))))
(call/cc
(lambda (k)
(set! success k)
(cont-success 'done))))))
;; 継続が残っていれば,それを呼びだす.もう継続がなくなっていたらあきらめる
(define (fail msg)
(cond ((null? all-ranges)
"No solution")
(else
((cdar all-ranges) #f))))
ほんとにあってるのかな?大域変数使うわけないような気がするのですが.
なんだかんだで楽しみにしているので,planet-scheme にははやく復旧しても らいたいです.
Planet Scheme 復旧したので見てみました.答えは……おぉ,見事. 私のはこれに比べたらまるで C のコードだ…… orz
なんと fail を書きかえていくとゆう方針でした.頭イイなぁ.解答のでは, fail 関数に関数の継続を記憶していってます.fail するたびに 0, 1, 2, 3 というように値を増す変数を考えましょう.
(define fail (lambda () (error "no solution")))
(define gen/cc
(lambda ()
(call/cc
(lambda (cont)
(step 0 cont)))))
;; (define step (lambda (n) (step (+ n 1)))) 相当
(define step
(lambda (n cont)
(let ((save fail))
(set! fail
(lambda ()
(set! fail save)
(step (+ n 1) cont)))
(cont n))))
> (define x (gen/cc)) > x 0 > (fail) > x 1 > (fail) > x 2
となります.キモは fail を呼ぶ時に,「既存の定義に fail を戻した後,次 のスッテプに進む」fail を作るところですね.こら面白い.何もなくなったら 最初の fail の定義に戻るため,全ての状態を尽して fail するとエラーがで る,と.
(define in-range
(lambda (a b)
(call/cc
(lambda (cont)
(enumerate a b cont)))))
(define enumerate
(lambda (a b cont)
(if (> a b)
(fail)
(let ((save fail))
(set! fail
(lambda ()
(set! fail save)
(enumerate (+ a 1) b cont)))
(cont a)))))
(let ((x (in-range 0 100))
(y (in-range 0 100))
(z (in-range 0 100)))
(if (= (* x x)
(+ (* 2 y) z 500))
(list x y z)
(fail))) ; => (23 0 29)
おもしろいな〜.↓の説明がわかりやすいっす.
http://www.iro.umontreal.ca/%7Eboucherd/mslug/meetings/20041020/minutes-en.html
posted: 2006/02/23 01:10 | permanent link to this entry | Tags: SCHEME