Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Maybe monad inside stack of transformers

Tags:

haskell

monads

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.

like image 812
Adrian Avatar asked Apr 17 '13 15:04

Adrian


2 Answers

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.

like image 50
Tikhon Jelvis Avatar answered Dec 05 '22 06:12

Tikhon Jelvis


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
like image 21
pat Avatar answered Dec 05 '22 06:12

pat