継続渡しスタイルあり (なし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
したがって、fn
withを呼び出すときは、マッピングの残りの処理を担当(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の最新の値を呼び出すことを意味します。通過 next
list
list
next
k
kmap
k
done
iterator
next
next
identity
next
identity
identity
継続とは、リスト要素を取得することを意味するためです。
と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
名前を使用しました。そうnext
state
(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 つの引数 (アイテムと継続) を取るkmap
and aを使用する代わりに、1 つの引数 (アイテム) のプロシージャを受け取り、そのプロシージャ内で継続を取得するために使用します。そうfn
for-each
call/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)))
プログラム状態の保存
これらの実装の両方で、ある意味で「プログラムの状態を保存」しています。保存している重要な状態は、リストを反復処理し続ける状態です。