无论好坏,Haskell的流行Servant库使得在涉及ExceptT err IO
的monad变换器堆栈中运行代码成为常见的。 Servant自己的处理程序monad是ExceptT ServantErr IO
。正如许多人认为的那样,这有点troublesome monad可以使用,因为有多种方式无法展开:1)通过基数IO
的正常异常,或2 )返回Left
。
作为Ed Kmett的exceptions
图书馆helpfully clarifies:
基于延续的monad和提供多种失败模式的
ErrorT e IO
等堆栈是此[MonadMask
]类的无效实例。
这非常不方便,因为MonadMask
允许我们访问有用的[多态版本] bracket
函数来进行资源管理(不会因异常而泄漏资源等)。但是在Servant的Handler
monad我们不能使用它。
我对此并不是很熟悉,但是有些人说解决方案是使用monad-control
,并且有许多合作伙伴库(如lifted-base
和lifted-async
)可以让您的monad访问资源管理工具,如bracket
(可能这适用于ExceptT err IO
和朋友?)。
然而,似乎monad-control
是losing favor in the community,但我不知道替代方案是什么。甚至Snoyman最近的safe-exceptions
库也使用Kmett的exceptions
库并避免使用monad-control
。
有人可以为像我这样试图为严肃的Haskell使用方式的人们澄清当前的故事吗?
答案 0 :(得分:6)
您可以在IO
中工作,最后返回类型为IO (Either ServantErr r)
的值,并将其包装在ExceptT
中以使其适合处理程序类型。这样您就可以在bracket
中正常使用IO
。这种方法的一个问题是您丢失了ExceptT
提供的“自动错误管理”。也就是说,如果您在处理程序中间失败,则必须在Either
上执行显式模式匹配等等。
以上基本上是重新实现MonadTransControl
的{{1}}个实例,
ExceptT
monad-control 在解除像instance MonadTransControl (ExceptT e) where
type StT (ExceptT e) a = Either e a
liftWith f = ExceptT $ liftM return $ f $ runExceptT
restoreT = ExceptT
这样的函数时工作正常,但它有奇怪的边角情况,其函数如下(取自this blog post):
bracket
如果我们传递给import Control.Monad.Trans.Control
callTwice :: IO a -> IO a
callTwice action = action >> action
callTwice' :: ExceptT () IO () -> ExceptT () IO ()
callTwice' = liftBaseOp_ callTwice
打印某事的动作并在
callTwice'
无论如何,它打印“foo”两次,即使我们的直觉说它应该在第一次执行动作失败后停止。
另一种方法是使用resourcet
库并在main :: IO ()
main = do
let printAndFail = lift (putStrLn "foo") >> throwE ()
runExceptT (callTwice' printAndFail) >>= print
monad中工作。您需要使用ExceptT ServantErr (ResourceT IO) r
等resourcet
函数代替bracket
,并在最后调整monad,如:
import Control.Monad.Trans.Resource
import Control.Monad.Trans.Except
adapt :: ExceptT ServantErr (ResourceT IO) r -> ExceptT err IO r
adapt = ExceptT . runResourceT . runExceptT
或者喜欢:
import Control.Monad.Morph
adapt' :: ExceptT err (ResourceT IO) r -> ExceptT err IO r
adapt' = hoist runResourceT
答案 1 :(得分:2)
我的建议:让您的代码存在于IO而不是ExceptT中,并将每个处理函数包装在ExceptT . try
中。