Como as entidades com uma identidade e um estado persistente mutável são modeladas em uma linguagem de programação funcional?

8

Em uma resposta a esta pergunta (escrita por Pete), existem algumas considerações sobre POO versus PF. Em particular, sugere-se que as linguagens FP não sejam muito adequadas para modelar objetos (persistentes) que tenham uma identidade e um estado mutável.

Eu queria saber se isso é verdade ou, em outras palavras, como modelar objetos em uma linguagem de programação funcional. Pelo meu conhecimento básico de Haskell, pensei que alguém poderia usar mônadas de alguma forma, mas realmente não sei o suficiente sobre esse tópico para encontrar uma resposta clara.

Então, como as entidades com uma identidade e um estado persistente mutável normalmente são modeladas em uma linguagem funcional?

Aqui estão alguns detalhes adicionais para esclarecer o que tenho em mente. Pegue um aplicativo Java típico no qual eu possa (1) ler um registro de uma tabela de banco de dados em um objeto Java, (2) modificar o objeto de maneiras diferentes, (3) salvar o objeto modificado no banco de dados.

Como isso seria implementado, por exemplo, em Haskell? Inicialmente, eu lia o registro em um valor de registro (definido por uma definição de dados), realizava diferentes transformações aplicando funções a esse valor inicial (cada valor intermediário é uma nova cópia modificada do registro original) e depois escrevia o valor final do registro para o banco de dados.

Isso é tudo o que existe? Como posso garantir que, a cada momento, apenas uma cópia do registro seja válida / acessível? Não se deseja que diferentes valores imutáveis ​​representem diferentes instantâneos do mesmo objeto para serem acessíveis ao mesmo tempo.

Giorgio
fonte
Depende de um idioma. Rich Hickey, o autor do Clojure, recomenda o uso de estruturas de dados (listas, dicionários, conjuntos etc.) para representar seus dados. Eu não sou completamente vendido com a ideia; Gosto de des-referenciar os campos pelo nome, em vez de indexar ou mnemonic, e fazer com que o compilador verifique se não estraguei tudo. O F # possui estruturas que são como classes. msdn.microsoft.com/en-us/library/dd233233.aspx Mesmo usando Java ou C #, você pode ter objetos imutáveis ​​que podem "mudar" criando um objeto totalmente novo.
1
@gnat: Obrigado por editar o título: objetos definitivamente não era o termo mais apropriado.
Giorgio

Respostas:

7

A maneira usual de realizar mudanças de estado em uma linguagem pura como Haskell é modelá-las como funções que pegam o estado antigo e retornam uma versão modificada. Mesmo para objetos complexos, isso é eficiente devido à preguiçosa estratégia de avaliação de Haskell - mesmo que você esteja criando sintaticamente um novo objeto, ele não é copiado na sua totalidade; cada campo é avaliado apenas quando necessário.

Se você tiver mais do que algumas mudanças de estado local, as coisas podem se tornar desajeitadas, e é aí que as mônadas entram. O paradigma da mônada pode ser usado para encapsular um estado e suas mudanças; o exemplo do livro é a Statemônada que vem com uma instalação padrão do Haskell. Note, no entanto, que uma mônada não é nada de especial: é apenas um tipo de dados que expõe dois métodos ( >>=e return) e atende a algumas expectativas (as 'leis da mônada'). Sob o capô, a mônada do Estado faz exatamente o mesmo: pegue o estado antigo e retorne um estado modificado; somente a sintaxe é melhor.

tdammers
fonte
2
Eu acho que preguiça é a palavra errada. É eficiente porque Haskell pode compartilhar subcomponentes não modificados do estado. Isso pode acontecer em qualquer idioma que coloque a mutabilidade no nível do tipo.
Daniel Gratzer
@jozefg - embora isso possa ser verdade, em Haskell pelo menos sua implementação está inerentemente ligada à preguiça da linguagem; simplesmente funciona porque o valor inteiro de uma estrutura é calculado apenas quando necessário, e a cadeia de thunks que representam as atualizações é avaliada nesse ponto e apenas na medida do necessário para determinar qual é o valor mais recente do membro solicitado. Linguagens estritas puras precisam de operações de atualização de registros como primitivas para serem eficientes; Haskell não faz por causa de sua preguiça.
Jules
2

