21

私は継続の概念を理解しようとしていますが、ウィキペディアの記事から、このようないくつかの小さな教育の例を見つけました:

(define the-continuation #f)

(define (test)
  (let ((i 0))
    ; call/cc calls its first function argument, passing 
    ; a continuation variable representing this point in
    ; the program as the argument to that function. 
    ;
    ; In this case, the function argument assigns that
    ; continuation to the variable the-continuation. 
    ;
    (call/cc (lambda (k) (set! the-continuation k)))
    ;
    ; The next time the-continuation is called, we start here.
    (set! i (+ i 1))
    i))

この小さな関数が何をするかは理解できますが、明確な用途は見当たりません。近いうちにコード全体で継続を使用する予定はありませんが、継続が適切な場合がいくつかあることを知りたいと思います。

だから私は、継続がプログラマーとして私に提供できるものについて、より明示的に有用なコードサンプルを探しています。

乾杯!

4

12 に答える 12

16

Algo&Data IIでは、(長い)関数から「終了」または「戻る」ためにこれらを常に使用していました

たとえば、ツリーをトラバースするためのBFSアルゴリズムは、次のように実装されました。

(define (BFS graph root-discovered node-discovered edge-discovered edge-bumped . nodes)
  (define visited (make-vector (graph.order graph) #f))
  (define q (queue.new))
  (define exit ())
  (define (BFS-tree node)
    (if (node-discovered node)
      (exit node))
    (graph.map-edges
     graph
     node
     (lambda (node2)
       (cond ((not (vector-ref visited node2))
              (when (edge-discovered node node2)
                (vector-set! visited node2 #t)
                (queue.enqueue! q node2)))
             (else
              (edge-bumped node node2)))))
    (if (not (queue.empty? q))
      (BFS-tree (queue.serve! q))))

  (call-with-current-continuation
   (lambda (my-future)
     (set! exit my-future)
     (cond ((null? nodes)
            (graph.map-nodes
             graph
             (lambda (node)
               (when (not (vector-ref visited node))
                 (vector-set! visited node #t)
                 (root-discovered node)
                 (BFS-tree node)))))
           (else
            (let loop-nodes
              ((node-list (car nodes)))
              (vector-set! visited (car node-list) #t)
              (root-discovered (car node-list))
              (BFS-tree (car node-list))
              (if (not (null? (cdr node-list)))
                (loop-nodes (cdr node-list)))))))))

ご覧のとおり、node-discovered関数がtrueを返すと、アルゴリズムは終了します。

    (if (node-discovered node)
      (exit node))

関数は「戻り値」も提供します:'ノード'

関数が終了する理由は、次のステートメントによるものです。

(call-with-current-continuation
       (lambda (my-future)
         (set! exit my-future)

exitを使用すると、実行前の状態に戻り、呼び出しスタックが空になり、指定した値が返されます。

したがって、基本的に、call-ccは、再帰全体が自動的に終了するのを待つのではなく、再帰関数からジャンプするために(ここで)使用されます(多くの計算作業を行う場合は非常にコストがかかる可能性があります)

call-ccで同じことを行う別の小さな例:

(define (connected? g node1 node2)
  (define visited (make-vector (graph.order g) #f))
  (define return ())
  (define (connected-rec x y)
    (if (eq? x y)
      (return #t))
    (vector-set! visited x #t)
    (graph.map-edges g
                     x
                     (lambda (t)
                       (if (not (vector-ref visited t))
                         (connected-rec t y)))))
  (call-with-current-continuation
   (lambda (future)
     (set! return future)
     (connected-rec node1 node2)
     (return #f))))
于 2008-08-29T11:36:06.473 に答える
9

シーサイド:

于 2008-08-29T09:44:13.073 に答える
7

@パット

シーサイド

はい、シーサイドはその好例です。そのコードをざっと見てみると、このメッセージは、コンポーネント間で Web を介して一見ステートフルな方法で制御を渡していることを示しているのを見つけました。

WAComponent >> call: aComponent
    "Pass control from the receiver to aComponent. The receiver will be
    temporarily replaced with aComponent. Code can return from here later
    on by sending #answer: to aComponent."

    ^ AnswerContinuation currentDo: [ :cc |
        self show: aComponent onAnswer: cc.
        WARenderNotification raiseSignal ]

とてもいい!

于 2008-08-29T10:18:18.517 に答える
7

独自の単体テスト ソフトウェアを作成しました。テストを実行する前に、テストを実行する前に継続を保存し、失敗すると、(オプションで) スキーム インタープリターにデバッグ モードに移行し、継続を再度呼び出すように指示します。このようにして、問題のあるコードを非常に簡単にステップ実行できます。

継続がシリアライズ可能な場合は、アプリケーションの障害時に保存し、再呼び出しして、変数値、スタック トレースなどに関する詳細情報を取得することもできます。

于 2008-09-16T06:43:19.817 に答える
5

継続は、一部の Web サーバーおよび Web フレームワークでセッション情報を保存するために使用されます。セッションごとに継続オブジェクトが作成され、セッション内の各リクエストで使用されます。

このアプローチに関する記事がここにあります。

于 2008-08-29T08:21:55.613 に答える
5

http://www.randomhacks.netからのこの投稿で、継続を使用してambオペレーターの実装に出くわしました。

ambオペレーターが行うことは次のとおりです。

# amb will (appear to) choose values
# for x and y that prevent future
# trouble.
x = amb 1, 2, 3
y = amb 4, 5, 6

# Ooops! If x*y isn't 8, amb would
# get angry.  You wouldn't like
# amb when it's angry.
amb if x*y != 8

# Sure enough, x is 2 and y is 4.
puts x, y 

そして、これが投稿の実装です:

# A list of places we can "rewind" to
# if we encounter amb with no
# arguments.
$backtrack_points = []

# Rewind to our most recent backtrack
# point.
def backtrack
  if $backtrack_points.empty?
    raise "Can't backtrack"
  else
    $backtrack_points.pop.call
  end
end

# Recursive implementation of the
# amb operator.
def amb *choices
  # Fail if we have no arguments.
  backtrack if choices.empty?
  callcc {|cc|
    # cc contains the "current
    # continuation".  When called,
    # it will make the program
    # rewind to the end of this block.
    $backtrack_points.push cc

    # Return our first argument.
    return choices[0]
  }

  # We only get here if we backtrack
  # using the stored value of cc,
  # above.  We call amb recursively
  # with the arguments we didn't use.
  amb *choices[1...choices.length]
end

# Backtracking beyond a call to cut
# is strictly forbidden.
def cut
  $backtrack_points = []
end

いいねamb

于 2008-09-10T09:13:35.327 に答える
3

プログラムの流れが直線的でない場合、または事前に決定されていない場合はいつでも、「実際の」例で継続を使用できます。おなじみの状況は、Web アプリケーションです

于 2008-08-29T08:20:18.767 に答える
3

継続は、サーバー プログラミング (Web アプリケーション フロントエンドを含む) におけるリクエストごとのスレッドの優れた代替手段です。

このモデルでは、リクエストが来るたびに新しい (重い) スレッドを起動する代わりに、関数でいくつかの作業を開始するだけです。次に、I/O をブロックする (つまり、データベースから読み取る) 準備ができたら、継続をネットワーク応答ハンドラーに渡します。応答が返ってきたら、継続を実行します。このスキームを使用すると、少数のスレッドで多くのリクエストを処理できます。

これにより、ブロッキング スレッドを使用するよりも制御フローが複雑になりますが、負荷が高い場合はより効率的です (少なくとも現在のハードウェアでは)。

于 2008-09-16T06:50:53.137 に答える
2

amb演算子は、プロローグのような宣言型プログラミングを可能にする良い例です。

私たちが話している間、私はSchemeで音楽作曲ソフトウェアをコーディングしています(私は音楽の背後にある理論の知識がほとんどないミュージシャンであり、その背後にある数学がどのように機能するかを確認するために自分の曲を分析しています)。

amb演算子を使用すると、メロディーが満たさなければならない制約を入力して、Schemeに結果を計算させることができます。

言語哲学のために継続がSchemeに入れられる可能性があります。Schemeは、Scheme自体でライブラリを定義することにより、他の言語で見られるプログラミングパラダイムを実現できるフレームワークです。継続は、「return」、「break」などの独自の抽象制御構造を作成するため、または宣言型プログラミングを有効にするためのものです。スキームはより「一般化」されており、そのような構成はプログラマーによっても指定できるようにする必要があります。

于 2010-05-18T06:47:24.493 に答える
1

非同期アクションを呼び出し、結果が得られるまで実行を中断する必要がある場合は、通常、結果をポーリングするか、完了時に非同期アクションによって実行されるコールバックに残りのコードを配置します。継続を使用すると、ポーリングの非効率的なオプションを実行する必要はありません。コールバックで非同期イベントの後に実行するすべてのコードをラップする必要はありません。コードの現在の状態をコールバックとして渡すだけです。 -そして、非同期アクションが完了するとすぐに、コードは効果的に「起動」されます。

于 2010-05-05T00:15:24.150 に答える
1

Google マップレット APIはどうですか? Asyncコールバックを渡す関数 (すべて で終わる) がたくさんあります。API 関数は非同期リクエストを実行し、その結果を取得してから、その結果をコールバックに渡します (「次に行うこと」として)。私には継続渡しスタイルによく似ています。

このは、非常に単純なケースを示しています。

map.getZoomAsync(function(zoom) {
    alert("Current zoom level is " + zoom); // this is the continuation
});  
alert("This might happen before or after you see the zoom level message");

これは Javascript であるため、テール コールの最適化は行われないため、継続へのコールごとにスタックが大きくなり、最終的に制御のスレッドをブラウザに返すことになります。それでも、それは素晴らしい抽象化だと思います。

于 2009-01-31T20:28:08.027 に答える
0

継続は、例外、デバッガーを実装するために使用できます。

于 2009-03-05T15:04:32.507 に答える