継続は、次に何が起こるかを何らかの値で表しますよね?それは、値を取り、何らかの計算を行う単なる関数ではありませんか?
(+ (* 2 3) 5)
の続き(* 2 3)
は(+ _ 5)
(define k (lambda (v) (+ v 5)))
call/cc
関数を使用せずにここで使用するポイントは何k
ですか?
継続は、次に何が起こるかを何らかの値で表しますよね?それは、値を取り、何らかの計算を行う単なる関数ではありませんか?
(+ (* 2 3) 5)
の続き(* 2 3)
は(+ _ 5)
(define k (lambda (v) (+ v 5)))
call/cc
関数を使用せずにここで使用するポイントは何k
ですか?
真実。すべてのプログラムには、停止するまで継続があります。通常、1 つの継続は、基礎となる実装によって行われる計算の 1 つのステップです。
あなたの例:
(+ (* 2 3) 5)
組み合わせ + は、最初に終了する組み合わせ * に依存します。したがって(+ result 5)
、確かに の続きです(* 2 3)
。ただし、このコンテキストでは手順ではありません。の有用性はcall/cc
、後悔して代わりに別のことをしたい、または後でこれに戻りたいという続きがある場合です。最初にやりましょう:
(define g 0)
(call/cc
(lambda (exit)
(/ 10 (if (= g 0) (exit +Inf.0) g))))
明らかに、if の結果が実行されたときに継続する除算がありますが、exit
実行されているため、全体が短絡して +Inf.0 が返されます。
後で除算を行わせずに、手順でそれを行うにはどうすればよいでしょうか? このスタイルでは、できません。
Scheme はコードをContinuation Passing Style(=CPS)に変換し、CPS では call/cc は特別なものではないため、これは魔法ではありません。CPS でコードを書くのは簡単ではありません。
これがCPSの定義ですcall/cc
(define (kcall/cc k consumer)
(consumer k (lambda (ignore v) (k v))))
おめでとう!あなたは継続渡しスタイルを発明しました! あなたが行ったこととの唯一の違いcall/cc
は、call/cc
それが自動的に行われ、コードを再構築する必要がないことです。
「継続」とは、計算の未来全体です。計算のすべてのポイントには継続があり、単純に言えば、現在のプログラム カウンターおよび現在のスタックと考えることができます。Schemecall/cc
関数は、現在の構成を便利にキャプチャし、それを関数にパッケージ化します。その関数を呼び出すと、計算のその時点に戻ります。したがって、継続は関数とは大きく異なります (ただし、継続関数は関数です)。
call/cc
通常適用される2 つの一般的なケースがあります。
非ローカル出口。継続を確立し、何らかの計算を行い、継続を呼び出した計算を突然終了します。
計算を再開/再入力します。この場合、継続を保存してから、必要に応じて再度呼び出します。
ケース #1 の例を次に示します。
(begin
;; do stuff
(call/cc (lambda (k)
;; do more
;; oops, must 'abort'
(k 'ignore)))
;; continue on
)
ケース #2 の例を次に示します。
> (define c #f)
> (let ((x 10))
(display (list (+ 1 (call/cc (lambda (k) (set! c k) x))) 111))
(display " more"))
(11 111) more
> (c 20)
(21 111) more
> (c 90)
(91 111) more
このケース #2 では、継続によって最上位の read-eval-print ループに戻ることに注意してください。これにより、この例で継続を再度呼び出すことができます。