継続渡しスタイルあり (なしcall/cc)
最初ではなく明示的な継続渡しスタイルを使用するバージョンを実装すると、この例を理解しやすくなる場合がありますcall/cc。この場合、継続渡しバージョンの から始めましょうmap:
(define (kmap fn list k)
(if (null? list)
(k list)
(fn (car list)
(lambda (head)
(kmap fn
(cdr list)
(lambda (tail)
(k (cons head tail))))))))
(define (identity x) x)
(kmap (lambda (x k) (k (+ 1 x))) '(1 2 3 4) identity)
;=> (2 3 4 5)
継続渡しスタイルに慣れていない場合、これは頭を悩ませるかもしれませんが、それほど難しくはありません。それぞれが「結果」で呼び出す必要がある追加のパラメーターを最後に取ることkmapを覚えておいてください。fnしたがって、fnwithを呼び出すときは、マッピングの残りの処理を担当(car list)するプロシージャも渡します。マッピングの残りの部分は、再び(lambda (head) ...)によって定義されます。kmapへの各呼び出しkmapは、リストをマッピングした結果のリストを受け取ることを期待する最終的な継続を取りfnます。
これで、継続渡しスタイルでマッピングを実装する方法がわかったので、それを使用してイテレータ生成手順を記述できます。このプロシージャiteratorはリストを受け取り、 の各要素を取得するために呼び出すことができるプロシージャを返しますlist。
(define (iterator list)
(define (next k)
(kmap (lambda (item more-next)
(set! next more-next)
(k item))
list
(lambda (results)
(k 'done))))
(lambda ()
(next identity)))
> (define get (iterator '(1 2)))
> (get)
1
> (get)
2
> (get)
done
> (get)
done
> (define get (iterator '(a b c)))
> (get)
a
> (get)
b
> (get)
c
> (get)
done
> (get)
done
ここでの秘訣は、ローカル プロシージャを定義することnextです。の各要素がいつ処理されるかを の残りの部分を処理するプロシージャとして再定義kmapするプロシージャで呼び出します。を再定義した後、要素で呼び出します。に渡された最後の継続は、実際には渡された結果を無視し、シンボルで呼び出すだけです。返されるのはの値ではなく、継続で呼び出す手続きです。ここでの間接化は、常にwithの最新の値を呼び出すことを意味します。通過 nextlistlistnextkkmapkdoneiteratornextnextidentitynextidentityidentity継続とは、リスト要素を取得することを意味するためです。
とcall/cc
を使用せず にこれを行う方法がわかったのでcall/cc、 を使用call/ccしてそれを行う方法を確認する準備が整いました。質問の定義を思い出してください。
(define (itr lst)
(define (state k)
(for-each (lambda (item)
(call/cc (lambda (h)
(set! state h)
(k item))))
lst)
(k 'done))
(define (generator)
(call/cc (lambda (k) (state k))))
generator)
発電機の返却
最初に注意してください
(define (generator)
(call/cc (lambda (k) (state k))))
generator
に簡略化できます
(lambda () (call/cc (lambda (k) (state k))))
これが、実装で行ったこととほぼ同じです。これを REPL から呼び出す場合、必要kなのは値を取得して返す (そして出力する) ことだけです。私たちのバージョンでは、単純に変更せずに返すことで近似しています。つまり、 を使用し、 の代わりにidentity名前を使用しました。そうnextstate
(lambda () (call/cc (lambda (k) (state k))))
のようです
(lambda () (next identity))
(stateまたはnext) 手順
残りは
(define (state k)
(for-each (lambda (item)
(call/cc (lambda (h)
(set! state h)
(k item))))
lst)
(k 'done))
私たちが行ったことと非常によく似ています。2 つの引数 (アイテムと継続) を取るkmapand aを使用する代わりに、1 つの引数 (アイテム) のプロシージャを受け取り、そのプロシージャ内で継続を取得するために使用します。そうfnfor-eachcall/cc
(for-each
(lambda (item)
(call/cc (lambda (h)
...
のようです
(kmap (lambda (item h)
...
for-eachは最後の継続引数を必要としないため、 result-ignoring を渡すことはできません(lambda () (k 'done))。代わりに、呼び出しの(k 'done) 後にfor-each呼び出すだけです。あれは、
(for-each fn list)
(k 'done)
のようです
(kmap fn
list
(lambda (result)
(k 'done)))
プログラム状態の保存
これらの実装の両方で、ある意味で「プログラムの状態を保存」しています。保存している重要な状態は、リストを反復処理し続ける状態です。