在像MonceptT和IO这样的monad堆栈中管理资源的最佳方法是什么?

时间:2016-11-02 03:35:46

标签: haskell exception-handling resources monad-transformers io-monad

无论好坏,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-baselifted-async)可以让您的monad访问资源管理工具,如bracket(可能这适用于ExceptT err IO和朋友?)。

然而,似乎monad-controllosing favor in the community,但我不知道替代方案是什么。甚至Snoyman最近的safe-exceptions库也使用Kmett的exceptions库并避免使用monad-control

有人可以为像我这样试图为严肃的Haskell使用方式的人们澄清当前的故事吗?

2 个答案:

答案 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) rresourcet函数代替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中。