Reputation: 187
I have a task to implement a function that repeatly asks user for a password and says if it is not correct. If the password is OK, then it says "Storing in database and exits". I need to use ErrorT monad transformer to tell user, that his password is incorrect, so the basic behavior looks like this
GHCi>runErrorT askPassword'
Enter your new password:
qwerty
Incorrect input: password is too short!
qwertyuiop
Incorrect input: password must contain some digits!
qwertyuiop123
Incorrect input: password must contain some punctuations!
qwertyuiop123!!!
Storing in database...
GHCi>
askPassword'
is like
data PwdError = PwdError String
type PwdErrorMonad = ErrorT PwdError IO
instance Error PwdError where
noMsg = PwdError "Unknown error"
strMsg s = PwdError s
instance Show PwdError where
show (PwdError s) = show s
askPassword' :: PwdErrorMonad ()
askPassword' = do
liftIO $ putStrLn "Enter your new password:"
value <- msum $ repeat getValidPassword'
liftIO $ putStrLn "Storing in database..."
and getValidPassword
looks like this:
getValidPassword' :: PwdErrorMonad String
getValidPassword' = do
s <- liftIO getLine
if length s < 8
then throwError $ PwdError "Incorrect input: password is too short!"
else if (not $ any isNumber s)
then throwError $ PwdError "Incorrect input: password must contain some digits!"
else if (not $ any isPunctuation s)
then throwError $ PwdError "Incorrect input: password must contain some punctuations!"
else return s
What am i missing here? Thanks for help!
Upvotes: 0
Views: 377
Reputation: 14588
The ErrorT datatype records errors in the Left
field, which holds exactly one error. It cannot save each error. If you want to just "throw an error", use the IO monad and use throw/catch
from Control.Exception
.
If you want to use your current code, try the following:
withErr :: MonadError e m => (e -> m ()) -> m a -> m a
withErr f ac = catchError ac (\e -> f e >> throwError e)
This function applies an additional function to the error before it is rethrown. In your case, you just want to print the error:
askPassword' :: PwdErrorMonad ()
askPassword' = do
liftIO $ putStrLn "Enter your new password:"
_ <- msum $ repeat $ withErr (liftIO . print) getValidPassword'
liftIO $ putStrLn "Storing in database..."
Another approach is to use the mtl API to create a function which runs a function until it doesn't return an error.
try :: MonadError e m => (e -> m ()) -> m a -> m a
try h ac = go where go = catchError ac (\e -> h e >> go)
You can do anything with the errors, for example, collect them in a list:
recordErrors :: MonadError e m => m a -> m (a, [e])
recordErrors = runWriterT . try (tell . (:[])) . lift
However, what you want is to just print them:
askPassword' :: PwdErrorMonad ()
askPassword' = do
liftIO $ putStrLn "Enter your new password:"
_ <- try (liftIO . print) getValidPassword'
liftIO $ putStrLn "Storing in database..."
Upvotes: 1