我有一些回傳Either值的函式,我想在一個do我認為稱為“短路”行為的 IO 塊中使用它們,以便任何Left值都會導致 IO 塊上的其余專案被繞過。到目前為止,我想出的最好的看起來是這樣的(一個人為的例子,說明了我正在嘗試做的事情):
import Control.Monad.Except
main :: IO ()
main = handleErrors <=< runExceptT $ do
liftIO $ putStrLn "Starting..."
let n = 6
n' <- ExceptT . return . f1 $ n
liftIO $ putStrLn "First check complete."
n'' <- ExceptT . return . f2 $ n'
liftIO $ putStrLn "Second check complete."
liftIO $ putStrLn $ "Done: " show n''
handleErrors :: Either String () -> IO ()
handleErrors (Left err) = putStrLn $ "*** ERROR: " err
handleErrors (Right _) = return ()
f1 :: Integer -> Either String Integer
f1 n
| n < 5 = Left "Too small"
| otherwise = Right n
f2 :: Integer -> Either String Integer
f2 n
| n == 10 = Left "Don't want 10"
| otherwise = Right n
ExceptT . return看起來笨拙 - 有沒有更好的方法?我不想更改函式本身以回傳例外值。
uj5u.com熱心網友回復:
如果我正確閱讀型別,ExceptT . return則與liftEither. (它有一個稍微復雜的實作,以便與 的任何實體一起作業MonadError)
uj5u.com熱心網友回復:
如果您可以控制f1and f2,那么一種方法是概括它們:
f1 :: MonadError String m => Integer -> m Integer
f1 n | n < 5 = throwError "Too small" | otherwise = pure n
f2 :: MonadError String m => Integer -> m Integer
f2 n | n == 10 = throwError "Don't want 10" | otherwise = pure n
實際上,我什至想抽象出這種模式:
avoiding :: MonadError e m => (a -> Bool) -> e -> a -> m a
avoiding p e a = if p a then throwError e else pure a
f1 = avoiding (<5) "Too small"
f2 = avoiding (==10) "Don't want 10"
無論如何,一旦你有了更通用的型別,你就可以直接將它用作 anEither或 an ExceptT。
main = ... $ do
...
n' <- f1 n
n'' <- f2 n'
...
如果您無法控制f1and f2,那么您可能會喜歡這個errors包,它提供了許多其他方便的東西,hoistEither. (這個函式可能在其他地方也可用,但是errors當你必須呼叫其他人的函式時,該包提供了許多有用的東西來在錯誤型別之間進行轉換。)
uj5u.com熱心網友回復:
上面的答案很好,但是值得了解一個在三個非常常見的情況下解決這個問題的包 - 將 Maybe、Either 和 exceptT 提升到看起來像 exceptT 的單子中 -提升錯誤
它定義了這個看起來有點神秘的類
class Monad m => HoistError m t e e' | t -> e where
-- | Given a conversion from the error in @t a@ to @e'@, we can hoist the
-- computation into @m@.
--
-- @
-- 'hoistError' :: 'MonadError' e m -> (() -> e) -> 'Maybe' a -> m a
-- 'hoistError' :: 'MonadError' e m -> (a -> e) -> 'Either' a b -> m b
-- 'hoistError' :: 'MonadError' e m -> (a -> e) -> 'ExceptT' a m b -> m b
-- @
hoistError
:: (e -> e')
-> t a
-> m a
和幾個運算子,例如:
-- Take the error returns and wrap it in your own error type
(<%?>) :: HoistError m t e e' => t a -> (e -> e') -> m a
-- If an error occurs, return this specific error
(<?>) :: HoistError m t e e' => t a -> e' -> m a
通過類的實體,這些函式可以有以下型別:
(<%?>) :: MonadError e m => Maybe a -> (() -> e) -> m a
(<%?>) :: MonadError e m => Either a b -> (a -> e) -> m b
(<%?>) :: MonadError e m => ExceptT a m b -> (a -> e) -> ExceptT e m b
(<?>) :: MonadError e m => Maybe a -> e -> m a
(<?>) :: MonadError e m => Either a b -> e -> m b
(<?>) :: MonadError e m => ExceptT a m b -> e -> ExceptT e m b
這使得處理錯誤更簡單:
data Errors
= ValidationError String
| ValidationNonNegative
| DatabaseError DBError
| GeneralError String
storeResult :: Integer -> ExceptT DBError IO ()
storeResult n = <write to the database>
log :: MonadIO m => String -> m ()
log str = liftIO $ putStrLn str
validateNonNeg :: Integer -> Maybe Integer
validateNonNeg n | n < 0 = Nothing | otherwise = Just n
main :: IO ()
main = handleErrors <=< runExceptT $ do
liftIO $ putStrLn "Starting..."
let n = 6
n' <- f1 n <%?> ValidationError
log "First check complete."
n'' <- f2 n' <%?> ValidationError
log "Second check complete."
validateNonNeg <?> ValidationNonNegative
storeResult n'' <%?> DatabaseError
log "Stored to database"
log $ "Done: " show n''
在這個示例中,您可以看到我們現在可以將 Either、Maybe 和 exceptT 視為它們是我們自己的應用程式 monad 的原生物件。
運算子的對齊是我在使用包時一直在做的事情,因此您可以忽略它們并閱讀快樂路徑代碼。在大型應用程式中,這種風格非常有效,并且使代碼審查更好,因為您可以忽略與錯誤相關的代碼并專注于邏輯。
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/474029.html
標籤:哈斯克尔
