在像 ExceptT a IO 这样的 monad 堆栈中管理资源的最佳方法是什么?
What is the best way to manage resources in a monad stack like ExceptT a IO?
无论好坏,Haskell 的流行 Servant library has made it common-place to run code in a monad transformer stack involving ExceptT err IO
. Servant's own handler monad is ExceptT ServantErr IO
. As many argue, this is a somewhat troublesome monad 工作因为有 多种方法无法展开 :1) 通过来自基础 IO
的正常异常,或 2) 通过返回 Left
.
作为 Ed Kmett 的 exceptions
library helpfully clarifies:
Continuation-based monads, and stacks such as ErrorT e IO
which provide for multiple failure modes, are invalid instances of this [MonadMask
] class.
这非常不方便,因为 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 使用的人澄清当前的故事吗?
您可以在 IO
、return 中处理最后一个 IO (Either ServantErr r)
类型的值,然后将其包装在 ExceptT
中以使其适合处理程序类型。这将使您可以在 IO
中正常使用 bracket
。这种方法的一个问题是您丢失了 ExceptT
提供的 "automatic error management"。也就是说,如果你在处理程序中间失败,你将不得不在 Either
和类似的事情上执行显式模式匹配。
上面基本上是为ExceptT
重新实现了MonadTransControl
实例,也就是
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
这样的提升函数时工作正常,但它有奇怪的极端情况,具有如下函数(取自 this blog post) :
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
library and work in a ExceptT ServantErr (ResourceT IO) r
monad. You would need to use resourcet
functions like 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
我的建议:让你的代码存在于 IO 而不是 ExceptT 中,并将每个处理函数包装在 ExceptT . try
.
中
无论好坏,Haskell 的流行 Servant library has made it common-place to run code in a monad transformer stack involving ExceptT err IO
. Servant's own handler monad is ExceptT ServantErr IO
. As many argue, this is a somewhat troublesome monad 工作因为有 多种方法无法展开 :1) 通过来自基础 IO
的正常异常,或 2) 通过返回 Left
.
作为 Ed Kmett 的 exceptions
library helpfully clarifies:
Continuation-based monads, and stacks such as
ErrorT e IO
which provide for multiple failure modes, are invalid instances of this [MonadMask
] class.
这非常不方便,因为 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 使用的人澄清当前的故事吗?
您可以在 IO
、return 中处理最后一个 IO (Either ServantErr r)
类型的值,然后将其包装在 ExceptT
中以使其适合处理程序类型。这将使您可以在 IO
中正常使用 bracket
。这种方法的一个问题是您丢失了 ExceptT
提供的 "automatic error management"。也就是说,如果你在处理程序中间失败,你将不得不在 Either
和类似的事情上执行显式模式匹配。
上面基本上是为ExceptT
重新实现了MonadTransControl
实例,也就是
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
这样的提升函数时工作正常,但它有奇怪的极端情况,具有如下函数(取自 this blog post) :
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
library and work in a ExceptT ServantErr (ResourceT IO) r
monad. You would need to use resourcet
functions like 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
我的建议:让你的代码存在于 IO 而不是 ExceptT 中,并将每个处理函数包装在 ExceptT . try
.