16

不管是好是坏,Haskell 流行的Servant库使得在涉及ExceptT err IO. Servant 自己的处理程序 monad 是ExceptT ServantErr IO. 正如许多人所说,这是一个有点麻烦的 monad,因为有多种方式无法展开:1)通过IO基础的正常异常,或 2)通过返回Left

正如 Ed Kmett 的exceptions图书馆所帮助澄清的那样:

基于延续的 monad 和ErrorT e IO提供多种故障模式的堆栈是此类 [ MonadMask] 的无效实例。

这非常不方便,因为MonadMask让我们可以访问有用的 [多态版本]bracket函数来进行资源管理(不会由于异常等而泄漏资源)。但是在Servant的Handlermonad中我们不能使用它。

我对它不是很熟悉,但有些人说解决方案是使用monad-control它,它是许多合作伙伴库喜欢的lifted-base,并且lifted-async可以让你的 monad 访问资源管理工具,比如bracket(大概这也适用于ExceptT err IO和朋友?)。

但是,它似乎在社区monad-control失去了青睐,但我无法确定替代方案是什么。甚至 Snoyman 最近safe-exceptions的库也使用了 Kmett 的exceptions库并避免了monad-control.

有人可以为像我这样试图认真使用 Haskell 的人澄清当前的故事吗?

4

2 回答 2

7

您可以在 中工作IO,在最后返回一个类型的值IO (Either ServantErr r)并将其包装ExceptT以使其适合处理程序类型。这会让你bracketIO. 这种方法的一个问题是您失去了ExceptT提供的“自动错误管理”。也就是说,如果您在处理程序的中间失败,您将不得不对Either类似的东西执行显式模式匹配。


以上基本上是重新实现的MonadTransControl实例ExceptT,即

instance MonadTransControl (ExceptT e) where
    type StT (ExceptT e) a = Either e a
    liftWith f = ExceptT $ liftM return $ f $ runExceptT
    restoreT = ExceptT

monad-control在提升诸如 之类的函数时工作正常bracket,但它有一些奇怪的极端情况,具有以下函数(取自此博客文章):

import Control.Monad.Trans.Control

callTwice :: IO a -> IO a
callTwice action = action >> action

callTwice' :: ExceptT () IO () -> ExceptT () IO ()
callTwice' = liftBaseOp_ callTwice

如果我们传递给callTwice'打印某些内容并在之后立即失败的操作

main :: IO ()
main = do
    let printAndFail = lift (putStrLn "foo") >> throwE ()
    runExceptT (callTwice' printAndFail) >>= print  

无论如何,它会打印两次“foo”,即使我们的直觉说它应该在第一次执行操作失败后停止。


另一种方法是使用resourcet库并在 ExceptT ServantErr (ResourceT IO) rmonad 中工作。您将需要使用resourcet类似allocate而不是 的函数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
于 2016-11-02T07:30:19.707 回答
3

我的建议:让你的代码在 IO 中而不是在 exceptT 中,并将每个处理函数包装在ExceptT . try.

于 2016-11-10T03:32:44.227 回答