0

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 ?

Ashish Negi
  • 5,193
  • 8
  • 51
  • 95
  • 1
    No need to bind a value to `a` if you aren't going to use it; `main = runMaybeT askPassphrase >> return ()` – chepner Jun 11 '16 at 13:13

1 Answers1

1

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.

ErikR
  • 51,541
  • 9
  • 73
  • 124
  • Can you give `type` of last example of `askPassphrase` ? Apologies for same name of functions for both versions.. i have not renamed them as `1` and `2`'s. Can you also change your answer with respect to that ? – Ashish Negi Jun 11 '16 at 16:40
  • Done - and I fixed a typo in the last code example. – ErikR Jun 11 '16 at 16:49
  • Also - pehaps this SO answer would help: http://stackoverflow.com/a/32582127/866915 – ErikR Jun 11 '16 at 16:49
  • your `askPassphrase2` gives me compiler errors.. What is its `type` ? `lift` and `runMaybeT` is lifting `IO` further to `t0 IO` – Ashish Negi Jun 11 '16 at 16:59
  • sorry still not compiles for me.. every line should be in same monad.. i guess line 3,4 `MaybeT IO ()` .. line 1 `MaybeT IO (Maybe String)`.. if you remove all `lift`s then it will compile but no `MabeT` usage would be their. – Ashish Negi Jun 11 '16 at 17:47
  • and i think we can not get it with `MaybeT ...` type for askPassphrase2 as `MaybeT` would not let us have failure condition. so may be we should remove all `lift`s – Ashish Negi Jun 11 '16 at 17:51
  • sorry.. my bad.. i had not put `askPassphrase2 :: MaybeT IO ()` type above the function and it was giving errors.. Thanks. – Ashish Negi Jun 11 '16 at 18:29