haskell - Combina estado con acciones de IO.
state monads (2)
El enfoque básico sería reescribir su mónada Op
como un transformador de mónada. Esto le permitiría usarlo en una "pila" de mónadas, cuya parte inferior podría ser IO
.
Aquí hay un ejemplo de cómo se vería eso:
import Data.Array
import Control.Monad.Trans
data Registers = Reg { foo :: Int }
data ST = ST {registers :: Registers,
memory :: Array Int Int}
newtype Op m a = Op {runOp :: ST -> m (ST, a)}
instance Monad m => Monad (Op m) where
return a = Op $ /st -> return (st, a)
(>>=) stf f = Op $ /st -> do (st1, a1) <- runOp stf st
(st2, a2) <- runOp (f a1) st1
return (st2, a2)
instance MonadTrans Op where
lift m = Op $ /st -> do a <- m
return (st, a)
getState :: Monad m => (ST -> a) -> Op m a
getState g = Op $ /st -> return (st, g st)
updState :: Monad m => (ST -> ST) -> Op m ()
updState g = Op $ /st -> return (g st, ())
testOpIO :: Op IO String
testOpIO = do x <- lift getLine
return x
test = runOp testOpIO
Las cosas clave a observar:
- El uso de la clase
MonadTrans
- El uso de la función de
lift
que actúa sobregetLine
, que se utiliza para llevar la funcióngetline
desde la mónadaIO
mónadaOp IO
.
Por cierto, si no desea que la mónada IO
esté siempre presente, puede reemplazarla con la mónada Identity
en Control.Monad.Identity
. La mónada Op Identity
comporta exactamente igual que la mónada Op
original.
Supongamos que tengo una mónada estatal como:
data Registers = Reg {...}
data ST = ST {registers :: Registers,
memory :: Array Int Int}
newtype Op a = Op {runOp :: ST -> (ST, a)}
instance Monad Op where
return a = Op $ /st -> (st, a)
(>>=) stf f = Op $ /st -> let (st1, a1) = runOp stf st
(st2, a2) = runOp (f a1) st1
in (st2, a2)
con funciones como
getState :: (ST -> a) -> Op a
getState g = Op (/st -> (st, g st)
updState :: (ST -> ST) -> Op ()
updState g = Op (/st -> (g st, ()))
Etcétera. Quiero combinar varias operaciones en esta mónada con acciones de IO. Por lo tanto, podría escribir un ciclo de evaluación en el que se realizaron las operaciones en esta mónada y se ejecutó una acción de E / S con el resultado o, creo, debería poder hacer algo como lo siguiente:
newtype Op a = Op {runOp :: ST -> IO (ST, a)}
Las funciones de impresión tendrían el tipo Op () y otras funciones tendrían el tipo Op a, por ejemplo, podría leer un carácter del terminal utilizando una función de tipo IO Char. Sin embargo, no estoy seguro de qué aspecto tendría esa función, ya que, por ejemplo, la siguiente no es válida.
runOp (do x <- getLine; setMem 10 ... (read x :: Int) ... ) st
ya que getLine tiene el tipo IO Char, pero esta expresión tendría el tipo Op Char. En resumen, ¿cómo haría esto?
Usar liftIO
¡Ya estás muy cerca! Tu sugerencia
newtype Op a = Op {runOp :: ST -> IO (ST, a)}
Es excelente y el camino por recorrer.
Para poder ejecutar getLine
en un contexto Op
, necesita ''levantar'' la operación IO
en la mónada Op
. Puedes hacer esto escribiendo una función liftIO
:
liftIO :: IO a -> Op a
liftIO io = Op $ /st -> do
x <- io
return (st, x)
Ahora puedes escribir:
runOp (do x <- liftIO getLine; ...
Usa la clase MonadIO
Ahora, el patrón de elevar una acción IO en una mónada personalizada es tan común que existe una clase de tipo estándar para ella:
import Control.Monad.Trans
class Monad m => MonadIO m where
liftIO :: IO a -> m a
Para que su versión de liftIO
convierta en una instancia de MonadIO
en MonadIO
lugar:
instance MonadIO Op where
liftIO = ...
Usa StateT
Actualmente ha escrito su propia versión de la mónada estatal, especializada en el estado ST
. ¿Por qué no usas la mónada estatal estándar? Le ahorra tener que escribir su propia instancia de Monad
, que siempre es la misma para la mónada estatal.
type Op = StateT ST IO
StateT
ya tiene una instancia de MonadIO
y una instancia de MonadIO
, por lo que puede usarlas inmediatamente.
Transformadores de mónada
StateT
es un llamado transformador de mónada . Solo desea acciones de IO
en su mónada Op
, por lo que ya me he especializado con la mónada IO
para usted (consulte la definición de type Op
). Pero los transformadores de mónada te permiten apilar mónadas arbitrarias. De esto es de lo que está hablando Inverflow. Puedes leer más sobre ellos here y here .