web-dev-qa-db-ja.com

継続を使用した例が理解できない

r6rsスキームレポート を読んでいて、継続の説明に混乱しています(密度が高すぎて、初心者には例が不足していることがわかります)。

このコードは何をしていて、どのように4に評価されますか? call/ccが1つの引数の関数である引数を必要とするのはなぜですか? call/ccの引数はどのように使用されますか?

(+ 1 (call-with-current-continuation
       (lambda (escape)
         (+ 2 (escape 3)))))
          =⇒ 4

この例はセクション1.11-継続からのものです。

4
user39685

call/ccは、1つの引数を持つ関数を取ります。これは、その引数に「現在の継続」が入力されるためです。この場合の継続は、call/ccの「直後」のポイント、つまり戻り値が設定されているポイントに戻ります。

したがって、(escape 3)を呼び出すと、式の残りの部分((+ 2 ...))は破棄され、call/ccの戻り値として3が設定されます。したがって、(+ 1 (call/cc ...))(+ 1 3)として評価されるため、結果は4になります。

4

クリスの答えは素晴らしいですが、私はそれを理解したので、初心者の観点から継続にもう少し説明を追加したいと思います。

call/ccの例

(define call/cc call-with-current-continuation)を実行し、hop(「TheSeasonedSchemer」の方法で)を使用して継続を表します。

パラメータを無視する:通常の評価

(call/cc
  (lambda (hop)
    (+ 2 3)))
=> 5

式全体をホッピングする:通常の評価も

(call/cc
  (lambda (hop)
    (hop (+ 2 3))))
=> 5

オペレーターのホッピング:

(call/cc
  (lambda (hop)
    ((hop +) 2 3))))
=> #<procedure:+>

オペランドのホッピング:

(call/cc
  (lambda (hop)
    (+ 2 (hop 3))))
=> 3

ホッパーをホッピングする:

(call/cc
  (lambda (hop)
    (hop hop)))
=> #<continuation>

基本的に、call/ccを使用すると、式から「ホップ」して、指定された戻り値で計算をすぐに中止する方法が得られます。

call/ccを使用するためのより洗練された方法はたくさんありますが、私にはわかりませんが、OPとは関係ありません。

1
user39685

多くの例を含むこの紹介 継続を理解するのに役立ちました。

とにかく。これらは、継続を(乱用)使用する方法のいくつかの例です。

(define (pair-up x)
    (call/cc (lambda (continuation)
        ;; recursive helper functions
        (define (pair-up-aux x)
        (if (null? x) 
            '()
            (if (null? (cdr x))
                (continuation #f) ; cancel everything and return #f
                (cons (cons (car x) (cadr x)) (pair-up-aux (cddr x)))))) ; build result
        (pair-up-aux x))))

このコードはリストを取り、(pair-up '(a b c d))が((a .b)(c .d))になるようなペアのリストを作成します。それは、consesを繰り返して連鎖させることによってそうします。リストの最後にいて、最後のペアを作成するための要素が1つ不足していることがわかったら、すべてをキャンセルして、代わりに#fを返します。 (pair-up '(a b c))を実行し、#fを返した場合、((a .b)。#f)を取得します。現時点でのcall/ccは、私が自分のリターンを構築し始めたとしても、私の考えを変えて、何か他のものを一緒に返す機会を与えてくれます。 (継続#f)は、call/ccが関数本体の周囲にあるため、このプロシージャの結果として#fを返します。次の例では、継続を伴うループを実装します。

(let ((x 0))
    (let ((cont (call/cc (lambda (x) (x x)))))
        (set! x (+ x 1))
        (display x)
        (newline)
        (if (= x 10)
            (display "Finished!\n")
            (cont cont))))

これで実際に1..10が出力され、継続を呼び出すと、letの本体が数回実行されます。 2つの別々のlet(またはlet *)を使用する必要がありました。そうしないと、xは各実行時に初期化され、xが10になることはありません。

0
Sylwester