簡體   English   中英

如何在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"

基本上我想在一些檢查結果為正的情況下“做一件事”。 每當一次檢查結果為否定時,我想保留有關違規檢查的信息並中止任務。 在現實生活中,這些檢查有不同的類型,因此我打電話給他們

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

這只是例子。 隨意想到EitherT Maybe, EitherT或aa singleton列表,我提取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 ,但可讀性的提高可能是微不足道的。 在添加進一步檢查時,Version2更勝一籌。

有這種最終的優雅方式嗎?

我不喜歡的:

1)我部分地濫用了Either monad而我實際上做的更像是一個Maybe monad,其中JustNothing的卷在monadic bind中切換

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)......?

maybe eithermtl包。 通過by, eitherCheck :: Show a => Either abShow 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")

“定義輔助函數”正是我將如何處理它。 錯誤庫已經提供了許多,可能的例外是滿足Bool函數。 對於那些我只會when / unless when使用的人

當然,在可能的范圍內,您應該將您所要求的操作推廣為適當的多態,以便不需要轉換。

所以我可能首先將你的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

它定義了一些輔助功能,基本上結合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"

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM