,我有以下组动作:错误检查中都在Haskell
action1 :: IO Bool
action2 :: IO Bool
action3 :: IO Bool
有些行动只是一个行动的组成
complexAction = do
action1
action2
action3
我需要的是检查结果施工每个动作并在虚假情况下返回False。我可以手动完成,但我确实知道haskell确实有工具来摆脱那种样板。
,我有以下组动作:错误检查中都在Haskell
action1 :: IO Bool
action2 :: IO Bool
action3 :: IO Bool
有些行动只是一个行动的组成
complexAction = do
action1
action2
action3
我需要的是检查结果施工每个动作并在虚假情况下返回False。我可以手动完成,但我确实知道haskell确实有工具来摆脱那种样板。
我建议你用MaybeT
monad变压器代替。使用它有很多优点,只是返回IO Bool
:
MaybeT IO()
。MaybeT
产生的单子是MonadPlus
的实例,因此您可以使用所有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'
最简单的方法是
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
正如彼得说,任何东西,但一个狭窄的,含有的情况下,你就要去接线代码进行适当的错误从一开始就处理几乎可以肯定更好。我知道我经常后悔没有这样做,谴责自己进行一些非常乏味的重构。
如果可以的话,我想推荐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
包肯定是wide-其他许多变化也是可能的。)
嗯,我不喜欢这种方法,因为我不知道如何处理返回的错误信息。我是新来的哈斯克尔,但我认为,而不是返回它更好地把它放到记录器单子。 – kubivan
第一种方法让我有点紧张,因为它依赖于评估顺序,这意味着它依赖于'和'的实现细节。 –
@RobinGreen不,它不是 - “序列”按顺序运行所有单子动作,然后是结果。您不能通过应用纯函数来混淆“IO”操作的评估顺序;不可能。 – 2013-11-24 10:09:24
为true,所有操作都会运行,但thunk不一定全部运行。我误解了。 –