Olhando para exemplos de "real" usa de continuações
-
09-06-2019 - |
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!
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
Beira-mar:
@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.
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.