在IO [String]上快速失败

时间:2015-10-30 17:23:37

标签: haskell

给定代表电子邮件列表的IO [String]

λ: let emails = return ["foo@bar.com", "bip@bap.net"] :: IO [String]

一个自动无法删除电子邮件的功能:

λ: let deleteEmail email = return $ Left "failed" :: IO (Either String ())

然后我查看了如何为列表中的每封电子邮件尝试删除每封电子邮件。但是,当单个电子邮件无法删除时,我想停止,即类似于sequence的行为。

λ: do { e <- emails; _ <- deleteEmail e; return e }
["foo@bar.com","bip@bap.net"]

λ: do { e <- emails; result <- deleteEmail e; return result }
Left "failed"

但是,通过查看第一个do的输出,如果无法删除foo@bar.com,则do会继续尝试删除bip@bap.net

如何在第一次电子邮件删除失败时修改上述代码失败?

2 个答案:

答案 0 :(得分:3)

有很多选择,但我会按照我的偏好顺序查看其中的三个。

  • 使用EitherT转换器而不是IO而不是IO (Either a)(您需要安装either包)
  • 使用fail Monad实例中的IO
  • throw例外。

答案 1 :(得分:2)

正如另一个答案所示,您可以使用ExceptTEitherT monad变换器。 ExceptT代表Either以及基础monad(在本例中为IO),您可以使用lift来评估操作。由于EihterT形成一个monad(因此也适用),您可以使用sequence_来组合所有电子邮件的删除,并在第一次失败时失败,例如。

import Control.Monad.Trans.Except
import Control.Monad.IO.Class (liftIO)
import Data.Foldable (sequence_)

getEmails :: IO [String]
getEmails = return ["foo@bar.com", "bip@bap.net", "something@example.com"]

deleteEmail :: String -> ExceptT String IO ()
deleteEmail email = do
  liftIO $ putStrLn ("Deleting " ++ email)
  if (isPrefixOf "bip" email)
     then throwE email
     else return ()

deleteAllEmails :: [String] -> ExceptT String IO ()
deleteAllEmails = sequence_ . map deleteEmail 

doDelete :: [String] -> IO ()
doDelete emails = do
  e <- runExceptT $ deleteAllEmails emails
  case e of
    Left err -> putStrLn $ "Failed: " ++ err
    Right _ -> putStrLn "success!"

您可能还想考虑使用Maybe String代替Either String ()和相应的MaybeT转换器。