Continuations são as construções de fluxo de controle menos compreendidas de todas. Esta falta de compreensão (ou consciência) é infeliz, dado que continuations permitem ao programador implementar recursos de linguagem e algoritmos poderosos.
– Matt Might, em Continuations By Example
A maneira usual de controlar o fluxo de execução de um programa de computador é via chamadas e retornos de procedimentos; uma pilha estrutura de dados é como linguagens de programação de alto nível mantém rastreamento do ponto ao qual cada sub-rotina ativa deve retornar o controle quando termina de executar.
Infelizmente, você precisará de mais do que isso se pretende escrever programas
úteis para resolver problemas do mundo real. É por isso que a maioria das linguagens de programação de
alto nível também fornecem outras primitivas de fluxo de controle, como a declaração goto,
loops e tratamento de exceções.
Não estou dizendo que implementar uma linguagem de programação é uma tarefa fácil, mas deixando isso de lado por um momento, é como se as linguagens de programação em geral lutassem o máximo que podem para tornar a pilha de chamadas algo tão oculto e intangível quanto possível - algo que ninguém além dela mesma tem permissão para controlar.
O que aconteceria se algumas linguagens de programação, em vez de manter a pilha de chamadas dentro de um cofre de aço sólido de 2", na verdade dessem aos programadores a habilidade de “capturá-las” como funções que podem ser invocadas, armazenadas e passadas como valores?
Neste post, espero mostrar o que continuations são e como podem ser usadas em situações práticas. Então pegue o Racket e vamos lá!
Atualização (20 de jun, 2014): Mudei algumas coisas neste post em resposta a alguns ótimos comentários nesta discussão no Reddit e neste post de blog por jecxjo.
Primeiro Exemplo
Nota: O problema a seguir é solucionável sem continuations, mas gostaria de começar com algo simples o suficiente.
Suponha que você está escrevendo código que faz interface com alguma API sobre HTTP. Também
suponha que esta API requer que um header SessionId seja enviado junto com a requisição
para evitar ataques
CSRF.
|
|
Quando a primeira requisição é enviada - sem o header SessionId - o servidor
responde com um erro, i.e. HTTP 409, caso no qual o procedimento atualiza
session com o session id fornecido pelo servidor e retenta a requisição.
O código faz sentido, mas está quebrado.
A chamada recursiva não é feita em posição de cauda. Então, quando acontece, outro stack frame é empurrado para a pilha de chamadas e mesmo que a requisição retentada tenha sucesso, o que é retornado ao chamador é a resposta àquela primeira requisição não autorizada.
Se apenas tivéssemos a chance de retornar aquela segunda resposta direto ao chamador em vez de ter a pilha se desenrolando…
|
|
Conheça call/cc
Uma continuation pode ser vista como o contexto de avaliação ao redor de uma expressão ou, em outras palavras, um snapshot do estado de controle atual do programa.
Aqui está um exemplo:
|
|
Acontece que, se renomearmos k para return, isso é exatamente o que
precisamos para corrigir aquele exemplo de cliente de API quebrado:
|
|
Agora a função captura a continuation atual no momento em que o procedimento
perform-request! é chamado pela primeira vez. Então, se o servidor nega uma requisição, re-enviamos a requisição com o SessionId fornecido e usamos aquela continuation capturada
para transferir o controle de volta ao chamador.
Legal, não acha? É como se estivéssemos congelando o tempo em let/cc, fazendo algumas
coisas, e então resumindo de lá.
Este é um caso de uso comum de continuations. Confira este projeto se você quiser ler o código que inspirou este exemplo.
Generators
Generators podem ser vistos como rotinas especiais que se comportam como iteradores. Se você está familiarizado com Python, provavelmente viu código como este:
|
|
Você vê alguma semelhança entre este exemplo e o anterior? Embora
Python não forneça uma facilidade call/cc-like na linguagem, pode-se argumentar
que seus generators são como uma continuation de pobre.
Vamos fingir por um momento que Racket não tinha uma biblioteca de generator que faz exatamente isso. Como isso poderia ser implementado em Racket usando continuations?
O que precisamos é uma função que retorna outra função que, quando chamada, produz um item de cada vez, até que a lista esteja esgotada.
|
|
Este código segue o mesmo padrão dos anteriores, mas não parece
funcionar da maneira que você esperaria. A razão deve estar clara: iterate
retorna um lambda que usa a continuation capturada para produzir o primeiro
item da lista.
Para fazer este código funcionar, precisamos capturar a continuation atual de dentro
de for-each e armazená-la para que possa ser usada para resumir a computação
quando next for chamada novamente.
|
|
Se você está tendo problemas para entender como este código funciona, o seguinte diagrama pode ajudar.

Diagrama de fluxo de estado.
Outros Exemplos
Passando para coisas de mais alto nível, em este exemplo Matthew Flatt explica como construir um servidor web baseado em continuations em Racket. Ainda no reino de continuation-based-web-sei-lá-o-que, se você está em Smalltalk, não esqueça de conferir Seaside, um framework de aplicação web que usa continuations para modelar múltiplos fluxos independentes entre diferentes componentes.
Se você não programa em Scheme ou Smalltalk para ganhar a vida, não se preocupe. As chances são de que sua linguagem suporte algum sabor de continuations, seja nativamente ou via alguma biblioteca de terceiros.
Conclusão
Parece que continuations podem ser usadas para implementar uma ampla variedade de construções de controle avançadas incluindo non-local exits, tratamento de exceções, backtracking e coroutines.
Neste post, espero ter esclarecido alguns dos aspectos chave sobre continuations. Se você tem alguma sugestão sobre como melhorar este texto, por favor me avise.