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?
Use liftIO
You're already very close! Your suggestion
newtype Op a = Op {runOp :: ST -> IO (ST, a)}
is excellent and the way to go.
To be able to execute getLine
in an Op
context, you need to 'lift' the IO
operation into the Op
monad. You can do this by writing a function liftIO
:
liftIO :: IO a -> Op a liftIO io = Op $ \st -> do x <- io return (st, x)
You can now write:
runOp (do x <- liftIO getLine; ...
Use class MonadIO
Now the pattern of lifting an IO action into a custom monad is so common that there is a standard type class for it:
import Control.Monad.Trans class Monad m => MonadIO m where liftIO :: IO a -> m a
So that your version of liftIO
becomes an instance of MonadIO
instead:
instance MonadIO Op where liftIO = ...
Use StateT
You've currently written your own version of the state monad, specialised to state ST
. Why don't you use the standard state monad? It saves you from having to write your own Monad
instance, which is always the same for the state monad.
type Op = StateT ST IO
StateT
already has a Monad
instance and a MonadIO
instance, so you can use those immediately.
Monad transformers
StateT
is a so-called monad transformer. You only want IO
actions in your Op
monad, so I've already specialized it with the IO
monad for you (see the definition of type Op
). But monad transformers allow you to stack arbitrary monads. This what intoverflow is talking about. You can read more about them here and here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With