dan*_*tin 28 monads state haskell
假设我有一个状态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)
Run Code Online (Sandbox Code Playgroud)
功能如
getState :: (ST -> a) -> Op a
getState g = Op (\st -> (st, g st)
updState :: (ST -> ST) -> Op ()
updState g = Op (\st -> (g st, ()))
Run Code Online (Sandbox Code Playgroud)
等等.我想将此monad中的各种操作与IO操作结合起来.所以我可以编写一个评估循环,在该循环中执行此monad中的操作,并使用结果执行IO操作,或者,我认为,我应该能够执行以下操作:
newtype Op a = Op {runOp :: ST -> IO (ST, a)}
Run Code Online (Sandbox Code Playgroud)
打印功能的类型为Op(),其他功能的类型为Op a,例如,我可以使用IO Char类型的函数从终端读取字符.但是,我不确定这样的函数会是什么样子,因为例如,以下内容无效.
runOp (do x <- getLine; setMem 10 ... (read x :: Int) ... ) st
Run Code Online (Sandbox Code Playgroud)
因为getLine具有类型IO Char,但是此表达式将具有类型Op Char.概括地说,我该怎么做?
Mar*_*ijn 31
使用liftIO
你已经很亲密了!你的建议
newtype Op a = Op {runOp :: ST -> IO (ST, a)}
Run Code Online (Sandbox Code Playgroud)
非常好,还有很长的路要走.
为了能够getLine在Op上下文中执行,您需要将IO操作"提升" 到Opmonad中.您可以通过编写函数来完成此操作liftIO:
liftIO :: IO a -> Op a
liftIO io = Op $ \st -> do
x <- io
return (st, x)
Run Code Online (Sandbox Code Playgroud)
你现在可以写:
runOp (do x <- liftIO getLine; ...
Run Code Online (Sandbox Code Playgroud)
使用MonadIO类
现在,将IO操作提升到自定义monad的模式非常普遍,因此有一个标准类型类:
import Control.Monad.Trans
class Monad m => MonadIO m where
liftIO :: IO a -> m a
Run Code Online (Sandbox Code Playgroud)
这样你的版本liftIO就变成了一个实例MonadIO:
instance MonadIO Op where
liftIO = ...
Run Code Online (Sandbox Code Playgroud)
使用StateT
您目前已经编写了自己的状态monad版本,专门用于声明ST.你为什么不使用标准的状态monad?它使您不必编写自己的Monad实例,这对于状态monad总是相同的.
type Op = StateT ST IO
Run Code Online (Sandbox Code Playgroud)
StateT已经有一个Monad实例和一个MonadIO实例,所以你可以立即使用它们.
Monad变形金刚
StateT是一个所谓的monad变压器.你只需要monad中的IO动作Op,所以我已经专门IO为你做了monad(参见定义type Op).但monad变换器允许你堆叠任意monad.这就是关于inverflow的内容.你可以在这里和这里阅读更多相关信息.
int*_*low 25
基本方法是将Opmonad 重写为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
Run Code Online (Sandbox Code Playgroud)
要注意的关键事项:
MonadTrans课程lift函数的使用,getLine用于将getline函数从IOmonad带入Op IOmonad.顺便提一下,如果你不想IO总是出现monad,你可以用Identitymonad 替换它Control.Monad.Identity.该Op Identity单子的行为完全一样,你原来的Op单子.
| 归档时间: |
|
| 查看次数: |
6214 次 |
| 最近记录: |