Lidando com problemas de estado em programação funcional

18

Aprendi a programar principalmente do ponto de vista de OOP (como a maioria de nós, tenho certeza), mas passei muito tempo tentando aprender a resolver problemas da maneira funcional. Tenho uma boa noção de como resolver problemas de cálculo com o FP, mas quando se trata de problemas mais complicados, sempre me pego revertendo à necessidade de objetos mutáveis. Por exemplo, se eu estiver escrevendo um simulador de partículas, desejarei "objetos" de partículas com uma posição mutável para atualizar. Como os problemas inerentemente "com estado" geralmente são resolvidos usando técnicas de programação funcional?

Andrew Martin
fonte
4
O primeiro passo provavelmente é perceber que os problemas não são inerentemente com estado.
Telastyn
4
Alguns problemas são inerentemente stateful, como gravar em um banco de dados ou desenhar uma GUI. Tomando meu exemplo de simulador de partículas, qual seria uma maneira alternativa de pensar sobre isso? Retornar novas partículas toda vez que sua posição é atualizada para evitar o estado parece ineficiente para mim, e não um bom modelo do mundo real.
Andrew Martin
4
Exceto, talvez, pelo exemplo do banco de dados, esses problemas não são inerentemente com estado. Por exemplo, para a programação da GUI, você realmente está usando o estado mutável como um modelo de tempo implícito e ruim ; a programação reativa funcional permite modelar o tempo explicitamente sem depender do estado, fornecendo fluxos de eventos que você pode combinar.
Tikhon Jelvis
1
Existe uma solução mais simples: quando você encontrar um problema que não é facilmente modelado com técnicas de FP, não use programação funcional para resolvê-lo. Ferramenta certa para o trabalho e tudo o que ...
Mason Wheeler
1
@AndrewMartin Não é um bom modelo do mundo real? A matemática usada na física para modelar o mundo real é puramente funcional. Com um coletor de lixo boa alocação de um objeto é tão barato quanto batendo um ponteiro, e tempo de coleta é proporcional ao número de vivos objetos. Se alguma coisa, eu aposto que a principal fonte de ineficiências na programação funcional é usar estruturas de dados que não são eficientes em cache. Listas vinculadas e árvores binárias não são exatamente filhos posteriores da eficiência do cache.
Doval

Respostas:

20

Os programas funcionais lidam muito bem com o estado, mas exigem uma maneira diferente de vê-lo. Para o seu exemplo de posição, uma coisa a considerar é ter sua posição em função do tempo, em vez de um valor fixo . Isso funciona bem para partículas que seguem um caminho matemático fixo, mas você precisa de uma estratégia diferente para lidar com uma mudança no caminho, como após uma colisão.

A estratégia básica aqui é criar funções que captam um estado e retornam o novo estado . Portanto, um simulador de partículas seria uma função que recebe um número Setde partículas como entrada e retorna um novo número Setde partículas após um intervalo de tempo. Depois, basta chamar repetidamente essa função com a entrada definida para o resultado anterior.

Karl Bielefeldt
fonte
5
+1 Não há problema em ter um estado no FP, apenas um estado não mutável .
jhewlett
1
Obrigado por esta visão. Minhas preocupações com a ineficiência foram frustradas pelo @logc; os detalhes técnicos de como o estado é transformado é um problema de implementação de baixo nível que a própria linguagem deve resolver. Eu assisti Rich Hickey explicar como ele faz isso com Clojure em um vídeo.
Andrew Martin
1
@ jhewlett: Para ser mais preciso: FP tem estado, mesmo estado mutável, mas eles não o representam usando variáveis ​​mutáveis.
Giorgio
9

Conforme observado por @KarlBielefeldt, a abordagem funcional para esse problema é vê-lo como retornando um novo estado de um estado anterior. As funções em si não contêm nenhuma informação e, portanto, sempre atualizam o estado m para o estado n .

Eu acho que você acha isso ineficiente porque assume que o estado anterior deve ser mantido na memória ao computar o novo estado. Observe que a escolha entre escrever um estado completamente novo ou reescrever o antigo é um detalhe da implementação do ponto de vista de uma linguagem funcional.

Por exemplo, digamos que eu tenha uma lista de um milhão de números inteiros e queira aumentar o décimo em uma unidade. Copiar a lista inteira com um novo número em sua décima posição é um desperdício, você está certo; mas é apenas a maneira conceitual de descrever a operação para o compilador ou intérprete de linguagem. O compilador ou intérprete é livre para pegar a primeira lista e substituir a décima posição.

A vantagem de descrever a operação dessa maneira é que o compilador pode raciocinar sobre a situação em que muitos encadeamentos desejam atualizar a mesma lista em posições diferentes. Se a operação for descrita como "vá para esta posição e sobrescreva o que você encontra", será o programador, e não o compilador, responsável por garantir que as sobrescrições não colidam.

Com tudo isso dito, mesmo em Haskell, existe uma mônada do Estado que ajuda a modelar situações em que "manter o estado" é uma solução mais intuitiva para um problema. Mas observe também alguns problemas que você acha que " inerentemente com estado, como gravar em um banco de dados " têm soluções imutáveis ​​como o Datomic . Isso pode ser surpreendente até você entender que é um conceito, não necessariamente sua realização.

logc
fonte
4
Eu acho que o trecho de atualização de uma lista grande é enganoso; Não conheço nenhum compilador que realmente realize essa otimização para você. Mesmo que o compilador possa fazer isso, isso só é possível nos casos em que você não se apega a versões anteriores da lista. A solução real é usar uma estrutura de dados de lista que não exija a cópia completa para alterar um único elemento.
D
@Doval: "Mesmo que o compilador possa fazer isso, isso só é possível nos casos em que você não se apega a versões anteriores da lista.": Isso me lembra tipos únicos no Clean.
Giorgio
4

A inscrição no modelo mental correto ajuda a pensar e gerenciar melhor o estado. Na minha opinião, o melhor modelo mental é o flip book . Depois que você clicar, você entenderá que o FP se apóia fortemente em estruturas de dados persistentes que capturam o estado do mundo e que funções são usadas para fazer a transição desse estado sem nenhuma mutação.

Rich Hickey ilumina essas idéias:

outras conversas, mas isso deve levá-lo na direção certa.

Mario T. Lanza
fonte
3

Ao escrever aplicativos grandes e moderadamente grandes, muitas vezes achei útil diferenciar as seções do aplicativo com estado e aquelas sem estado.

As estruturas de classes / dados na seção com estado armazenam os dados do aplicativo e as funções nesta seção funcionam com conhecimento implícito dos dados do aplicativo.

As classes / estruturas / funções de dados na seção sem estado estão lá para suportar os aspectos puramente algorítmicos do aplicativo. Eles não têm conhecimento implícito dos dados do aplicativo. Eles trabalham em uma natureza puramente funcional. As partes com estado do aplicativo podem sofrer alterações de estado como efeito colateral das funções em execução na seção sem estado do aplicativo.

A parte mais difícil é descobrir quais classes / funções colocar na seção sem estado e quais classes / funções colocar na seção com estado e ter a disciplina para colocá-los em arquivos / bibliotecas separados.

R Sahu
fonte
Como isso responde à pergunta? (not-downvoting)
kravemir 22/09
@kravemir, se você escreve um aplicativo usando OOP ou FP, precisa entender onde está o estado do aplicativo.
R Sahu