Affrontare i problemi di stato nella programmazione funzionale


18

Ho imparato a programmare principalmente dal punto di vista OOP (come la maggior parte di noi, ne sono sicuro), ma ho trascorso molto tempo a cercare di imparare a risolvere i problemi in modo funzionale. Ho una buona conoscenza di come risolvere i problemi di calcolo con FP, ma quando si tratta di problemi più complicati mi ritrovo sempre a dover ricorrere a oggetti mutabili. Ad esempio, se sto scrivendo un simulatore di particelle, voglio aggiornare gli "oggetti" con una posizione mutabile. Come vengono generalmente risolti i problemi intrinsecamente "statali" utilizzando tecniche di programmazione funzionale?


4
Il primo passo è probabilmente rendersi conto che i problemi non sono intrinsecamente significativi.
Telastyn,

4
Alcuni problemi sono intrinsecamente significativi, come scrivere su un database o disegnare una GUI. Prendendo il mio esempio di simulatore di particelle, quale sarebbe un modo alternativo di pensarci? Restituire nuove particelle ogni volta che la loro posizione si aggiorna per evitare lo stato mi sembra inefficiente, e non è un buon modello del mondo reale.
Andrew Martin,

4
Tranne forse per l'esempio del database, questi problemi non sono intrinsecamente significativi. Ad esempio, per la programmazione della GUI, stai davvero utilizzando lo stato mutabile come un modello di tempo scadente e implicito ; la programmazione reattiva funzionale consente di modellare il tempo in modo esplicito senza fare affidamento sullo stato fornendo flussi di eventi che è possibile combinare.
Tikhon Jelvis,

1
C'è una soluzione più semplice: quando si arriva a un problema che non è facilmente modellabile con le tecniche FP, non usare la programmazione funzionale per risolverlo. Strumento giusto per il lavoro e tutto il resto ...
Mason Wheeler,

1
@AndrewMartin Non è un buon modello del mondo reale? La matematica usata in fisica per modellare il mondo reale è puramente funzionale. Con un buon garbage collector l'allocazione di un oggetto costa meno di un puntatore e il tempo di raccolta è proporzionale al numero di oggetti vivi . Semmai, scommetterei che la principale fonte di inefficienze nella programmazione funzionale sta usando strutture di dati che non sono efficienti nella cache. Gli elenchi collegati e gli alberi binari non sono esattamente i bambini poster dell'efficienza della cache.
Doval,

Risposte:


20

I programmi funzionali gestiscono lo stato molto bene, ma richiedono un modo diverso di vederlo. Per il tuo esempio di posizione, una cosa da considerare è avere la tua posizione in funzione del tempo anziché di un valore fisso . Questo funziona bene per le particelle che seguono un percorso matematico fisso, ma è necessaria una strategia diversa per gestire un cambiamento nel percorso, ad esempio dopo una collisione.

La strategia di base qui è creare funzioni che assumano uno stato e restituiscano il nuovo stato . Quindi un simulatore di particelle sarebbe una funzione che accetta una Setdi particelle come input e restituisce una nuova Setdi particelle dopo un intervallo di tempo. Quindi basta chiamare ripetutamente quella funzione con il suo input impostato sul risultato precedente.


5
+1 Va bene avere uno stato in FP, ma non uno stato mutabile .
jhewlett,

1
Grazie per questa visione. Le mie preoccupazioni sull'inefficienza sono state vanificate da @logc; i dettagli tecnici di come lo stato viene trasformato è un problema di implementazione di basso livello che il linguaggio stesso dovrebbe risolvere. Ho visto Rich Hickey spiegare come lo fa con Clojure in un video.
Andrew Martin,

1
@jhewlett: Per essere più precisi: FP ha uno stato, anche mutevole, ma non lo rappresenta usando variabili mutabili.
Giorgio,

9

Come notato da @KarlBielefeldt, l'approccio funzionale a tale problema è quello di vederlo come se restituisse un nuovo stato da uno stato precedente. Le funzioni stesse non contengono alcuna informazione, quindi aggiorneranno sempre lo stato m allo stato n .

