Estou aprendendo programação funcional e tenho problemas para entender como alguns cenários específicos são implementados sem o uso de atribuição. O seguinte problema simples resume bastante minha confusão.
Escreva um programa que receba eventos sobre alterações em uma determinada estrutura de dados e emita eventos quando essa estrutura de dados atinge um determinado estado.
Então, eu tenho uma cópia da estrutura de dados que mantenho
datastructure_copy::DataStructure
Eu tenho o fluxo de eventos que são disparados quando ele muda:
datastructure_changes::Stream Change
Eu tenho uma função que aplica uma alteração a uma estrutura de dados e retorna uma nova cópia:
apply_change::Change -> DataStructure -> DataStructure
E eu tenho um predicado que verifica se o estado dos dados atingiu o estado desejado.
is_ready::DataStructure ->Boolean
Em outras palavras, eu preciso de algo como 'reduzir' que funcione em fluxos.
Eu sei que uma maneira de implementar isso é recalcular o estado toda vez que uma mudança chega, no entanto, isso parece impraticável. Joguei um pouco com a mônada do Estado, mas parece-me que ela visa resolver um problema diferente.
Então, existe outra maneira de fazer isso?
Observe que minha pergunta é puramente conceitual e não estou profundamente familiarizado com Haskell.
fonte
Respostas:
Se as alterações aplicadas quando um evento ocorrer não forem distributivas, de uma maneira ou de outra, você terá que recalcular o estado toda vez que um evento ocorrer, pois o estado final nada mais é do que o estado inicial, além de alterações sucessivas. E mesmo que as alterações sejam distributivas, você normalmente deseja transformar sucessivamente um estado no próximo, pois deseja interromper seu processo o mais rápido que um determinado estado é alcançado e desde que seja necessário calcular o próximo estado para determinar se o novo é o estado desejado.
Na programação funcional, as alterações de estado são normalmente representadas por chamadas de função e / ou parâmetros de função.
Como você não pode prever quando o estado final será calculado, você não deve usar a função recursiva não-cauda. Um fluxo de estados, no qual cada estado se baseia no anterior, poderia ser uma boa alternativa.
Portanto, no seu caso, eu responderia à pergunta pelo seguinte código, em Scala:
O que pode dar, por exemplo (simplifiquei a saída):
val states: Stream[Double] = ...
é a linha em que estados sucessivos são calculados.O primeiro elemento desse fluxo é o estado inicial do sistema.
zip
mescla o fluxo de estados com o fluxo de eventos em um único fluxo de pares de elementos, cada par sendo um (estado, evento).map
transforma cada par em um único valor, sendo o novo estado, calculado em função do estado antigo e do evento associado. Um novo estado é, portanto, um estado calculado anteriormente, mais o evento associado que "modifica" o estado.Então, basicamente, você define um fluxo potencialmente infinito de estados, cada novo estado sendo uma função do último estado calculado e um novo evento. Como os fluxos são preguiçosos no Scala (entre outros), só são computados sob demanda, portanto você não precisa calcular estados inúteis e pode calcular quantos estados desejar.
Se você está interessado apenas no primeiro estado que respeita o predicado, substitua a última linha do código por:
Que recupera:
fonte
val states: Stream[Double]...
Você diz que tem 2 funções:
e se eu entendi direito,
is_ready
é bastante caro, então você não quer fazer isso para cada evento de mudança repetidamente.O que você precisa é que uma função pegue um DataStructure inicial e o condense em um estado simples e em uma função que assume um estado condensado, uma Alteração e gera um novo estado condensado.
Digamos que o DataStructure seja um trigêmeo
x,y,z
e você esteja esperando que x, ye z sejam números primos. Seu estado condensado pode então ser um conjunto de quais x, y, z não são primos. Uma mudança que faz x prime remove x do conjunto. Uma mudança que faz x não prime adiciona x ao conjunto (se não estiver presente). O DataStructure está pronto e o conjunto está vazio.A idéia seria que atualizar o estado condensado é muito mais barato que atualizar o DataStructure e a computação já está do zero.
Nota: Uma abordagem ainda melhor poderia ser acompanhar quais x, y, z foram verificados como primos e se eles foram. Para cada alteração, você sinaliza o campo relevante como não marcado. Então, quando is_ready for chamado, verifique e lembre-se. Isso é melhor se você não verificar is_ready após cada alteração, pois x poderá mudar várias vezes e você verificará apenas o prime uma vez.
fonte