21 Stimmen

Suche nach Beispielen für die "echte" Verwendung von Fortsetzungen

Ich versuche, das Konzept der Fortsetzungen zu begreifen, und habe mehrere kleine Lehrbeispiele wie dieses aus dem Wikipedia-Artikel :

(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))

Ich verstehe, was diese kleine Funktion bewirkt, aber ich kann keine offensichtliche Anwendung dafür erkennen. Ich erwarte zwar nicht, dass ich in nächster Zeit überall in meinem Code Fortsetzungen verwenden werde, aber ich wünschte, ich wüsste ein paar Fälle, in denen sie sinnvoll sein können.

Ich bin also auf der Suche nach explizit nützlichen Codebeispielen, die zeigen, was Fortsetzungen mir als Programmierer bieten können.

Zum Wohl!

16voto

sven Punkte 17688

In Algo & Data II haben wir diese immer benutzt, um eine (lange) Funktion zu beenden oder zurückzugeben

Der BFS-Algorithmus zur Durchquerung von Bäumen wurde beispielsweise folgendermaßen implementiert:

(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)))))))))

Wie Sie sehen, wird der Algorithmus beendet, wenn die Funktion node-discovered den Wert true liefert:

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

die Funktion liefert auch einen "Rückgabewert": 'node'

Warum die Funktion beendet wird, liegt an dieser Anweisung:

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

Wenn wir exit verwenden, kehrt es in den Zustand vor der Ausführung zurück, leert den Aufrufstapel und gibt den Wert zurück, den Sie ihm gegeben haben.

Im Grunde genommen wird call-cc (hier) verwendet, um aus einer rekursiven Funktion herauszuspringen, anstatt darauf zu warten, dass die gesamte Rekursion von selbst endet (was bei umfangreichen Berechnungen ziemlich teuer sein kann)

ein weiteres kleineres Beispiel, das dasselbe mit call-cc tut:

(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))))

9voto

Pat Punkte 35602

1 Stimmen

Ja, Seaside ist ein tolles Beispiel, ich habe es in einem 6-Monats-Projekt verwendet! Ich bin allerdings auf der Suche nach Codebeispielen.

7voto

@Pat

Seaside

Ja, Seaside ist ein großartiges Beispiel. Ich habe den Code schnell durchgeblättert und diese Nachricht gefunden, die die Übergabe von Steuerelementen zwischen Komponenten auf eine scheinbar zustandslose Weise im Web veranschaulicht.

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 ]

Sehr schön!

7voto

Jonathan Arkell Punkte 10225

Ich habe meine eigene Unit-Testing-Software entwickelt. Bevor ich den Test ausführe, speichere ich die Fortsetzung, und wenn der Test fehlschlägt, sage ich (optional) dem Schema-Interpreter, dass er in den Debug-Modus wechseln und die Fortsetzung erneut aufrufen soll. Auf diese Weise kann ich ganz einfach durch den problematischen Code gehen.

Wenn Ihre Continuations serialisierbar sind, können Sie sie auch bei Anwendungsfehlern speichern und sie dann erneut aufrufen, um detaillierte Informationen über Variablenwerte, Stack Traces usw. zu erhalten.

5voto

Dave Webb Punkte 184809

Fortsetzungen werden von einigen Webservern und Web-Frameworks verwendet, um Sitzungsinformationen zu speichern. Für jede Sitzung wird ein Fortsetzungsobjekt erstellt und dann von jeder Anfrage innerhalb der Sitzung verwendet.

Ein Artikel über diesen Ansatz findet sich hier.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X