Mantenimento dello stato senza incarico


10

Sto imparando la programmazione funzionale e ho difficoltà a capire come vengono implementati alcuni scenari particolari senza l'uso di compiti. Il seguente semplice problema riassume praticamente la mia confusione.

Scrivere un programma che riceve eventi sulle modifiche di una determinata struttura di dati ed emette eventi quando questa struttura di dati raggiunge un determinato stato.

Quindi ho una copia della struttura dati che mantengo

datastructure_copy::DataStructure 

Ho il flusso di eventi che vengono attivati ​​quando cambia:

datastructure_changes::Stream Change

Ho una funzione che applica una modifica a una struttura di dati e ne restituisce una nuova copia:

apply_change::Change -> DataStructure -> DataStructure

E ho un predicato che controlla se lo stato dei dati ha raggiunto lo stato desiderato.

is_ready::DataStructure ->Boolean

In altre parole, ho bisogno di qualcosa come "riduci" che funzioni sui flussi.

So che un modo per implementarlo è ricalcolare lo stato ogni volta che arriva un cambiamento, tuttavia questo sembra poco pratico. Ho giocato un po 'con la monade di stato, ma mi sembra che sia destinato a risolvere un problema diverso.

Quindi c'è un altro modo per farlo?

Nota che la mia domanda è puramente concettuale e non conosco bene Haskell.


In un modo o nell'altro non vedrai mai un 'incarico' (nota la differenza tra incarico e legame) in Haskell perché 'Haskell non ha la nozione di "incarico", "stato mutabile" o "variabili" ed è un "puro" linguaggio funzionale "' . State Monad dovrebbe essere quello che stai cercando, avresti solo bisogno di imparare come usarlo. Se vuoi, posso dare una risposta più completa più tardi oggi.
Francesco Gramano,

Risposte:


2

So che un modo per implementarlo è ricalcolare lo stato ogni volta che arriva un cambiamento, tuttavia questo sembra poco pratico.

Se le modifiche applicate quando si verifica un evento non sono distributive, in un modo o nell'altro, dovrai ricalcolare lo stato ogni volta che si verifica un evento, poiché lo stato finale non è altro che lo stato iniziale, più le modifiche successive. E anche se le modifiche sono distributive, in genere si desidera trasformare in successione uno stato in quello successivo, poiché si desidera interrompere il processo con la stessa velocità con cui viene raggiunto un determinato stato e poiché è necessario calcolare lo stato successivo per determinare se il quello nuovo è lo stato desiderato.

Nella programmazione funzionale, i cambiamenti di stato sono generalmente rappresentati da chiamate di funzione e / o parametri di funzione.

Poiché non è possibile prevedere quando verrà calcolato lo stato finale, non è necessario utilizzare la funzione ricorsiva non di coda. Un flusso di stati, in cui ogni stato è basato su quello precedente, potrebbe essere una buona alternativa.

Quindi, nel tuo caso, risponderei alla domanda con il seguente codice, in Scala:

import scala.util.Random

val initState = 0.0
def nextState(state: Double, event: Boolean): Double = if(event) state + 0.3 else state - 0.1 // give a new state
def predicate(state: Double) = state >= 1

// random booleans as events
// nb: must be a function in order to force Random.nextBoolean to be called for each  element of the stream
def events(): Stream[Boolean] = Random.nextBoolean #:: events()  

val states: Stream[Double] = initState #:: states.zip(events).map({ case (s,e) => nextState(s,e)}) // a stream of all the successive states

// stop when the state is >= 1 ;
// display all the states computed before it stopped
states takeWhile(! predicate(_)) foreach println 

Che può dare, ad esempio (ho semplificato l'output):

0.0
0.3
0.2
0.5
0.8

val states: Stream[Double] = ... è la linea in cui vengono calcolati gli stati successivi.

Il primo elemento di questo flusso è lo stato iniziale del sistema. zipunisce il flusso di stati con il flusso di eventi in un singolo flusso di coppie di elementi, ogni coppia essendo un (stato, evento). maptrasforma ogni coppia in un singolo valore come nuovo stato, calcolato in funzione del vecchio stato e dell'evento associato. Un nuovo stato è quindi uno stato precedentemente calcolato, più l'evento associato che "modifica" lo stato.

Quindi, in sostanza, definisci un flusso potenzialmente infinito di stati, ogni nuovo stato essendo una funzione dell'ultimo stato calcolato e un nuovo evento. Poiché i flussi sono pigri in Scala (tra gli altri), ce ne sono solo su richiesta, quindi non è necessario calcolare gli stati inutili e si possono calcolare tutti gli stati che si desidera.

Se sei interessato solo al primo stato che rispetta il predicato, sostituisci l'ultima riga di codice con:

states find predicate get

Che recupera:

res7: Double = 1.1

Puoi darci qualche idea sulla linea che fa la magia:val states: Stream[Double]...
Bobby Marinoff,

Sicuro. Per favore, guarda la mia modifica.
mgoeminne,

1

Dici di avere 2 funzioni:

apply_change::Change -> DataStructure -> DataStructure
is_ready::DataStructure ->Boolean

e se ti capisco bene allora is_readyè piuttosto costoso quindi non vuoi farlo più e più volte per ogni evento di cambiamento.

Ciò di cui hai bisogno è una funzione che prende una DataStructure iniziale e la condensa in uno stato semplice e in una funzione che accetta uno stato condensato, una modifica e genera un nuovo stato condensato.

Supponiamo che DataStructure sia una tripletta x,y,ze stai aspettando che x, ye z siano numeri primi. Il tuo stato condensato potrebbe quindi essere un insieme di cui x, y, z non sono primi. Una modifica che rende x prime rimuove x dall'insieme. Una modifica che rende x non prime aggiunge x all'insieme (se non presente). DataStructure è pronta, quindi il set è vuoto.

L'idea sarebbe che l'aggiornamento dello stato condensato è molto più economico dell'aggiornamento di DataStructure e il calcolo è già da zero.

Nota: un approccio ancora migliore potrebbe essere quello di tenere traccia di quali di x, y, z sono stati controllati per essere primi e se dove. Per ogni modifica si contrassegna il campo relativo come non selezionato. Quindi quando is_ready viene chiamato, controlla e ricorda. Questo è meglio se non si controlla is_ready dopo ogni modifica poiché x potrebbe cambiare più volte e si controlla solo per prime una volta.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.