Eu não sou desenvolvedor de linguagem funcional, então, por favor, me atire em chamas se eu estiver errado, mas se estiver certo, pode ser uma analogia interessante.

Me disseram uma vez que o Excel é essencialmente uma linguagem funcional. Não estou falando sobre VBA e assim por diante, estou falando sobre o que acontece na planilha.

Portanto, você tem entradas, que podem ser tabulares, células individuais ou intervalos nomeados, etc., e através de uma cadeia de operações, você acaba com um resultado ou com muitos resultados. Mude uma entrada e ela flui imediatamente.

Essa analogia mantém a água?

Ian
fonte
1
Ao argumento de autoridade , você está correto;)
phant0m
1
Acredito que usar o Excel assim seja mais parecido com a programação de fluxo de dados , mas há alguma margem de manobra em que o Excel pode se encaixar. Pessoalmente, eu não tenho certeza que eu diria que é uma língua em primeiro lugar ...
Izkata
1
Você tem um ponto lá. Mas, ainda assim, o Excel é funcional nesse sentido. PS: Eu recomendo que você dê uma olhada na apresentação, acho muito interessante.
Phant0m
@Izkata Excel representaria um caso especial de fluxo de dados de programação: programação reativa
itsbruce
2

Presumo que você esteja falando sobre a característica apátrida da linguagem funcional pura.

Você toma o estado inicial como uma entrada e retorna o estado final como uma saída. Nada fundamentalmente diferente do que você faz em uma linguagem com uma noção de estado, exceto que você é mais explícito a respeito e pode ser tedioso e menos eficiente (*).

Observe que você não precisa necessariamente modificar todos os locais que referenciam seu objeto: eles podem conter um token e, em seguida, basta modificar a estrutura de dados que mapeia os tokens para o estado atual. Até então, você está implementando um sistema completo do estado usando uma linguagem sem estado e recebe de volta os problemas dos idiomas completos do estado.


(*) Por exemplo, procurei - e não encontrei - a estrutura de dados que me permite retornar em O (1) uma cópia modificada de uma estrutura de dados indexável por números inteiros consecutivos em O (1) também.

AProgrammer
fonte
+1. Sim, eu estava me referindo à modelagem de uma entidade com estado como um objeto (que tem uma identidade e um histórico de estados subsequentes) em uma linguagem sem estado. Qual seria a contrapartida do seu exemplo (marcado com (*)) em uma linguagem como Java? Uma matriz? Uma matriz de objetos?
Giorgio
@Giorgio, sim. Eu sei como fazer isso em O (log n), mas não em O (1) - e, em seguida, o fator constante também é um sucesso. Observe que eu usei estruturas de dados projetadas para linguagem funcional pura como uma maneira de implementar desfazer em linguagens completas de estado.
AProgrammer
Aliás, tecnicamente , um trie é O (1) sob as mesmas suposições simplificadoras frequentemente usadas ao discutir essas coisas, sendo uma coleção de tamanho variável com operações de inserção / exclusão cuja complexidade de tempo não depende do número de elementos presentes. Eles não são realmente comparáveis ​​a uma matriz mutável, no entanto.
CA McCann
Uma lista de associação normal [(Int, v)]permitirá que você substitua o valor em qualquer índice em tempo constante, simplesmente aceitando o novo valor. Desvantagens: o valor antigo ainda está usando RAM e a pesquisa é linear.
Singapolyma
1

Em resumo, cada estado de uma entidade é sua própria entidade. Portanto, na sua lógica de negócios clássica de "pedido", há uma Orderentidade e uma OrderVersionentidade. Ambos são imutáveis. A lógica que adiciona um item de linha pega a versão antiga e a nova OrderLineItemVersioncomo entrada e retorna uma nova OrderVersionentidade.

Isso facilita algumas coisas (principalmente a funcionalidade "desfazer"), mas algumas são mais difíceis.

Scott Whitlock
fonte