Finlay Weber
Finlay Weber

Reputation: 4163

How to use Do notation with both Maybe and IO

I am trying to get a good grip on the do notation in Haskell.

I could use it with Maybe and then print the result. Like this:

maybeAdd :: Maybe Integer
maybeAdd = do one <- maybe1
              two <- maybe2
              three <- maybe3
              return (one + two + three)

main :: IO ()
main = putStr (show $ fromMaybe 0 maybeAdd)

But instead of having a separate function I am trying to use the do notation with the Maybe inside the main function. But I am not having any luck. The various attempts I tried include:

main :: IO ()
main = do one <- maybe1
          two <- maybe2
          three <- maybe3
          putStr (show $ fromMaybe 0 $ return (one + two + three))
main :: IO ()
main = do one <- maybe1
          two <- maybe2
          three <- maybe3
          putStr (show $ fromMaybe 0 $ Just (one + two + three))
main :: IO ()
main = do one <- maybe1
          two <- maybe2
          three <- maybe3
          putStr (show $ (one + two + three))

All of these leads to various types of compilation errors, which unfortunately I failed to decipher to get the correct way to do it.

How do I achieve the above? And perhaps maybe an explanation of why the approaches I tried were wrong also?

Upvotes: 4

Views: 2166

Answers (2)

Redu
Redu

Reputation: 26191

The MaybeT monad transformer would come handy in this particular case. MaybeT monad transformer is just a type defined something like;

newtype MaybeT m a = MaybeT {runMaybeT :: m (Maybe a)}

Actually transformers like MaybeT, StateT etc, are readily available in Control.Monad.Trans.Maybe, Control.Monad.Trans.State... For illustration purposes it' Monad instance could be something like shown below;

instance Monad m => Monad (MaybeT m) where
  return  = MaybeT . return . Just
  x >>= f = MaybeT $ runMaybeT x >>= g
            where
            g Nothing  = return Nothing
            g (Just x) = runMaybeT $ f x

so as you will notice the monadic f function takes a value that resides in the Maybe monad which itself is in another monad (IO in our case). The f function does it's thing and wraps the result back into MaybeT m a.

Also there is a MonadTrans class where you can have some common functionalities those are used by the transformer types. One such is lift which is used to lift the value into a transformer according to that particular instance's definition. For MaybeT it should look like

instance MonadTrans MaybeT where
    lift = MaybeT . (liftM Just)

Lets perform your task with monad transformers.

addInts :: MaybeT IO ()
addInts = do
          lift $ putStrLn "Enter two integers.."
          i <- lift getLine
          guard $ test i
          j <- lift getLine
          guard $ test j
          lift . print $ (read i :: Int) + (read j :: Int)
          where
          test = and . (map isDigit)

So when called like

λ> runMaybeT addInts
Enter two integers..
1453
1571
3024
Just ()

The catch is, since a monad transformer is also a member of Monad typeclass, one can nest them indefinitelly and still do things under a singe do notation.

Edit: answer gets downvoted but it is unclear to me why. If there is something wrong with the approach please care to elaborate me so that it helps people including me to learn something better.

Taking the opportunity of being on the edit session, i would like to add a better code since i think Char based testing might not be the best idea as it will not take negative Ints into account. So let's try using readMaybe from the Text.Read package while we are doing things with the Maybe type.

import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Class (lift)
import Text.Read (readMaybe)

addInts :: MaybeT IO ()
addInts = do
          lift $ putStrLn "Enter two integers.."
          i <- lift getLine
          MaybeT $ return (readMaybe i :: Maybe Int)
          j <- lift getLine
          MaybeT $ return (readMaybe j :: Maybe Int)
          lift . print $ (read i :: Int) + (read j :: Int)

I guess now it works better...

λ> runMaybeT addInts
Enter two integers..
-400
500
100
Just ()

λ> runMaybeT addInts
Enter two integers..
Not an Integer
Nothing

Upvotes: 3

chi
chi

Reputation: 116174

Each do block must work within a single monad. If you want to use multiple monads, you could use multiple do blocks. Trying to adapt your code:

main :: IO ()
main = do -- IO block
   let x = do -- Maybe block
          one <- maybe1
          two <- maybe2
          three <- maybe3
          return (one + two + three)
   putStr (show $ fromMaybe 0 x)

You could even use

main = do -- IO block
   putStr $ show $ fromMaybe 0 $ do -- Maybe block
      one <- maybe1
      two <- maybe2
      three <- maybe3
      return (one + two + three)
   -- other IO actions here

but it could be less readable in certain cases.

Upvotes: 8

Related Questions