Differenza tra State, ST, IORef e MVar


91

Sto lavorando a Scrivi uno schema in 48 ore (ho circa 85 ore) e sono arrivato alla parte relativa all'aggiunta di variabili e compiti . C'è un grande salto concettuale in questo capitolo, e vorrei che fosse stato fatto in due fasi con un buon refactoring nel mezzo piuttosto che saltare direttamente alla soluzione finale. Comunque…

Ho ottenuto perso con un certo numero di classi diverse che sembrano servire allo stesso scopo: State, ST, IORef, e MVar. I primi tre sono menzionati nel testo, mentre l'ultimo sembra essere la risposta preferita a molte domande StackOverflow sui primi tre. Sembrano tutti portare uno stato tra invocazioni consecutive.

Cosa sono ciascuno di questi e in che modo differiscono l'uno dall'altro?


In particolare queste frasi non hanno senso:

Invece, utilizziamo una funzionalità chiamata thread di stato , che consente a Haskell di gestire lo stato aggregato per noi. Questo ci consente di trattare le variabili mutabili come faremmo in qualsiasi altro linguaggio di programmazione, utilizzando le funzioni per ottenere o impostare variabili.

e

Il modulo IORef consente di utilizzare variabili stateful all'interno della monade IO .

Tutto ciò rende la linea type ENV = IORef [(String, IORef LispVal)]confusa: perché la seconda IORef? Cosa si romperà se scriverò type ENV = State [(String, LispVal)]invece?

Risposte:


119

La Monade di Stato: un modello di stato mutevole

La monade State è un ambiente puramente funzionale per programmi con stato, con una semplice API:

  • ottenere
  • mettere

Documentazione nel pacchetto mtl .

La monade State è comunemente usata quando è necessario lo stato in un singolo thread di controllo. In realtà non utilizza lo stato mutabile nella sua implementazione. Invece, il programma è parametrizzato dal valore state (cioè lo stato è un parametro aggiuntivo a tutti i calcoli). Lo stato sembra essere mutato solo in un singolo thread (e non può essere condiviso tra i thread).

La monade ST e le STRef

La monade ST è la cugina ristretta della monade IO.

Consente uno stato mutabile arbitrario , implementato come memoria mutabile effettiva sulla macchina. L'API è sicura nei programmi privi di effetti collaterali, poiché il parametro di tipo rango 2 impedisce ai valori che dipendono dallo stato modificabile di sfuggire all'ambito locale.

Consente quindi una mutabilità controllata in programmi altrimenti puri.

Comunemente utilizzato per array mutabili e altre strutture dati che vengono mutate, quindi congelate. È anche molto efficiente, poiché lo stato mutabile è "accelerato hardware".

API principale:

  • Control.Monad.ST
  • runST - avvia un nuovo calcolo dell'effetto memoria.
  • E STRefs : puntatori a celle mutabili (locali).
  • Anche gli array basati su ST (come i vettori) sono comuni.

Consideralo il fratello meno pericoloso della monade IO. O IO, dove puoi solo leggere e scrivere in memoria.

IORef: STRefs in IO

Questi sono STRef (vedi sopra) nella monade IO. Non hanno le stesse garanzie di sicurezza delle STRef sulla località.

MVars: IORefs con serrature

Come STRefs o IORefs, ma con un blocco collegato, per un accesso simultaneo sicuro da più thread. IORefs e STRefs sono sicuri solo in un'impostazione multi-thread quando si utilizza atomicModifyIORef(un'operazione atomica di confronto e scambio). Gli MVars sono un meccanismo più generale per condividere in modo sicuro lo stato mutevole.

Generalmente, in Haskell, usa MVars o TVars (cellule mutabili basate su STM), su STRef o IORef.


3
Cosa significa M in MVars e T in TVars? Immagino "Mutevole", "Transazionale". Interessante come ST significhi State Thread.
CMCDragonkai

10
Perché dici che MVardovrebbe essere preferito STRef? STRefgarantisce che solo un thread può modificarlo (e che non possono verificarsi altri tipi di IO) - sicuramente è meglio se non ho bisogno dell'accesso simultaneo allo stato mutabile?
Benjamin Hodgson

