Qual è il modo corretto di pensare alle monadi di stato?

1

Ultimamente ho usato un approccio funzionale nella mia programmazione. So che mutabilità e cambiamenti di stato non sono grandi nel paradigma. Ora mi trovo a lavorare con database e altre strutture dati in cui lo stato deve essere modificato. Ho cercato di conoscere le monadi e altri mezzi per interagire con lo stato, ma sono faticoso per comprendere il concetto.

Mi chiedevo, qual è il modo corretto di affrontare lo stato e le modifiche dello stato necessarie? Che tipo di costrutti funzionali possono essere utilizzati e come posso usarli. E, ancora più importante, come dovrei pensare all'interazione mentre sto codificando?

Questo sarebbe uno scenario in cui sarebbe una buona idea passare a OOP o qualche altro paradigma? Se lo faccio, come dovrei pensare a quell'interazione e lavorare attraverso i paradigmi.

    
posta Ucenna 13.11.2017 - 00:55
fonte

2 risposte

5

La programmazione funzionale comprende una serie di tecniche, a seconda di chi chiedi:

  • funzioni di ordine superiore,
  • chiusure,
  • trasparenza referenziale e
  • rendere esplicito lo stato.

Il valore dell'utilizzo di funzioni pure è che queste funzioni possono essere composte, senza effetti collaterali che portano a comportamenti imprevisti. Il loro comportamento è caratterizzato unicamente dai loro input e output. In quanto tali, sono più facili da ragionare. Idealmente, lo stato non dovrebbe essere un contesto globale implicito, ma un parametro esplicito / valore di ritorno di una funzione.

Finora, la teoria. In pratica, la stragrande maggioranza dei linguaggi funzionali, comprese le famiglie Lisp e ML, è anche un imperativo: lo stato può essere implicito nel flusso di controllo e le affermazioni possono avere effetti collaterali. Non è necessario rendere sempre lo stato esplicito. Altri aspetti della programmazione funzionale si applicano ancora.

Quello strano è Haskell. Poiché Haskell utilizza la valutazione lazy, le espressioni non vengono valutate in alcun ordine particolare. Se le espressioni hanno effetti collaterali, l'ordine di questi effetti non è specificato. Pertanto, Haskell introduce la monade IO e la monade di stato per codificare le dipendenze tra gli effetti a livello di sistema di tipi. Queste monadi consentono di forzare il calcolo anche quando il risultato viene eliminato o quando l'operazione non ha risultati chiari.

Nelle lingue senza valutazione pigra, la necessità di monade di stato o di IO è notevolmente ridotta. In effetti, la maggior parte delle lingue non ha un sistema di tipo sufficientemente espressivo per codificare queste monadi. È quindi sbagliato vedere le tecniche specifiche di Haskell come l'unico modo per essere veramente funzionali. Mescolare tecniche funzionali e imperative è perfettamente soddisfacente.

    
risposta data 13.11.2017 - 09:23
fonte
8

Mi piacerebbe offrire una prospettiva leggermente diversa che potrebbe aiutarti con il tuo problema attuale.

Non pensare che i cambiamenti di stato siano negativi. Dopo tutto, se non ci fossero stati cambiamenti nel tuo programma, potresti tranquillamente sostituirlo con un no-op.

Il problema con il cambiamento di stato è che è notoriamente difficile ragionare sullo sviluppatore e sul sistema. Ciò è peggiorato se le modifiche dello stato sono ampiamente diffuse attraverso il programma e soprattutto se sono implicite o nascoste.

Ciò che la programmazione funzionale promuove con concetti come la purezza e l'immutabilità non è tanto la rimozione del cambiamento di stato. Si tratta di rendere il cambiamento di stato trasparente e incapsulato.

Quindi, per fare il tuo esempio, un approccio sarebbe quello di avere la logica che genera i comandi DB semplicemente accumulare i comandi ma non eseguirli. Una volta che li hai tutti, puoi avere un componente che esegue i comandi.

Inoltre, se la tua logica di generazione accetta l'attuale elenco di comandi e restituisce un nuovo elenco, con le modifiche apportate, hai purezza nella generazione. Il vantaggio è che è più facile ragionare e testare le tue funzioni logiche.

Potrebbe sembrare che tutto ciò che hai fatto sia spostare la complessità e, in qualche misura, è vero. Ma nota, il codice che gestisce la mutabilità è piuttosto specifico: non ha alcuna logica di generazione, esegue solo comandi. Tutto ciò di cui avevi bisogno per cambiare il comando del comando wrt, ecc. Verrebbe incapsulato lì.

Dove le monadi tendono ad essere utili è quando hai bisogno di mantenere l'ordine e le informazioni sulla dipendenza, cioè se riesci a capire cosa devi fare, fallo subito e non sono molto utili. Ma si noti, nello schema sopra, c'è un ritardo concettuale tra la generazione e l'esecuzione in modo che possano essere utili. Una volta che consideri i percorsi di errore, il loro rigore può essere inestimabile.

E, in pratica, è il ritardo tra le azioni che trovo sia un buon indicatore che una monade può essere utile. Anche al di fuori delle lingue funzionali. Ad esempio, la gestione degli errori e il codice asincrono spesso traggono vantaggio dalle monadi. interessante, queste sono le aree in cui le monadi hanno avuto crossover, ad es. i futures sono abbastanza comuni nelle lingue non funzionali.

Per inciso, questa è la mia opinione sul motivo per cui le monadi sono state particolarmente prevalenti in Haskell. Non è tanto che il sistema di tipi possa esprimerli bene, anche se questo aiuta. È che, a causa della pigrizia di Haskell, quasi tutto ha un ritardo tra l'esecuzione del codice e l'effetto che si verifica.

    
risposta data 13.11.2017 - 13:47
fonte

Leggi altre domande sui tag