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
.
do
to run only in theMaybe
monad, or does it need access to theStateT Int
andIO
too? – pat