Reputation: 4163
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
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 test
ing might not be the best idea as it will not take negative Int
s 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
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