2015-02-27 12 views
11

Quali sono le differenze e gli usi previsti per ioToST e unsafeSTToIO definiti in GHC.IO?Qual è la differenza tra `ioToST` e` unsafeIOToST` da GHC.IO

-- --------------------------------------------------------------------------- 

-- Coercions between IO and ST 

-- | A monad transformer embedding strict state transformers in the 'IO' 
-- monad. The 'RealWorld' parameter indicates that the internal state 
-- used by the 'ST' computation is a special one supplied by the 'IO' 
-- monad, and thus distinct from those used by invocations of 'runST'. 
stToIO  :: ST RealWorld a -> IO a 
stToIO (ST m) = IO m 

ioToST  :: IO a -> ST RealWorld a 
ioToST (IO m) = (ST m) 

-- This relies on IO and ST having the same representation modulo the 
-- constraint on the type of the state 
-- 
unsafeIOToST  :: IO a -> ST s a 
unsafeIOToST (IO io) = ST $ \ s -> (unsafeCoerce# io) s 

unsafeSTToIO :: ST s a -> IO a 
unsafeSTToIO (ST m) = IO (unsafeCoerce# m) 
+5

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

risposta

12

Le versioni sicure devono iniziare nel monade IO (perché non si può ottenere un ST RealWorld da runST) e consentono di passare da un contesto IO e un contesto ST RealWorld. Sono sicuri perché ST RealWorld è fondamentalmente la stessa cosa di IO.

Le versioni non sicure possono essere avviate ovunque (perché runST possono essere richiamate ovunque) e consentono di passare da un monad ST arbitrario a una monade IO. L'utilizzo di runST da un contesto puro e l'esecuzione di un valore unsafeIOToST all'interno della monade di stato equivale fondamentalmente all'utilizzo di unsafePerformIO.

+4

So che non è la domanda dell'OP, ma nella mia mente la domanda più grande è cosa, se non altro, rende "non sicuro STATO" poco sicuro. – dfeuer

+1

@dfeuer Potrebbe essere chiamato non sicuro solo per simmetria con 'unsafeIOToST', o forse puoi trovare un modo per usarlo per accedere a' STVar' al di fuori del suo wrapper 'runST', nel qual caso sarebbe davvero pericoloso tutto si. –

+2

Ho trovato una discussione su haskell-cafe. Sembra che non sia sicuro, ma non capisco nessuno degli esempi. – dfeuer

9

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.

+3

"Quindi sarebbe davvero utile jolly essere in grado di scrivere codice e usarlo in entrambe le monadi". In realtà c'è un modo molto semplice per raggiungere questo scopo senza nulla di pericoloso: ['primitive'] (https://hackage.haskell.org/package/primitive) offre una classe' PrimMonad' con istanze 'ST s' e' IO' e quasi tutto ciò che si potrebbe desiderare di scrivere funzioni che possono occuparsi di entrambe le istanze. Queste conversioni sono necessarie solo per far fronte al fatto che molte funzioni della libreria sono (inutilmente) specifiche. – dfeuer

+1

Ehm ... Ho appena provato, e il tuo programma non ha tipografico. I veri esempi sembrano essere piuttosto più sottili. – dfeuer

+2

Credo che l'esempio di 'foobar' non possa funzionare. Il motivo è che runST ha bisogno di un valore polytyped, mentre 'v' è monotipato. Infatti, 'unsafeSTToIO (newSTRef 42)' ha tipo 'forall s. IO (STRef s Int) ', e non' IO (forall s. STRef s Int) '. – chi

Problemi correlati