I linguaggi funzionali, per definizione, non dovrebbero mantenere variabili di stato. Perché, allora, Haskell, Clojure e altri forniscono implementazioni di memoria transazionale di software (STM)? Esiste un conflitto tra due approcci?
I linguaggi funzionali, per definizione, non dovrebbero mantenere variabili di stato. Perché, allora, Haskell, Clojure e altri forniscono implementazioni di memoria transazionale di software (STM)? Esiste un conflitto tra due approcci?
Risposte:
Non c'è nulla di sbagliato in un linguaggio funzionale che mantiene lo stato mutevole. Anche i linguaggi funzionali "puri" come Haskell devono mantenere lo stato per interagire con il mondo reale. Linguaggi funzionali "impuri" come Clojure consentono effetti collaterali che possono includere lo stato mutante.
Il punto principale è che i linguaggi funzionali scoraggiano lo stato mutabile meno che non ne abbia davvero bisogno . Lo stile generale è programmare utilizzando funzioni pure e dati immutabili e interagire solo con lo stato mutabile "impuro" nelle parti specifiche del codice che lo richiedono. In questo modo, puoi mantenere "puro" il resto della tua base di codice.
Penso che ci siano diversi motivi per cui STM è più comune nei linguaggi funzionali:
Personalmente mi piace l'approccio di Clojure di consentire la mutabilità, ma solo nel contesto di "riferimenti gestiti" rigorosamente controllati che possono partecipare alle transazioni STM. Tutto il resto nella lingua è "puramente funzionale".
;; define two accounts as managed references
(def account-a (ref 100))
(def account-b (ref 100))
;; define a transactional "transfer" function
(defn transfer [ref-1 ref-2 amount]
(dosync
(if (>= @ref-1 amount)
(do
(alter ref-1 - amount)
(alter ref-2 + amount))
(throw (Error. "Insufficient balance!")))))
;; make a stranfer
(transfer account-a account-b 75)
;; inspect the accounts
@account-a
=> 25
@account-b
=> 175
Si noti che il codice sopra è completamente transazionale e atomico: un osservatore esterno che legge i due saldi all'interno di un'altra transazione vedrà sempre uno stato atomico coerente, ovvero i due saldi saranno sempre pari a 200. Con la concorrenza basata su blocco, questo è un problema sorprendentemente difficile risolvere in un grande sistema complesso con molte entità transazionali.
Per un po 'di illuminazione in più, Rich Hickey fa un ottimo lavoro nel spiegare l'STM di Clojure in questo video
I linguaggi funzionali, per definizione, non dovrebbero mantenere variabili di stato
La tua definizione è sbagliata. La lingua che non può mantenere lo stato semplicemente non può essere utilizzata.
La differenza tra i linguaggi funzionali e quelli imperativi non è che uno di essi ha uno stato e l'altro no. In un certo senso mantengono lo stato.
Le lingue imperative sono state diffuse dallo stato in tutto il programma.
I linguaggi funzionali isolano e mantengono esplicitamente lo stato tramite firme di tipo. Ed è per questo che forniscono sofisticati meccanismi di gestione dello stato come STM.
A volte un programma richiede uno stato mutabile (ad esempio, i contenuti del database per un'app Web) e sarebbe bello poterlo utilizzare senza perdere i vantaggi della programmazione funzionale. Nei linguaggi non funzionali, lo stato mutevole permea tutto. Se lo rendi esplicito con un qualche tipo di API speciale , puoi limitarlo a una piccola regione identificabile mentre tutto il resto rimane puramente funzionale. I vantaggi di FP includono un debug più semplice, test unitari ripetibili, concorrenza indolore e compatibilità multicore / GPU.