假设我有一个状态monad,例如:
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)
具有
等功能getState :: (ST -> a) -> Op a
getState g = Op (\st -> (st, g st)
updState :: (ST -> ST) -> Op ()
updState g = Op (\st -> (g st, ()))
等等。我想将此monad中的各种操作与IO操作结合起来。所以我可以编写一个评估循环,在该循环中执行此monad中的操作并执行结果的IO操作,或者,我认为,我应该能够执行以下操作:
newtype Op a = Op {runOp :: ST -> IO (ST, a)}
打印功能的类型为Op(),其他功能的类型为Op a,例如,我可以使用IO Char类型的函数从终端读取字符。但是,我不确定这样的功能是什么样的,例如,以下内容无效。
runOp (do x <- getLine; setMem 10 ... (read x :: Int) ... ) st
因为getLine具有类型IO Char,但此表达式将具有类型Op Char。概括地说,我该怎么做?
答案 0 :(得分:31)
使用liftIO
你已经很亲密了!你的建议
newtype Op a = Op {runOp :: ST -> IO (ST, a)}
非常好,还有出路。
为了能够在getLine
上下文中执行Op
,您需要将IO
操作“提升”到Op
monad中。您可以通过编写函数liftIO
:
liftIO :: IO a -> Op a
liftIO io = Op $ \st -> do
x <- io
return (st, x)
您现在可以写:
runOp (do x <- liftIO getLine; ...
使用MonadIO类
现在,将IO动作提升为自定义monad的模式非常普遍,因此有一个标准类型类:
import Control.Monad.Trans
class Monad m => MonadIO m where
liftIO :: IO a -> m a
这样您的liftIO
版本就会成为MonadIO
的实例:
instance MonadIO Op where
liftIO = ...
使用StateT
您目前编写了自己的州monad版本,专门用于州ST
。你为什么不使用标准的状态monad?它使您不必编写自己的Monad
实例,这对于状态monad来说总是相同的。
type Op = StateT ST IO
StateT
已有Monad
个实例和MonadIO
个实例,因此您可以立即使用这些实例。
Monad变形金刚
StateT
是一个所谓的 monad转换器。您只需在IO
monad中执行Op
次操作,因此我已经将IO
monad专门用于您(请参阅type Op
的定义)。但monad变换器允许你堆叠任意monad。这就是关于inverflow的内容。您可以详细了解他们here和here。
答案 1 :(得分:25)
基本方法是将Op
monad重写为monad变换器。这将允许您在monad的“堆栈”中使用它,其底部可能是IO
。
以下是一个可能的示例:
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
要注意的关键事项:
MonadTrans
类lift
函数作用于getLine
,用于将getline
函数从IO
monad引入Op IO
monad 顺便提一下,如果您不希望IO
monad始终存在,则可以将其替换为Identity
中的Control.Monad.Identity
monad。 Op Identity
monad的行为与原始的Op
monad完全相同。