@CMCDragonkai Ho sempre pensato che la M stia per mutex, ma non riesco a trovarlo documentato da nessuna parte.
Andrew Thaddeus Martin

37

Ok, inizio con IORef. IOReffornisce un valore che è mutabile nella monade IO. È solo un riferimento ad alcuni dati e, come ogni riferimento, ci sono funzioni che consentono di modificare i dati a cui si riferisce. In Haskell, tutte queste funzioni operano in IO. Puoi pensarlo come un database, un file o un altro archivio dati esterno: puoi ottenere e impostare i dati al suo interno, ma per farlo è necessario passare attraverso IO. Il motivo per cui IO è necessario è perché Haskell è puro ; il compilatore ha bisogno di un modo per sapere a quali dati punta il riferimento in un dato momento (leggi il post sul blog di sigfpe "Avresti potuto inventare le monadi" ).

MVars sono fondamentalmente la stessa cosa di uno IORef, tranne per due differenze molto importanti. MVarè una primitiva di concorrenza, quindi è progettato per l'accesso da più thread. La seconda differenza è che una MVarè una scatola che può essere piena o vuota. Quindi dove un IORef Intha sempre un Int(o è in fondo), un MVar Intpuò avere un Into può essere vuoto. Se un thread tenta di leggere un valore da un vuoto MVar, si bloccherà fino a quando non MVarviene riempito (da un altro thread). Fondamentalmente un MVar aè equivalente a un IORef (Maybe a)con semantica extra utile per la concorrenza.

Stateè una monade che fornisce uno stato mutabile, non necessariamente con IO. In effetti, è particolarmente utile per i calcoli puri. Se hai un algoritmo che usa lo stato ma non lo è IO, una Statemonade è spesso una soluzione elegante.

C'è anche una versione trasformatori monade di Stato, StateT. Questo viene spesso utilizzato per contenere dati di configurazione del programma o tipi di stato "game-world-state" nelle applicazioni.

STè qualcosa di leggermente diverso. La struttura dati principale in STè il STRef, che è come un IORefma con una monade diversa. La STmonade usa l'inganno del sistema dei tipi (i "thread di stato" menzionati dai documenti) per garantire che i dati mutabili non possano sfuggire alla monade; cioè, quando esegui un calcolo ST ottieni un risultato puro. Il motivo per cui ST è interessante è che è una monade primitiva come IO, che consente ai calcoli di eseguire manipolazioni di basso livello su bytearrays e puntatori. Ciò significa che STpuò fornire un'interfaccia pura durante l'utilizzo di operazioni di basso livello su dati mutabili, il che significa che è molto veloce. Dal punto di vista del programma, è come se il STcalcolo venisse eseguito in un thread separato con archiviazione locale del thread.


17

Altri hanno fatto le cose fondamentali, ma per rispondere alla domanda diretta:

Tutto ciò rende ENV = IORef [(String, IORef LispVal)] confuso il tipo di linea . Perché il secondo IORef? Cosa si romperà se lo faccio type ENV = State [(String, LispVal)]invece?

Lisp è un linguaggio funzionale con stato mutevole e ambito lessicale. Immagina di aver chiuso su una variabile mutabile. Ora hai un riferimento a questa variabile in giro all'interno di qualche altra funzione, diciamo (in pseudocodice in stile haskell) (printIt, setIt) = let x = 5 in (\ () -> print x, \y -> set x y). Ora hai due funzioni: una stampa x e l'altra imposta il suo valore. Quando si valuta printIt, si desidera cercare il nome di x nell'ambiente iniziale in cui è printItstato definito, ma si desidera cercare il valore a cui è associato il nome nell'ambiente in cui printItviene chiamato (dopo che setItpotrebbe essere stato chiamato un numero qualsiasi di volte ).

Ci sono modi oltre ai due IORef per farlo, ma certamente hai bisogno di più dell'ultimo tipo che hai proposto, il che non ti consente di alterare i valori a cui i nomi sono legati in modo lessicale. Cerca su Google il "problema dei funargs" per un'interessante storia preistorica.

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.