Penso che lo trovi inefficiente perché si presume che lo stato precedente debba essere tenuto in memoria mentre si calcola il nuovo stato. Si noti che la scelta tra scrivere uno stato completamente nuovo o riscrivere quello vecchio in atto è un dettaglio di implementazione dal punto di vista di un linguaggio funzionale.

Ad esempio, supponiamo di avere un elenco di un milione di numeri interi e di voler aumentare il decimo di un'unità. Copiare l'intero elenco con un nuovo numero nella sua decima posizione è uno spreco, hai ragione; ma è solo il modo concettuale di descrivere l'operazione al compilatore o all'interprete linguistico. Il compilatore o l'interprete è libero di prendere il primo elenco e sovrascrivere la decima posizione.

Il vantaggio di descrivere l'operazione in questo modo è che il compilatore può ragionare sulla situazione in cui molti thread vogliono aggiornare lo stesso elenco in posizioni diverse. Se l'operazione viene descritta come "vai in questa posizione e sovrascrivi ciò che trovi", allora è il programmatore, non il compilatore, che si occupa di assicurarsi che le sovrascritture non si scontrino.

Detto questo, anche in Haskell esiste una monade statale che aiuta a modellare le situazioni in cui "mantenere lo stato" è una soluzione più intuitiva per un problema. Ma per favore nota anche che alcuni problemi che trovi " intrinsecamente di stato, come scrivere su un database " hanno soluzioni immutabili come Datomic . Questo può essere sorprendente fino a quando non capisci che è un concetto, non necessariamente la sua realizzazione.


4
Penso che lo snippet sull'aggiornamento di un grande elenco sia fuorviante; Non conosco nessun compilatore che eseguirà effettivamente tale ottimizzazione per te. Anche se il compilatore potrebbe farlo, è possibile solo nei casi in cui non ti aggrappi alle versioni precedenti dell'elenco. La vera soluzione è quella di utilizzare una struttura di dati elenco che non richiede la copia del tutto per modificare un singolo elemento.
Doval,

@Doval: "Anche se il compilatore potesse farlo, è possibile solo nei casi in cui non ti aggrappi alle versioni precedenti dell'elenco.": Questo mi ricorda tipi unici in Clean.
Giorgio,

4

Abbonarsi al giusto modello mentale aiuta a pensare e gestire meglio lo stato. Nella mia mente, il miglior modello mentale è il libro a fogli mobili . Una volta che questo clic capirai che FP si appoggia pesantemente su strutture di dati persistenti che catturano lo stato del mondo e che le funzioni vengono utilizzate per passare a tale stato senza alcuna mutazione.

Rich Hickey illumina queste idee:

Ci sono altri discorsi, ma questo dovrebbe mandarti nella giusta direzione.


3

Quando ho scritto applicazioni grandi e moderatamente grandi, ho trovato spesso utile distinguere tra le sezioni dell'applicazione che sono stateful e quelle che sono apolidi.

Le classi / strutture di dati nella sezione stateful memorizzano i dati dell'applicazione e le funzioni in questa sezione funzionano con conoscenza implicita dei dati dell'applicazione.

Le classi / strutture di dati / funzioni nella sezione senza stato sono lì per supportare gli aspetti puramente algoritmici dell'applicazione. Non hanno una conoscenza implicita dei dati dell'applicazione. Funzionano in una natura puramente funzionale. Le parti con stato dell'applicazione potrebbero verificarsi cambiamenti di stato come effetto collaterale dell'esecuzione di funzioni nella sezione senza stato dell'applicazione.

La parte più difficile è capire quali classi / funzioni mettere nella sezione stateless e quali classi / funzioni mettere nella sezione stateful e avere la disciplina per metterle in file / librerie separate.


Come risponde alla domanda? (no downvoting)
kravemir,

@kravemir, sia che tu scriva un'applicazione usando OOP o FP, devi capire dove si trova lo stato dell'applicazione.
R Sahu,
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.