As continuações de primeira classe são úteis nas linguagens de programação modernas orientadas a objetos?

10

As continuações são extremamente úteis em linguagens de programação funcionais (por exemplo, a Contmônada em Haskell), pois permitem uma notação simples e regular para código no estilo imperativo. Eles também são úteis em algumas linguagens imperativas mais antigas porque podem ser usadas para implementar recursos de linguagem ausentes (por exemplo, exceções, corotinas, linhas verdes). Mas para uma linguagem moderna orientada a objetos com suporte embutido para esses recursos, que argumentos haveria para adicionar suporte a continuações de primeira classe (seja o estilo delimitado mais moderno resete shiftou semelhante a um esquema call-with-current-continuation)?

Existem argumentos contra a adição de suporte além do desempenho e da complexidade da implementação?

Jules
fonte

Respostas:

15

Vamos deixar Eric Lippert responder a este:

A pergunta óbvia neste momento é: se o CPS é tão impressionante, por que não o usamos o tempo todo? Por que a maioria dos desenvolvedores profissionais nunca ouviu falar disso, ou, aqueles que o consideram, como algo que somente aqueles programadores malucos do Scheme fazem?

Primeiro de tudo, é simplesmente difícil para a maioria das pessoas que está acostumada a pensar em sub-rotinas, loops, try-catch-finalmente e assim por diante, pensando sobre os delegados sendo usados ​​para o fluxo de controle dessa maneira. Estou revisando minhas anotações sobre o CPS do CS442 agora e vejo que em 1995 escrevi um profQUOTE: “Com as continuações, você precisa ficar de cabeça para baixo e se recompor”. O professor Duggan (*) estava absolutamente correto ao dizer isso. Lembre-se de alguns dias atrás, que nosso pequeno exemplo de transformação CPS da expressão C # M (B ()? C (): D ()) envolveu quatro lambdas. Nem todo mundo é bom em ler códigos que usam funções de ordem superior. Impõe um grande fardo cognitivo.

Além disso: uma das coisas boas de ter instruções de fluxo de controle específicas inseridas em um idioma é que elas permitem que seu código expresse claramente o significado do fluxo de controle enquanto oculta os mecanismos - pilhas de chamadas e endereços de retorno e listas de manipuladores de exceção e regiões protegidas e assim por diante. As continuações tornam os mecanismos do fluxo de controle explícitos na estrutura do código. Toda essa ênfase no mecanismo pode sobrecarregar o significado do código.

No próximo artigo , ele explica como a assincronia e as continuações são exatamente equivalentes uma à outra e analisa uma demonstração de como executar uma operação de rede síncrona simples (mas bloqueadora) e reescrevê-la no estilo assíncrono, certificando-se de cobrir todas as informações ocultas. pegadinhas que precisam ser cobertas para acertar. Isso se transforma em uma enorme bagunça de código. Seu resumo no final:

Santo Deus, que bagunça enorme que fizemos. Expandimos duas linhas de código perfeitamente claro em duas dezenas de linhas do espaguete mais maravilhoso que você já viu. E é claro que ainda nem compila porque os rótulos não estão no escopo e temos um erro de atribuição definido. Ainda precisamos reescrever o código para corrigir esses problemas.

Lembre-se do que eu estava dizendo sobre os prós e contras do CPS?

  • PRO: Fluxos de controle arbitrariamente complexos e interessantes podem ser construídos com peças simples - verifique.
  • CON: A reificação do fluxo de controle através de continuações é difícil de ler e de raciocinar sobre - verifique.
  • CON: O código que representa os mecanismos do fluxo de controle excede completamente o significado do código - verificação.
  • CON: A transformação do fluxo de controle de código comum em CPS é o tipo de coisa em que os compiladores são bons, e quase ninguém mais é - verifique.

Este não é um exercício intelectual. Pessoas reais acabam escrevendo código moralmente equivalente ao acima, o tempo todo, quando lidam com assincronia. E, ironicamente, mesmo que os processadores fiquem mais rápidos e mais baratos, passamos cada vez mais tempo esperando por coisas que não são vinculadas ao processador. Em muitos programas, grande parte do tempo gasto é com o processador vinculado a zero, aguardando os pacotes de rede fazerem a viagem da Inglaterra ao Japão e vice-versa, ou os discos girarem, ou o que seja.

E eu nem falei sobre o que acontece se você quiser compor mais operações assíncronas. Suponha que você queira fazer do ArchiveDocuments uma operação assíncrona que leve um delegado. Agora, todo o código que chama deve ser escrito também no CPS. A mancha apenas se espalha.

É importante acertar a lógica assíncrona no futuro, e isso só será mais importante no futuro, e as ferramentas que fornecemos para você “ficar de cabeça para baixo e virar-se do avesso”, como o professor Duggan disse sabiamente.

O estilo de passagem contínua é poderoso, sim, mas deve haver uma maneira melhor de fazer bom uso desse poder do que o código acima.

Vale a pena ler os dois artigos e as séries a seguir sobre um novo recurso da linguagem C # que move toda essa bagunça para o compilador e permite que você escreva seu código como fluxo de controle normal com uma palavra-chave especial para marcar certas partes como assíncronas. não é um desenvolvedor de C #. Não sou, mas ainda foi a experiência esclarecedora quando a encontrei pela primeira vez.

Mason Wheeler
fonte
5
Vale a pena notar que, se seu idioma suportar extensões de sintaxe como macros, as continuações se tornarão muito mais úteis, pois você poderá ocultar os mecanismos para implementar vários tipos de fluxo de controle interessante dentro das extensões de sintaxe. O que é basicamente como "aguardar" funciona de qualquer maneira, apenas definido na linguagem, pois o C # não fornece as ferramentas para definir você mesmo esse tipo de extensão de sintaxe.
Jack
Bom artigo, apesar de observar que o CPS e as continuações de primeira classe não são exatamente a mesma coisa (o CPS é o que você faz em um idioma sem suporte para continuações quando achar necessário). Parece que o C # está seguindo exatamente a mesma rota em que estou pensando (embora não possa decidir se quero automatizar a transformação de pneus para o CPS ou implementar continuações delimitadas de cópia em pilha totalmente desenvolvidas).
Jules
@ Jack - é exatamente o que estou considerando agora: a linguagem que estou projetando possui um recurso de macro que pode suportar a transformação do CPS automaticamente. Mas continuações nativas completas podem oferecer melhor desempenho ... a questão é: com que frequência elas seriam usadas em uma situação crítica de desempenho?
Jules