Pergunta

Eu estou tentando compreender o conceito de continuação, e encontrei várias pequenas ensino de exemplos como este de Artigo da wikipédia:

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

Eu entendo que este pequeno função faz, mas eu não posso ver qualquer óbvio aplicação.Apesar de não esperar para usar a continuação de todo o meu código em breve, gostaria de saber alguns casos, onde eles podem ser apropriados.

Então, eu estou procurando mais explicitamente útil exemplos de código do que a continuação pode oferecer-me como um programador.

Saúde!

Foi útil?

Solução

Em Algo Dados e II usamos todas as vezes para "sair" ou "retornar" a partir de uma (longa) função

por exemplo, o BFS algorthm para atravessar árvores foi implementado assim:

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

Como você pode ver, o algoritmo será encerrado quando o nó-descoberto função retorna true:

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

a função também de dar um "valor de retorno":'nó'

qual a função termina, é devido a essa declaração:

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

quando usamos saia, ele vai voltar para o estado anterior à execução, esvaziar a pilha de chamadas e retornar o valor que você deu a ele.

Então, basicamente, chamar-cc é usado (aqui) para saltar para fora de uma função recursiva, em vez de esperar que a recursão ao fim por si (o que pode ser bastante caro ao fazer lotes de trabalho computacional)

outro pequeno exemplo fazendo o mesmo com a chamada-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))))

Outras dicas

@Pat

Beira-mar

Sim, Beira-mar é um grande exemplo.Eu consultei o seu código rapidamente e encontrei esta mensagem ilustrando a passagem de controle entre componentes em um aparentemente statefull forma através da 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 ]

Tão bom!

Eu construí a minha própria unidade de teste de software.Antes de executar o teste, eu armazenar a continuação antes de executar o teste e, em seguida, em caso de falha, eu (opcionalmente) diga o esquema de intérprete para soltar no modo de depuração, e re-invocar a continuação.Dessa forma eu posso percorrer o código problemático muito facilmente.

Se o seu continuações são serializable, você também pode armazenar, em seguida, em falha do aplicativo e, em seguida, re-invocar para obter informações detalhadas sobre valores de variáveis, os rastreamentos de pilha, etc.

Continuações são utilizados por alguns servidores web e frameworks web para armazenar informações de sessão.Uma continuação objeto é criado para cada sessão e, em seguida, utilizados por cada pedido dentro da sessão.

Há um artigo sobre esta abordagem aqui.

Eu vim através de uma implementação do amb operador de este post a partir de http://www.randomhacks.net, usando continuações.

Aqui está o que o amb opeerator faz:

# 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 

E aqui está o post da implementação:

# 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

Eu gosto amb!

Continuações podem ser usadas na "vida real" exemplos sempre que o fluxo do programa é não linear, ou mesmo não pré-determinada.Uma situação familiar é aplicações web.

Continuações são uma boa alternativa para thread-per-request em programação de servidor (incluindo o aplicativo web de interfaces gráficas.

Neste modelo, em vez de lançar um novo (pesado) thread de cada vez que uma solicitação é recebida, o que você acabou de iniciar o trabalho em uma função.Então, quando você estiver pronto para bloco de I/O (por exemplo,a leitura do banco de dados), você passa uma continuação para a rede de resposta do manipulador.Quando a resposta vem de trás, você pode executar a continuação.Com este programa, você pode processar lotes de pedidos com apenas alguns segmentos.

Isso torna o fluxo de controle mais complexos do que usando o bloqueio de threads, mas sob carga pesada, é mais eficiente (pelo menos no hardware de hoje).

A amb operador é um bom exemplo que permite prólogo-como programação declarativa.

Como nós falamos, eu sou a codificação de uma peça de música de composição de software em Scheme (eu sou um músico, com quase nenhum conhecimento sobre a teoria por trás da música e eu só estou analisando meu próprio peças para ver como a matemática por trás funciona.)

Usando a amb operador posso basta preencher os constrangimentos que uma melodia deve satisfazer e deixar Esquema de descobrir o resultado.

Continuações são, provavelmente, colocado em Regime por causa da língua filosofia, Esquema é uma estrutura, permitindo que você percebe sobre qualquer paradigma de programação encontrados em outros idiomas através da definição de bibliotecas em Regime de si.Continuações são para fazer o seu próprio controle abstrato de estruturas como 'retorno', 'break' ou para activar a programação declarativa.O esquema é mais 'generalizar' e implora que tais construções deve ser capaz de ser especificado pelo programador também.

Como sobre o O Google Mapplets API?Há um monte de funções (tudo termina em Async) para que você passar de uma chamada de retorno.A função de API faz uma solicitação assíncrona, obtém-se o resultado, depois passa o resultado para o seu retorno de chamada (como "a próxima coisa a fazer").Soa muito como continuação passando estilo para mim.

Este exemplo mostra um caso muito simples.

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");

Como este é o Javascript não há chamada de cauda de otimização, de modo que a pilha vai crescer com cada chamada para uma continuação, e você vai eventualmente voltar a thread de controle para o navegador.Tudo a mesma coisa, eu acho que é uma boa abstração.

Se você tem invocar uma ação assíncrona, e suspender a execução até obter o resultado, normalmente, quer sondar o resultado ou colocar o resto do seu código em um retorno de chamada para ser executado pela ação assíncrona após a conclusão.Com continuações você não precisa fazer o ineficiente opção de consulta, e você não precisa quebrar todo o seu código para ser executado após o asynch evento em um retorno de chamada - você acabou de passar o estado atual do código de chamada de retorno - e o código está efectivamente a "acordar" assim como o asynch ação completa.

Continuações podem ser usadas para implementar exceções, um depurador.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top