haskell io ghc state-monad

haskell - ¿Cuál es la diferencia entre `ioToST` y` insafeIOToST` de GHC.IO



state-monad (2)

Las versiones seguras deben comenzar en la mónada IO (porque no puede obtener un ST RealWorld desde runST ) y le permiten cambiar entre el contexto IO y un contexto ST RealWorld . Están seguros porque ST RealWorld es básicamente lo mismo que IO.

Las versiones inseguras pueden comenzar en cualquier lugar (porque runST se puede llamar en cualquier lugar) y le permiten cambiar entre una mónada de ST arbitraria y la mónada IO. Utilizar runST desde un contexto puro y luego hacer unsafeIOToST dentro de la mónada de estado es básicamente equivalente a usar unsafePerformIO .

¿Cuáles pueden ser las diferencias y los usos previstos para ioToST y unsafeSTToIO definido en 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)


TL; DR . Las cuatro de estas funciones son solo typecasts. Todos son no operativos en el tiempo de ejecución. La única diferencia entre ellos son las firmas de tipo, ¡pero son las firmas de tipo las que hacen cumplir todas las garantías de seguridad en primer lugar!

La mónada ST y la mónada IO dan un estado mutable.

Es famoso que es imposible escapar de la mónada IO . [Bueno, no, puedes si usas unsafePerformIO . ¡No hagas eso!] Debido a esto, todas las E / S que ejecutará tu programa se agrupan en un solo bloque IO gigante, lo que impone un ordenamiento global de las operaciones. [Al menos, hasta que llames a forkIO , pero de todos modos ...]

La razón por la que unsafePerformIO es tan unsafePerformIO inseguro es que no hay forma de averiguar exactamente cuándo, si o cuántas veces ocurrirán las operaciones de E / S adjuntas, lo que generalmente es algo muy malo .

La mónada ST también proporciona un estado mutable, pero tiene un mecanismo de escape: la función runST . Esto le permite convertir un valor impuro en uno puro. Pero ahora no hay forma de garantizar el orden en que se ejecutarán los bloques ST separados. Para evitar una devastación total, debemos asegurarnos de que los bloques ST separados no puedan "interferir" entre sí.

Por esa razón, no puede realizar ninguna operación de E / S en la mónada ST . Puede acceder al estado mutable, pero ese estado no puede escapar del bloque ST .

La mónada IO y la mónada ST son en realidad la misma mónada . Y un IORef es en realidad un STRef , y así sucesivamente. Por lo tanto, sería realmente útil poder escribir código y usarlo en ambas mónadas. Y las cuatro funciones que menciona son tipos que le permiten hacer exactamente eso.

Para entender el peligro, necesitamos entender cómo ST logra es un pequeño truco. Todo está en el tipo fantasma en las firmas de tipo. Para ejecutar un bloque ST , debe funcionar para todas las posibles s :

runST :: (forall s. ST s x) -> x

Todas las cosas mutables tienen s en el tipo también, y por un accidente feliz, eso significa que cualquier intento de devolver cosas mutables de la mónada ST estará mal escrito. (Esto es realmente un truco, pero funciona tan bien ...)

Al menos, estará mal escrito si usa runST . Tenga en cuenta que ioToST le da un ST RealWorld x . En términos generales, IO x & approx; ST RealWorld x . Pero runST no lo aceptará como entrada. Entonces no puedes usar runST para ejecutar I / O.

El ioToST le proporciona un tipo que no puede usar con runST . Pero unsafeIOToST le da un tipo que funciona bien con runST . En ese punto, has implementado básicamente unsafePerformIO :

unsafePerformIO = runST . ioToST

El unsafeSTToIO permite obtener elementos mutables de un bloque ST , y potencialmente a otro:

foobar = do v <- unsafeSTToIO (newSTRef 42) let w = runST (readSTRef v) let x = runST (writeSTRef v 99) print w

¿Quieres adivinar qué se va a imprimir? Porque el caso es que tenemos tres acciones de ST aquí, que pueden suceder en cualquier orden. ¿La readSTRef ocurrirá antes o después de writeSTRef ?

[En realidad, en este ejemplo, la escritura nunca ocurre, porque no "hacemos" nada con x . Pero si transfiero x a una parte del código distante y no relacionada, y ese código pasa a inspeccionarlo, de repente nuestra operación de E / S hace algo diferente. ¡El código puro no debería poder afectar cosas mutables como esa!]

Editar: Parece que fui un poco prematuro. La función unsafeSTToIO permite tomar un valor mutable de la mónada ST , pero parece que requiere una segunda llamada para unsafeSTToIO para volver a poner la cosa mutable en la mónada ST . (En ese punto, ambas acciones son acciones IO , por lo que su orden está garantizada).

Por supuesto, también podría mezclar algo unsafeIOToST con unsafeIOToST , pero eso realmente no prueba que unsafeSTToIO por sí mismo no es seguro:

foobar = do v <- unsafeSTToIO (newSTRef 42) let w = runST (unsafeIOToST $ unsafeSTToIO $ readSTRef v) let x = runST (unsafeIOToST $ unsafeSTToIO $ writeSTRef v 99) print w

He jugado con esto, y todavía no he logrado convencer al verificador de tipos para que me permita hacer algo probablemente inseguro usando soloSTTOIO inseguro. Sigo convencido de que se puede hacer, y los diversos comentarios sobre esta cuestión parecen estar de acuerdo, pero en realidad no puedo construir un ejemplo. Aunque tienes la idea; cambie los tipos, y su seguridad se rompe.