Programação assíncrona em idiomas funcionais

31

Sou principalmente um programador de C / C ++, o que significa que a maior parte da minha experiência é com paradigmas processuais e orientados a objetos. No entanto, como muitos programadores de C ++ sabem, o C ++ mudou de ênfase ao longo dos anos para um estilo funcional, culminando finalmente na adição de lambdas e fechamentos no C ++ 0x.

Independentemente disso, embora eu tenha uma experiência considerável em codificação em um estilo funcional usando C ++, tenho muito pouca experiência com linguagens funcionais reais, como Lisp, Haskell, etc.

Recentemente, comecei a estudar essas linguagens, porque a idéia de "sem efeitos colaterais" em linguagens puramente funcionais sempre me intrigou, especialmente no que diz respeito às suas aplicações à concorrência e à computação distribuída.

No entanto, vindo de um background em C ++, estou confuso sobre como essa filosofia de "sem efeitos colaterais" funciona com a programação assíncrona. Por programação assíncrona, entendo qualquer estilo de estrutura / API / codificação que despacha manipuladores de eventos fornecidos pelo usuário para manipular eventos que ocorrem de forma assíncrona (fora do fluxo do programa). Isso inclui bibliotecas assíncronas, como Boost.ASIO, ou até mesmo C simples manipuladores de sinal ou manipuladores de eventos da GUI Java.

A única coisa que todos eles têm em comum é que a natureza da programação assíncrona parece exigir a criação de efeitos colaterais (estado), para que o fluxo principal do programa perceba que um manipulador de eventos assíncrono foi chamado. Normalmente, em uma estrutura como Boost.ASIO, um manipulador de eventos altera o estado de um objeto, para que o efeito do evento seja propagado além do tempo de vida da função do manipulador de eventos. Realmente, o que mais um manipulador de eventos pode fazer? Ele não pode "retornar" um valor ao ponto de chamada, porque não há um ponto de chamada. O manipulador de eventos não faz parte do fluxo principal do programa; portanto, a única maneira de afetar o programa real é alterar algum estado (ou longjmpoutro ponto de execução).

Parece que a programação assíncrona tem a ver com a produção assíncrona de efeitos colaterais. Isso parece completamente em desacordo com os objetivos da programação funcional. Como esses dois paradigmas são reconciliados (na prática) em linguagens funcionais?

Charles Salvia
fonte
3
Uau, eu estava prestes a escrever uma pergunta como essa e não sabia como colocá-la e depois vi isso nas sugestões!
Amogh Talpallikar

Respostas:

11

Toda a sua lógica é sólida, exceto que eu acho que sua compreensão da programação funcional é um pouco extremada. No mundo real, a programação funcional, assim como a programação orientada a objetos ou imperativa, trata da mentalidade e de como você aborda o problema. Você ainda pode escrever programas no espírito da programação funcional enquanto modifica o estado do aplicativo.

Na verdade, você precisa modificar o estado do aplicativo para realmente fazer qualquer coisa. Os caras da Haskell dirão que seus programas são "puros" porque envolvem todas as mudanças de estado em uma mônada. No entanto, seus programas ainda interagem com o mundo exterior. (Caso contrário, qual é o ponto!)

Ênfase na programação funcional "sem efeitos colaterais" quando faz sentido. No entanto, para fazer a programação do mundo real, como você disse, você precisa modificar o estado do mundo. (Por exemplo, respondendo a eventos, gravando em disco e assim por diante.)

Para obter mais informações sobre programação assíncrona em linguagens funcionais, recomendo fortemente que você analise o modelo de programação de Fluxos de trabalho assíncrono do F # . Ele permite que você escreva programas funcionais enquanto oculta todos os detalhes confusos da transição de threads em uma biblioteca. (De maneira muito semelhante às mônadas do estilo Haskell.)

Se o 'corpo' do encadeamento simplesmente computa um valor, a geração de vários encadeamentos e a execução de valores paralelos ainda está dentro do paradigma funcional.

Chris Smith
fonte
5
Também: olhar para Erlang ajuda. A linguagem é muito simples, puro (todos os dados é imutável), e é tudo sobre o processamento assíncrono.
9000
Basicamente, depois de entender os benefícios da abordagem funcional e alterar o estado somente quando for importante, você automaticamente garantirá que, mesmo se você trabalhar com algo como Java, saiba quando modificar o estado e como manter essas coisas sob controle.
Amogh Talpallikar
Discordo - o fato de um programa ser composto de funções 'puras' não significa que ele não interage com o mundo exterior, é dizer que todas as funções em um programa para um conjunto de argumentos sempre retornam o mesmo resultado, e isso é (pureza) é muito importante, porque, do ponto de vista prático - esse programa será menos problemático, mais 'testável', a execução bem-sucedida de funções pode ser comprovada matematicamente.
Gill Bates
8

