[英]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.Identity
的Identity
monad。 Op Identity
monad的行為與原始的Op
monad完全相同。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.