这个问题问得好!我什至写了一个实际的库FailT https://github.com/lehins/FailT解决了优雅失败的问题MonadFail
.
我不确定确切的设置是什么,但我想用一个具体的例子来回答这个问题。假设我们有一个一元动作,它接受另一个一元动作,该动作产生MyType
我们可以打赌我们的家庭农场将是Expected
case:
data MyType a = Expected a | Unexpected a
deriving (Show)
myFunc :: (Show a, MonadFail m) => m (MyType a) -> m String
myFunc t = do
Expected a <- t
pure $ show a
不幸的是没有合适的解决方案base
或其他与 GHC 连接的库,例如transformers
or mtl
例如。
就像你已经知道的那样ErrorT
本来可以完美地解决这个问题,但出于充分的理由,它被弃用了。
ExceptT
本来可以提供一个明智的MonadFail
例如,但这对于向后兼容性来说是一个很大的问题。
正如评论中所建议的那样ExceptT
with a newtype
并提供定制MonadFail
实例当然可以工作,但是谁希望每次遇到这个问题时都将其包含在代码库中。为了完整起见,这里是一个最小的独立解决方案:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import Control.Monad.Trans.Except
newtype ResultT m a = ResultT (ExceptT String m a)
deriving (Functor, Applicative, Monad)
instance Monad m => MonadFail (ResultT m) where
fail = ResultT . throwE
runResultT :: ResultT m a -> m (Either String a)
runResultT (ResultT m) = runExceptT m
当然,还可以派生出更多实例,但不是全部。Alternative
and MonadPlus
有丑陋的行为。
无论如何,这是一个足够常见的问题,需要有适当的可重用解决方案来解决。这是可以使用以下方法处理的方法FailT https://hackage.haskell.org/package/FailT-0.1.1.0/docs/Control-Monad-Trans-Fail-String.html#t:FailT单子变压器:
λ> import Control.Monad.Trans.Fail.String (runFailT)
λ> runFailT (myFunc (pure (Expected "Totally fine")))
Right "\"Totally fine\""
λ> runFailT (myFunc (pure (Unexpected "Surprise!")))
Left "Pattern match failure in 'do' block at /home/lehins/github/public/haskell/stackoverflow/errort-is-deprecated-but-exceptt-doesnt-fit/src/Lib.hs:25:3-12"
对于任何对这个主题感兴趣的人,我还写了一篇关于它的完整博客文章:上课不及格 https://alexey.kuleshevi.ch/blog/2023/01/16/fail-with-class/.