Esta é uma pergunta fascinante. A opinião mais interessante sobre ela é, na minha opinião, a abordagem adotada no Clojure e explicada neste vídeo:

http://www.infoq.com/presentations/Value-Identity-State-Rich-Hickey

Basicamente, a "solução" proposta é a seguinte:

  • Você escreve a maior parte do seu código como funções "puras" clássicas, com estruturas de dados imutáveis ​​e sem efeitos colaterais
  • Os efeitos colaterais são isolados pelo uso de referências gerenciadas que controlam as alterações sujeitas às regras de memória transacional do software (ou seja, todas as suas atualizações para o estado mutável ocorrem dentro de uma transação isolada adequada)
  • Se você adotar essa visão do mundo, poderá ver "eventos" assíncronos como gatilhos para uma atualização transacional de estado mutável, em que a atualização é uma função pura.

Provavelmente não expressei a ideia tão claramente quanto os outros, mas espero que isso dê uma idéia geral - basicamente, ele está usando um sistema STM simultâneo para fornecer a "ponte" entre a programação funcional pura e a manipulação de eventos assíncrona.

Mikera
fonte
6

Uma observação: uma linguagem funcional é pura, mas seu tempo de execução não é.

Por exemplo, os tempos de execução de Haskell envolvem filas, multiplexação de threads, coleta de lixo, etc ... tudo isso não é puro.

Um bom exemplo é a preguiça. Haskell suporta avaliação lenta (esse é o padrão, na verdade). Você cria um valor lento ao preparar uma operação. Em seguida, você pode criar várias cópias desse valor, e ele ainda é "lento", desde que não seja necessário. Quando o resultado é necessário, ou se o tempo de execução encontrar algum tempo, o valor é realmente calculado e o estado do objeto lento é alterado para refletir que não é mais necessário executar a computação (mais uma vez) para obter o resultado. Agora está disponível em todas as referências; portanto, o estado do objeto mudou, mesmo que seja uma linguagem pura.

Matthieu M.
fonte
2

Estou confuso sobre como essa filosofia "sem efeitos colaterais" funciona com a programação assíncrona. Por programação assíncrona, quero dizer ...

Esse seria o ponto, então.

Um estilo de som, sem efeito colateral é incompatível com estruturas que dependem de estado. Encontre uma nova estrutura.

O padrão WSGI do Python, por exemplo, nos permite criar aplicativos sem efeitos colaterais.

A ideia é que as várias "mudanças de estado" sejam refletidas por um ambiente de valores que podem ser construídos de forma incremental. Cada solicitação é um pipeline de transformações.

S.Lott
fonte
"permite criar aplicativos com efeitos colaterais" Acho que falta uma palavra em algum lugar.
Christopher Mahan
1

Tendo aprendido o encapsulamento com o Borland C ++ depois de aprender C, quando o Borland C ++ não possuía modelos que permitissem genéricos, o paradigma de orientação a objetos me deixou desconfortável. Uma maneira mais natural de calcular parecia filtrar dados através de tubos. O fluxo externo tinha identidade separada e independente do fluxo de entrada imutável interno, em vez de ser considerado um efeito colateral, ou seja, toda fonte de dados (ou filtro) era autônoma de outras. O pressionamento de tecla (um evento de exemplo) restringia as combinações assíncronas de entrada do usuário aos códigos de teclas disponíveis. As funções operam nos argumentos dos parâmetros de entrada, e o estado encapsulado pela classe é apenas um atalho para evitar a passagem explícita de argumentos repetitivos entre pequenos subconjuntos de funções, além de ser cauteloso em um contexto vinculado, impedindo o abuso desses argumentos de qualquer função arbitrária.

Aderir rigidamente a paradigmas particulares causa inconveniência ao lidar com abstrações com vazamentos, por exemplo. tempos de execução comerciais, como JRE, DirectX, .net, são os principais proponentes orientados a objetos. Para restringir o inconveniente, as línguas optam por mônadas acadêmicas sofisticadas, como Haskell, ou o suporte a múltiplos paradigmas flexível, como o F # finalmente conseguiu. A menos que o encapsulamento seja útil em alguns casos de uso de herança múltipla, a abordagem de múltiplos paradigmas pode ser uma alternativa superior a alguns padrões de programação específicos de paradigmas, às vezes complexos.

Chawathe Vipul
fonte