在Haskell中的do块内进行错误检查

时间:2013-11-24 08:46:39

标签: haskell

我有以下一系列行动:

action1 :: IO Bool
action2 :: IO Bool
action3 :: IO Bool

某些行动只是另一个行动的组合

complexAction = do
  action1
  action2
  action3

我需要的是检查每个动作的结果的构造,并且在假的情况下返回False。我可以手动完成,但我知道haskell确实有工具来摆脱那种样板。

3 个答案:

答案 0 :(得分:3)

最简单的方法是

complexAction = fmap and (sequence [action1, action2, action3])

但你也可以编写自己的组合子,在第一次动作后停止:

(>>/) :: Monad m => m Bool -> m Bool -> m Bool
a >>/ b = do
   yes  <- a
   if yes then b else return False

您希望声明固定性以使其成为关联的

infixl 1 >>/  

然后你可以做

complexAction = action1 >>/ action2 >>/ action3

答案 1 :(得分:2)

我建议你改用MaybeT monad变压器。使用它比返回IO Bool

有很多优点
  • 您的操作可以有不同的类型和返回值(不仅仅是true / false)。如果您不需要任何结果,请使用MaybeT IO ()
  • 后者可以依赖前面的结果。
  • 由于MaybeT会生成MonadPlus实例的monad,因此您可以使用所有monad plus操作。即mzero表示失败的操作,x mplus yy iff x失败。

稍微不利的是,您必须对lift进行IOMaybeT IO次操作。这可以通过将您的操作编写为MonadIO m => ... -> m a而不是... -> IO a来解决。

例如:

import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans
import Control.Monad.Trans.Maybe

-- Lift print and putStrLn
print' :: (MonadIO m, Show a) => a -> m ()
print' = liftIO . print

putStrLn' :: (MonadIO m) => String -> m ()
putStrLn' = liftIO . putStrLn

-- Add something to an argument
plus1, plus3 :: Int -> MaybeT IO Int
plus1 n    = print' "+1"   >> return (n + 1)
plus3 n    = print' "+3"   >> return (n + 3)

-- Ignore an argument and fail
justFail :: Int -> MaybeT IO a
justFail _ = mzero

-- This action just succeeds with () or fails.
complexAction :: MaybeT IO ()
complexAction = do
  i <- plus1 0
  justFail i -- or comment this line out <----------------<
  j <- plus3 i
  print' j

-- You could use this to convert your actions to MaybeT IO:
boolIOToMaybeT :: IO Bool -> MaybeT IO ()
boolIOToMaybeT x = do
  r <- lift x
  if r then return () else mzero
-- Or you could have even more general version that works with other
-- transformers as well:
boolIOToMaybeT' :: (MonadIO m, MonadPlus m) => IO Bool -> m ()
boolIOToMaybeT' x = do
  r <- liftIO x
  if r then return () else mzero

main :: IO ()
main = runMaybeT complexAction >>= print'

答案 2 :(得分:2)

正如彼得所说,除了一个狭窄且包含的案例之外,你几乎肯定会从一开始就更好地连接代码以进行正确的错误处理。我知道我经常后悔不这样做,谴责自己进行一些非常乏味的重构。

如果可以的话,我想推荐Gabriel Gonzalez的errors软件包,它在Haskell的各种错误处理机制上比传统方法更加一致。它允许您通过代码检查EitherEither是捕获错误的好方法。 (相比之下,Maybe将丢失错误方面的信息。)一旦安装了软件包,就可以编写如下内容:

module Errors where

import Control.Error
import Data.Traversable (traverse)

data OK = OK Int deriving (Show)

action1, action2, action3 :: IO (Either String OK)
action1 = putStrLn "Running action 1" >> return (Right $ OK 1)
action2 = putStrLn "Running action 2" >> return (Right $ OK 2)
action3 = putStrLn "Running action 3" >> return (Left "Oops on 3")

runStoppingAtFirstError :: [IO (Either String OK)] -> IO (Either String [OK])
runStoppingAtFirstError = runEitherT . traverse EitherT

...输出如

*Errors> runStoppingAtFirstError [action1, action2]
Running action 1
Running action 2
Right [OK 1,OK 2]
*Errors> runStoppingAtFirstError [action1, action3, action2]
Running action 1
Running action 3
Left "Oops on 3"

(但请注意,此处的计算在第一个错误处停止,并且直到最终结束时才开始计算 - 这可能不是您想要的。errors包肯定是足够广泛的许多其他变化是可能的。)