我有以下一系列行动:
action1 :: IO Bool
action2 :: IO Bool
action3 :: IO Bool
某些行动只是另一个行动的组合
complexAction = do
action1
action2
action3
我需要的是检查每个动作的结果的构造,并且在假的情况下返回False。我可以手动完成,但我知道haskell确实有工具来摆脱那种样板。
答案 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
:
MaybeT IO ()
。MaybeT
会生成MonadPlus
实例的monad,因此您可以使用所有monad plus操作。即mzero
表示失败的操作,x mplus y
,y
iff x
失败。稍微不利的是,您必须对lift
进行IO
次MaybeT 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的各种错误处理机制上比传统方法更加一致。它允许您通过代码检查Either
,Either
是捕获错误的好方法。 (相比之下,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
包肯定是足够广泛的许多其他变化是可能的。)