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
Opmonad as a monad transformer. This would allow you to use it in a “stack” of monads, the bottom of which might beIO.Here’s an example of what that might look like:
The key things to observe:
MonadTransclassliftfunction acting ongetLine, which is used to bring thegetlinefunction from theIOmonad and into theOp IOmonad.Incidentally, if you don’t want the
IOmonad to always be present, you can replace it with theIdentitymonad inControl.Monad.Identity. TheOp Identitymonad behaves exactly the same as your originalOpmonad.