如何在 Haskell 中控制流程
How to do control flow in Haskell
我马上举例说明我想做什么。
version1 :: IO ()
version1 =
if boolCheck
then case maybeCheck of
Nothing -> putStrLn "Error: simple maybe failed"
Just v -> case eitherCheck of
Left e -> putStrLn $ "Error: " ++ show e
Right w -> monadicBoolCheck v >>= \case
False -> putStrLn "Error: monadic bool check failed"
True -> print "successfully doing the thing"
else putStrLn "simple bool check failed"
基本上我想"do a thing"在一些检查结果为阳性的情况下。
每当单次检查结果为负数时,我想保留有关违规检查的信息并中止任务。
在现实生活中,这些支票有不同的类型,因此我称它们为
boolCheck :: Bool
maybeCheck :: Maybe a
eitherCheck :: Show a => Either a b
monadicBoolCheck :: Monad m => m Bool
这些只是例子。
也可以随意考虑 monadic Maybe,EitherT
或我提取 head
的单例列表,当它不是单例时失败。
现在我正在尝试改进上述实现,Either
monad 进入我的脑海,因为它具有中止并显示错误消息的概念。
version2 :: IO ()
version2 = do
result <- runEitherT $ do
if boolCheck
then pure ()
else left "simple bool check failed"
v <- case maybeCheck of
Just x -> pure x
Nothing -> left "simple maybe check failed"
w <- hoistEither . mapLeft show $ eitherCheck
monadicBoolCheck v >>= \case
True -> pure ()
False -> left "monadic bool check failed"
case result of
Left msg -> putStrLn $ "Error: " ++ msg
Right _ -> print "successfully doing the thing"
虽然我更喜欢 version2
,但可读性的提高可能微不足道。
版本 2 在添加更多检查方面更胜一筹。
是否有一种最终优雅的方式来做到这一点?
我不喜欢的:
1) 我在一定程度上滥用了 Either
monad,而我实际上做的更像是一个 Maybe
monad,其中包含 Just
和 Nothing
的卷monadic 绑定
2) 将支票转换为 Either
需要相当冗长的 case
或转换函数(如 hoistEither
)。
提高可读性的方法可能是:
1) 定义辅助函数以允许像
这样的代码
v <- myMaybePairToEither "This check failed" monadicMaybePairCheck
monadicMaybePairCheck :: Monad m => m (Maybe x, y)
...
myMaybePairToEither :: String -> m (Maybe x, y) -> EitherT m e z
myMaybePairToEither _ (Just x, y) = pure $ f x y
myMaybePairToEither msg (Nothing, _) = left msg
2) 始终使用显式大小写,甚至不使用 hoistEither
3) 定义我自己的 monad 来阻止 Either
滥用......我可以提供所有的转换函数(如果没有人已经做过类似的事情)
4) 尽可能使用 maybe
和 either
5) ... ?
"Define helper functions" 正是我处理这个问题的方式。 errors library provides many already, with the possible exception of satisfying Bool
functions. For those I would just use when
/unless
.
当然,在可能的范围内,您应该将调用的操作提升为适当的多态性,这样就不需要转换。
使用 maybe
、either
和 mtl
包。顺便说一下,eitherCheck :: Show a => Either a b
的 Show a
约束可能不是您想要的:它允许调用者选择他们想要的任何类型,只要该类型实现了 Show a
。您可能打算让 a
成为一种类型,以便调用者 仅 能够调用 show
值。可能吧!
{-# LANGUAGE FlexibleContexts #-}
newtype Error = Error String
gauntlet :: MonadError Error m => m ()
gauntlet = do
unless boolCheck (throw "simple bool check failed")
_ <- maybe (throw "simple maybe check failed") pure maybeCheck
_ <- either throw pure eitherCheck
x <- monadicBoolCheck
unless x (throw "monadic bool check failed")
return ()
where
throw = throwError . Error
version2 :: IO ()
version2 =
putStrLn (case gauntlet of
Left (Error e) ->
"Error: " ++ e
Right _ ->
"successfully doing thing")
所以我可能会首先将您的 version2
改造成类似
的东西
import Control.Monad.Trans
import Control.Monad.Trans.Either hiding (left, right)
import Control.Monad
import Control.Applicative
import Control.Arrow
version3 :: IO ()
version3 = eitherT onFailure onSuccess $ do
guard boolCheck <|> fail "simple bool check failed"
v <- hoistEither $ maybe (Left "simple maybe check failed") Right maybeCheck
w <- hoistEither . left show $ eitherCheck
lift (guard =<< monadicBoolCheck v) <|> fail "monadic boolcheck failed"
where
onFailure msg = putStrLn $ "Error: "++msg
onSuccess _ = print "successfully doing the thing"
我觉得它更具可读性,但仍然有点尴尬,所以如果我做了很多
像这样的代码,我会介绍一些助手:
version4 :: IO ()
version4 = eitherT onFailure onSuccess $ do
failUnless "simple bool check failed" boolCheck
v <- hoistMaybe "simple maybe check failed" maybeCheck
w <- hoistEitherWith show eitherCheck
failUnless "monadic boolcheck failed" =<< lift (monadicBoolCheck v)
where
onFailure msg = putStrLn $ "Error: "++msg
onSuccess _ = print "successfully doing the thing"
failUnless :: Monad m => String -> Bool -> m ()
failUnless _ True = return ()
failUnless msg _ = fail msg
hoistMaybe :: Monad m => e -> Maybe a -> EitherT e m a
hoistMaybe err = hoistEither . maybe (Left err) Right
hoistEitherWith :: Monad m => (e -> e') -> Either e a -> EitherT e' m a
hoistEitherWith f = hoistEither . left f
为了在此处获得所有可能的选项,请查看此要点:
https://gist.github.com/rubenmoor/c390901247e4e7bb97cf
它定义了几个辅助函数,基本上结合了maybe
、either
等和throwError
。并产生这样的代码。
gauntlet :: MonadError Error m => m (a, b, c)
gauntlet = do
assertTrue boolCheck $ Error "simple bool check failed"
v <- assertJust maybeCheck $ Error "simple maybe check failed"
assertNothing maybeCheck' $ Error . show
w <- assertRight eitherCheck $ Error . show
b <- monadicBoolCheck
assertTrue b $ Error "monadic bool check failed"
x <- assertSingletonList list $ Error "list not singleton"
pure (v, w, x)
version3 :: IO ()
version3 = putStrLn $
case gauntlet of
Left (Error e) -> "Error: " ++ e
Right result -> "successfully doing thing with result"
我马上举例说明我想做什么。
version1 :: IO ()
version1 =
if boolCheck
then case maybeCheck of
Nothing -> putStrLn "Error: simple maybe failed"
Just v -> case eitherCheck of
Left e -> putStrLn $ "Error: " ++ show e
Right w -> monadicBoolCheck v >>= \case
False -> putStrLn "Error: monadic bool check failed"
True -> print "successfully doing the thing"
else putStrLn "simple bool check failed"
基本上我想"do a thing"在一些检查结果为阳性的情况下。 每当单次检查结果为负数时,我想保留有关违规检查的信息并中止任务。 在现实生活中,这些支票有不同的类型,因此我称它们为
boolCheck :: Bool
maybeCheck :: Maybe a
eitherCheck :: Show a => Either a b
monadicBoolCheck :: Monad m => m Bool
这些只是例子。
也可以随意考虑 monadic Maybe,EitherT
或我提取 head
的单例列表,当它不是单例时失败。
现在我正在尝试改进上述实现,Either
monad 进入我的脑海,因为它具有中止并显示错误消息的概念。
version2 :: IO ()
version2 = do
result <- runEitherT $ do
if boolCheck
then pure ()
else left "simple bool check failed"
v <- case maybeCheck of
Just x -> pure x
Nothing -> left "simple maybe check failed"
w <- hoistEither . mapLeft show $ eitherCheck
monadicBoolCheck v >>= \case
True -> pure ()
False -> left "monadic bool check failed"
case result of
Left msg -> putStrLn $ "Error: " ++ msg
Right _ -> print "successfully doing the thing"
虽然我更喜欢 version2
,但可读性的提高可能微不足道。
版本 2 在添加更多检查方面更胜一筹。
是否有一种最终优雅的方式来做到这一点?
我不喜欢的:
1) 我在一定程度上滥用了 Either
monad,而我实际上做的更像是一个 Maybe
monad,其中包含 Just
和 Nothing
的卷monadic 绑定
2) 将支票转换为 Either
需要相当冗长的 case
或转换函数(如 hoistEither
)。
提高可读性的方法可能是:
1) 定义辅助函数以允许像
这样的代码v <- myMaybePairToEither "This check failed" monadicMaybePairCheck
monadicMaybePairCheck :: Monad m => m (Maybe x, y)
...
myMaybePairToEither :: String -> m (Maybe x, y) -> EitherT m e z
myMaybePairToEither _ (Just x, y) = pure $ f x y
myMaybePairToEither msg (Nothing, _) = left msg
2) 始终使用显式大小写,甚至不使用 hoistEither
3) 定义我自己的 monad 来阻止 Either
滥用......我可以提供所有的转换函数(如果没有人已经做过类似的事情)
4) 尽可能使用 maybe
和 either
5) ... ?
"Define helper functions" 正是我处理这个问题的方式。 errors library provides many already, with the possible exception of satisfying Bool
functions. For those I would just use when
/unless
.
当然,在可能的范围内,您应该将调用的操作提升为适当的多态性,这样就不需要转换。
使用 maybe
、either
和 mtl
包。顺便说一下,eitherCheck :: Show a => Either a b
的 Show a
约束可能不是您想要的:它允许调用者选择他们想要的任何类型,只要该类型实现了 Show a
。您可能打算让 a
成为一种类型,以便调用者 仅 能够调用 show
值。可能吧!
{-# LANGUAGE FlexibleContexts #-}
newtype Error = Error String
gauntlet :: MonadError Error m => m ()
gauntlet = do
unless boolCheck (throw "simple bool check failed")
_ <- maybe (throw "simple maybe check failed") pure maybeCheck
_ <- either throw pure eitherCheck
x <- monadicBoolCheck
unless x (throw "monadic bool check failed")
return ()
where
throw = throwError . Error
version2 :: IO ()
version2 =
putStrLn (case gauntlet of
Left (Error e) ->
"Error: " ++ e
Right _ ->
"successfully doing thing")
所以我可能会首先将您的 version2
改造成类似
import Control.Monad.Trans
import Control.Monad.Trans.Either hiding (left, right)
import Control.Monad
import Control.Applicative
import Control.Arrow
version3 :: IO ()
version3 = eitherT onFailure onSuccess $ do
guard boolCheck <|> fail "simple bool check failed"
v <- hoistEither $ maybe (Left "simple maybe check failed") Right maybeCheck
w <- hoistEither . left show $ eitherCheck
lift (guard =<< monadicBoolCheck v) <|> fail "monadic boolcheck failed"
where
onFailure msg = putStrLn $ "Error: "++msg
onSuccess _ = print "successfully doing the thing"
我觉得它更具可读性,但仍然有点尴尬,所以如果我做了很多 像这样的代码,我会介绍一些助手:
version4 :: IO ()
version4 = eitherT onFailure onSuccess $ do
failUnless "simple bool check failed" boolCheck
v <- hoistMaybe "simple maybe check failed" maybeCheck
w <- hoistEitherWith show eitherCheck
failUnless "monadic boolcheck failed" =<< lift (monadicBoolCheck v)
where
onFailure msg = putStrLn $ "Error: "++msg
onSuccess _ = print "successfully doing the thing"
failUnless :: Monad m => String -> Bool -> m ()
failUnless _ True = return ()
failUnless msg _ = fail msg
hoistMaybe :: Monad m => e -> Maybe a -> EitherT e m a
hoistMaybe err = hoistEither . maybe (Left err) Right
hoistEitherWith :: Monad m => (e -> e') -> Either e a -> EitherT e' m a
hoistEitherWith f = hoistEither . left f
为了在此处获得所有可能的选项,请查看此要点:
https://gist.github.com/rubenmoor/c390901247e4e7bb97cf
它定义了几个辅助函数,基本上结合了maybe
、either
等和throwError
。并产生这样的代码。
gauntlet :: MonadError Error m => m (a, b, c)
gauntlet = do
assertTrue boolCheck $ Error "simple bool check failed"
v <- assertJust maybeCheck $ Error "simple maybe check failed"
assertNothing maybeCheck' $ Error . show
w <- assertRight eitherCheck $ Error . show
b <- monadicBoolCheck
assertTrue b $ Error "monadic bool check failed"
x <- assertSingletonList list $ Error "list not singleton"
pure (v, w, x)
version3 :: IO ()
version3 = putStrLn $
case gauntlet of
Left (Error e) -> "Error: " ++ e
Right result -> "successfully doing thing with result"