6
votes

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.

2
Do you want the code in the inner do to run only in the Maybe monad, or does it need access to the StateT Int and IO too?pat

2 Answers

8
votes

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.

3
votes

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