【问题标题】:How to do control flow in Haskell如何在 Haskell 中进行控制流
【发布时间】:2016-01-28 00:44:23
【问题描述】:

我将举一个我想马上做的例子。

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"

基本上我想在一些检查结果为肯定的情况下“做一件事”。 每当单次检查结果为负时,我想保留有关违规检查的信息并中止任务。 在现实生活中,这些支票有不同的类型,因此我称它们为

boolCheck        :: Bool
maybeCheck       :: Maybe a
eitherCheck      :: Show a => Either a b
monadicBoolCheck :: Monad m => m Bool

这些只是示例。 也可以随意考虑单子 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,在单子绑定中切换了JustNothing

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) 尽可能使用maybeeither

5) ... ?

【问题讨论】:

    标签: haskell monads control-flow either


    【解决方案1】:

    使用maybeeithermtl 包。顺便说一句,eitherCheck :: Show a =&gt; Either a bShow 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")
    

    【讨论】:

    • 我们做到了??/接受,其他语言
    【解决方案2】:

    “定义辅助函数”正是我处理这个问题的方式。 errors 库已经提供了许多功能,但满足 Bool 功能的可能除外。对于那些我会just use when/unless

    当然,在可能的范围内,您应该将您调用的操作提升为适当的多态性,这样就不需要转换。

    【讨论】:

    • 好点。 Foor Bool 类似 `unless boolCheck $ Left "check failed" 已经更好了。
    【解决方案3】:

    所以我可能会先将您的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
    

    【讨论】:

    • 我喜欢这样,每一次检查都变成了单行,实际的控制流程是最简洁的。然而,当我向其他人展示代码(有点像那样)时,我意识到很难解释它的作用,而且这些辅助函数往往非常技术性和特殊性。如果再次达成共识,即这些辅助函数是可行的方法,我会选择它。甚至可以将它们放入类型类ToEither 并定义一个运算符以允许代码anyWeirdTypedCheck ||! "This check failed"
    • BoolEitherMaybe 以及像 (Bool, a) 这样的组合类型转换为 Eithers 的不同方式可能会破坏拥有类型类的想法
    【解决方案4】:

    为了在此处获得所有可能的选项,请查看以下要点:

    https://gist.github.com/rubenmoor/c390901247e4e7bb97cf

    它定义了几个辅助函数,基本上结合了maybeeither等与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"
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-10-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-25
      • 1970-01-01
      • 2018-03-02
      • 1970-01-01
      相关资源
      最近更新 更多