I have the following code:
import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.State
type T = StateT Int IO Int
someMaybe = Just 3
f :: T
f = do
x <- get
val <- lift $ do
val <- someMaybe
-- more code in Maybe monad
-- return 4
return 3
When I use do
notation inside to work in Maybe
monad it fails. From the error it gives it looks like type signature for this do
doesn't match. However I have no idea how to fix it. I tried some lift
combinations, but none of them worked and I don't want to guess anymore.
The problem is that Maybe
is not part of your transformer stack. If your transformer only knows about StateT Int
and IO
, it does not know anything about how to lift Maybe
.
You can fix this by changing your type T
to something like:
type T = StateT Int (MaybeT IO) Int
(You'll need to import Control.Monad.Trans.Maybe
.)
You will also need to change your inner do
to work with MaybeT
rather than Maybe
. This means wrapping raw Maybe a
values with MaybeT . return
:
f :: T
f = do
x <- get
val <- lift $ do
val <- MaybeT $ return someMaybe
-- more code in Maybe monad
return 4
return 3
This is a little awkward, so you probably want to write a function like liftMaybe
:
liftMaybe = MaybeT . return
If you used lift
to lift IO a
values in other parts of your code, this will now break because you have three levels in your transformer stack now. You will get an error that looks like this:
Couldn't match expected type `MaybeT IO t0'
with actual type `IO String'
To fix this, you should use liftIO
for all your raw IO a
values. This uses a typeclass to life IO
actions through any number of transformer layers.
In response to your comment: if you only have a bit of code depending on Maybe
, it would be easier just to put the result of the do
notation into a variable and match against that:
let maybeVal = do val <- someMaybe
-- more Maybe code
return 4
case maybeVal of
Just res -> ...
Nothing -> ...
This means that the Maybe
code will not be able to do an IO. You can also naturally use a function like fromMaybe
instead of case
.
If you want to run the code in the inner do
purely in the Maybe
monad, you will not have access to the StateT Int
or IO
monads (which might be a good thing). Doing so will return a Maybe
value, which you will have to scrutinize:
import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.State
type T = StateT Int IO Int
someMaybe = Just 3
f :: T
f = do
x <- get
-- no need to use bind
let mval = do
-- this code is purely in the Maybe monad
val <- someMaybe
-- more code in Maybe monad
return 4
-- scrutinize the resulting Maybe value now we are back in the StateT monad
case mval of
Just val -> liftIO . putStrLn $ "I got " ++ show val
Nothing -> liftIO . putStrLn $ "I got a rock"
return 3
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