Ashish Negi
Ashish Negi

Reputation: 5301

understanding MonadTransformer examples

I am going through the tutorial at https://en.wikibooks.org/wiki/Haskell/Monad_transformers

I wrote following piece of codes. One without and other with MonadTransformer instance :

-- Simple Get Password functions. 
getPassphrase1 :: IO (Maybe String)
getPassphrase1 = do
  password <- getLine
  if isValid password
  then return $ Just password
  else return Nothing


askPassphrase1 :: IO ()
askPassphrase1 = do
  putStrLn "Enter password < 8 , alpha, number and punctuation:"
  p <- getPassphrase1
  case p of
    Nothing -> do          -- Q1. ### How to implement this with `MonadTrans` ?
      putStrLn "Invalid password. Enter again:"
      askPassphrase1
    Just password ->
      putStrLn $ "Your password is " ++ password

-- The validation test could be anything we want it to be.
isValid :: String -> Bool
isValid s = length s >= 8
            && any isAlpha s
            && any isNumber s
            && any isPunctuation s

Another using MonadT which i wrote myself.

getPassphrase2 :: MaybeT IO String
getPassphrase2 = do
  password <- lift getLine
  guard $ isValid password
  return password

askPassphrase2 :: MaybeT IO ()
askPassphrase2 = do
  lift $ putStrLn "Enter password < 8 , alpha, number and punctuation:"
  p <- getPassphrase2
  -- Q1. How to print "Invalid password" ?
  lift $ putStrLn $ "Your password is " ++ p

-- The validation test could be anything we want it to be.
isValid :: String -> Bool
isValid s = length s >= 8
            && any isAlpha s
            && any isNumber s
            && any isPunctuation s

main :: IO ()
main = do
  a <- runMaybeT askPassphrase2
  return ()

Both works.

But i am unable to understand how to add wrong password support in MonadTrans example. ?

Also, is main method ok.. or it can be written in a better way ?

Upvotes: 0

Views: 189

Answers (1)

ErikR
ErikR

Reputation: 52059

guard is the not what you want in the MaybeT approach. To check for an invalid password and be able to provide your own processing in that case you would just use your original version of getPassphase and lift it into the the MaybeT IO monad:

getPassphease2 = do result <- lift $ getPassphrase1
                    case result of
                        Nothing -> lift $ putStrLn "bad password"
                        Just pw -> lift $ putStrLn "your password is: " ++ pw

Explanation...

The MaybeT IO monad is for giving IO-actions the capability to fail and having that failure automatically handled by the monad. If any step fails, control goes all the way back to the runMaybeT and runMaybeT returns Nothing. It's like throwing an exception.

The point of using MaybeT is that you do no have to explicitly check to see if a step has failed - that checking is performed by the MaybeT monad after each step is called. That means you can write code assuming that each preceding step has succeeded - i.e. as if you were on the "happy path". This also means that you can't do something different if the previous step failed.

One possibility using the MaybeT version of getPassphrase is this:

main = do result <- runMaybeT askPassphrase2
          case result of
            Just _  -> return ()
            Nothing  -> putStrLn "Some failure happened... perhaps wrong password?"

The problem is that if runMaybeT returns Nothing it could mean that any step in askPassphrase failed, not just the guard.

Another way to use your MaybeT version of getPassphrase is to have askPassphrase run it with runMaybeT:

askPassphrase2 = do result <- lift $ runMaybeT getPassphrase2
                    case result of
                      Nothing -> lift $ putStrLn "bad password"
                      Just pw -> lift $ putStrLn $ "Your password is " ++ pw

Here we're using runMaybeT like a try-catch block.

Upvotes: 1

Related Questions