将状态与IO操作相结合

时间:2010-09-03 22:58:16

标签: haskell state monads

假设我有一个状态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。概括地说,我该怎么做?

2 个答案:

答案 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的内容。您可以详细了解他们herehere

答案 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完全相同。