将状态与IO操作组合在一起 [英] Combine state with IO actions

查看:138
本文介绍了将状态与IO操作组合在一起的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我有一个状态monad,比如:

  data寄存器= Reg {...} 

data ST = ST {registers :: Registers,
memory :: Array Int Int}

newtype Op a = Op {runOp :: ST - > (ST,a)}

实例Monad Op其中
返回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的类型为I O Char,但是这个表达式应该有Op Char类型。总的来说,我该如何做到这一点?

解决方案

基本方法是重写你的 Op monad作为monad转换器。这将允许你使用它在monads的堆栈中,它的底部可以是 IO



下面是一个可能的例子:

  import Data.Array 
import Control.Monad.Trans

data Registers = Reg {foo :: Int}

data ST = ST {registers :: Registers,
memory :: Array Int Int}

newtype Op ma = Op {runOp :: ST - > m(ST,a)}

实例Monad m => Monad(Op m)其中
返回a = Op $ \st - > return(st,a)
(>> =)stf f = Op $ \st - > (st1,a1)< - runOp stf st
(st2,a2)< - runOp(f a1)st1
返回(st2,a2)

实例MonadTrans Op其中
lift m = Op $ \st - >做一个< - 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 class

  • 使用 getLine 上运行函数,该函数用于将 getline 函数从 IO monad,并进入 Op IO monad。



  • 顺便提一句,如果您不希望 IO monad始终存在,您可以用 Identity monad在 Control.Monad.Identity 中。 操作标识 monad的行为与原始操作 monad完全相同。


    Suppose I have a state monad such as:

    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)
    

    with functions like

    getState :: (ST -> a) -> Op a
    getState g = Op (\st -> (st, g st)
    
    updState :: (ST -> ST) -> Op ()
    updState g = Op (\st -> (g st, ()))
    

    and so forth. I want to combine various operations in this monad with IO actions. So I could either write an evaluation loop in which operations in this monad were performed and an IO action is executed with the result, or, I think, I should be able to do something like the following:

    newtype Op a = Op {runOp :: ST -> IO (ST, a)}
    

    Printing functions would have type Op () and other functions would have type Op a, e.g., I could read a character from the terminal using a function of type IO Char. However, I'm not sure what such a function would look like, since e.g., the following is not valid.

    runOp (do x <- getLine; setMem 10 ... (read x :: Int) ... ) st
    

    since getLine has type IO Char, but this expression would have type Op Char. In outline, how would I do this?

    解决方案

    The basic approach would be to rewrite your Op monad as a monad transformer. This would allow you to use it in a "stack" of monads, the bottom of which might be IO.

    Here's an example of what that might look like:

    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
    

    The key things to observe:

    • The use of the MonadTrans class
    • The use of the lift function acting on getLine, which is used to bring the getline function from the IO monad and into the Op IO monad.

    Incidentally, if you don't want the IO monad to always be present, you can replace it with the Identity monad in Control.Monad.Identity. The Op Identity monad behaves exactly the same as your original Op monad.

    这篇关于将状态与IO操作组合在一起的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