在Haskell中使用ErrorT

时间:2015-05-08 19:17:26

标签: haskell error-handling monad-transformers

我有一项任务是实现一个重复询问用户密码的函数,并说明它是否正确。如果密码正常,则显示“存储在数据库中并退出”。我需要使用ErrorT monad转换器告诉用户他的密码不正确,所以基本行为看起来像这样

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'就像

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..." 

getValidPassword看起来像这样:

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

我在这里缺少什么? 谢谢你的帮助!

1 个答案:

答案 0 :(得分:1)

ErrorT数据类型在Left字段中记录错误,该字段只包含一个错误。它无法保存每个错误。如果您只想“抛出错误”,请使用IO monad并使用throw/catch中的Control.Exception

如果您想使用当前代码,请尝试以下操作:

withErr :: MonadError e m => (e -> m ()) -> m a -> m a
withErr f ac = catchError ac (\e -> f e >> throwError e)

此函数在重新抛出之前对错误应用其他函数。在您的情况下,您只想打印错误:

askPassword' :: PwdErrorMonad ()
askPassword' = do
    liftIO $ putStrLn "Enter your new password:" 
    _ <- msum $ repeat $ withErr (liftIO . print) getValidPassword'
    liftIO $ putStrLn "Storing in database..." 

另一种方法是使用mtl API创建一个运行函数的函数,直到它不返回错误。

try :: MonadError e m => (e -> m ()) -> m a -> m a
try h ac = go where go = catchError ac (\e -> h e >> go)

您可以对错误执行任何操作,例如,将它们收集在列表中:

recordErrors :: MonadError e m => m a -> m (a, [e])
recordErrors = runWriterT . try (tell . (:[])) . lift 

但是,你想要的只是打印它们:

askPassword' :: PwdErrorMonad ()
askPassword' = do
    liftIO $ putStrLn "Enter your new password:"
    _ <- try (liftIO . print) getValidPassword'  
    liftIO $ putStrLn "Storing in database..."