TL; DR. Tutte e quattro queste funzioni sono solo tipografiche. Sono tutti no-op in fase di esecuzione. La differenza solo tra loro è il tipo di firme — ma sono le firme di tipo che fanno rispettare tutte le garanzie di sicurezza in primo luogo!
Il ST
monade e IO
monade sia darvi stato mutevole.
È notoriamente impossibile sfuggire alla monade IO
. [Bene, no, è possibile se si utilizza unsafePerformIO
. Per non farlo!] Per questo motivo, tutto l'I/O che il tuo programma eseguirà verrà raggruppato in un unico blocco gigante IO
, applicando così un ordine globale alle operazioni. [Almeno, fino a quando si chiama forkIO
, ma in ogni caso ...]
La ragione unsafePerformIO
è così maledettamente pericoloso è che non v'è alcun modo di capire esattamente quando, se, o quante volte l'ho chiuso/O operazioni si verificherà — che è in genere una cosa molto brutta .
Il ST
monade fornisce anche stato mutabile, ma fa hanno un meccanismo di fuga — la funzione runST
. Ciò ti consente di trasformare un valore impuro in uno puro. Ma ora c'è in nessun modo per garantire quale ordine verrà separato per i blocchi ST
. Per evitare la completa devastazione, è necessario assicurarsi che i blocchi separati di ST
non "interferiscano" tra loro.
Per questo motivo, non è possibile eseguire operazioni di I/O nella monade ST
. È possibile accedere allo stato mutabile, ma a tale stato non è consentito di uscire dal blocco ST
.
Il IO
monade e ST
Monade sono in realtà stessa Monade. E uno IORef
è in realtà un STRef
e così via. Quindi sarebbe davvero utile essere in grado di scrivere codice e usarlo in entrambe le monadi. E tutte e quattro le funzioni che hai citato sono cast di tipo che ti permettono di fare esattamente questo.
Per capire il pericolo, dobbiamo capire come lo ST
raggiunge il suo piccolo trucco. È tutto nel tipo fantasma s
nelle firme del tipo. Per eseguire un blocco ST
, ha bisogno di lavorare per tutti i possibili s
:
runST :: (forall s. ST s x) -> x
Tutte le cose mutabili ha s
nel tipo così, e da un incidente felice, questo significa che ogni tentativo di tornare roba mutabili fuori della monade ST
verrà scritta male. (Questo è davvero un po 'un trucco, ma funziona perfettamente ...)
Almeno, sarà mal digitato se si utilizza runST
. Si noti che ioToST
fornisce un ST RealWorld x
. In parole povere, IO x
& ca. ST RealWorld x
. Ma runST
non lo accetterà come input. Quindi non è possibile utilizzare runST
per eseguire I/O.
Il ioToST
fornisce un tipo che non è possibile utilizzare con runST
. Ma unsafeIOToST
ti dà un tipo che funziona perfettamente con runST
. A quel punto, si è fondamentalmente implementato unsafePerformIO
:
unsafePerformIO = runST . ioToST
Il unsafeSTToIO
consente di ottenere cose mutabili da un unico ST
blocco, e potenzialmente in un altro:
foobar = do
v <- unsafeSTToIO (newSTRef 42)
let w = runST (readSTRef v)
let x = runST (writeSTRef v 99)
print w
Wanna Prova a indovinare che cosa sta succedendo per essere stampato? Perché il fatto è che qui abbiamo tre azioni ST
, che possono avvenire in qualsiasi ordine. Il readSTRef
si verificherà prima o dopo lo writeSTRef
?
[In realtà, in questo esempio, la scrittura non avviene mai, perché non "facciamo" nulla con x
. Ma se passo x
ad una parte lontana, non correlata del codice, e quel codice capita di ispezionarlo, improvvisamente la nostra operazione di I/O fa qualcosa di diverso. codice puro non dovrebbe essere in grado di influenzare cose mutevoli del genere]
Edit: Sembra ero un po 'prematuro. La funzione unsafeSTToIO
consente di estrarre un valore mutabile dalla monade ST
, ma sembra che sia necessaria una seconda chiamata a unsafeSTToIO
per rimettere la cosa mutabile nella la monade ST
nuovamente. (A quel punto, entrambe le azioni sono IO
azioni, quindi il loro ordine è garantito.)
Si potrebbe naturalmente mix in qualche unsafeIOToST
pure, ma che in realtà non dimostrare che unsafeSTToIO
di per sé è pericoloso:
foobar = do
v <- unsafeSTToIO (newSTRef 42)
let w = runST (unsafeIOToST $ unsafeSTToIO $ readSTRef v)
let x = runST (unsafeIOToST $ unsafeSTToIO $ writeSTRef v 99)
print w
ho giocato in giro con questo, e io non sono ancora riuscito a convincere il tipo di controllo di farmi fare qualcosa di dimostrabilmente sicuri usando solounsafeSTToIO
. Rimango convinto che si possa fare, e i vari commenti su questa domanda sembrano essere d'accordo, ma non riesco a costruire un esempio. Hai capito il senso però; cambia i tipi e la tua sicurezza si rompe.
La differenza è nei tipi.ioToST può produrre solo un 'ST RealWorld a' che non è possibile passare a' runST :: (per tutti i punti a) -> a'. – user2407038