簡體   English   中英

將狀態與IO操作相結合

[英]Combine state with IO actions

假設我有一個狀態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。 概括地說,我該怎么做?

使用liftIO

你已經很親密了! 你的建議

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

非常好,還有很長的路要走。

為了能夠在Op上下文中執行getLine ,您需要將IO操作“提升”到Op monad中。 您可以通過編寫函數liftIO來完成此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變換器 你只需要你的Op monad中的IO動作,所以我已經將它專門用於你的IO monad(參見type Op的定義)。 但monad變換器允許你堆疊任意monad。 這就是關於inverflow的內容。 你可以在這里這里閱讀更多相關信息。

基本方法是將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始終存在,則可以將其替換為Control.Monad.IdentityIdentity monad。 Op Identity monad的行為與原始的Op monad完全相同。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM