我目前正在构建一个新的API,它当前提供的功能之一是:
inSpan :: Tracer -> Text -> IO a -> IO a
我想将
Tracer
移至monad,使我的签名更像inSpan :: MonadTracer m => Text -> m a -> m a
inSpan
的实现使用bracket
,这意味着我有两个主要选择:class MonadUnliftIO m => MonadTracer m
要么
class MonadMask m => MonadTracer m
但是我该选哪个呢?请注意,我可以控制我提到的所有类型,这使我稍微倾向于
MonadMask
,因为它没有在底部强制执行IO
(也就是说,我们可能有一个纯MonadTracer
实例)。还有什么我应该考虑的吗?
最佳答案
让我们先列出选项(在此过程中重复一些问题):MonadMask
库中的
exceptions
。这可以在各种monad和转换器上使用,并且不需要基本monad为IO
。 MonadUnliftIO
(或unliftio-core
)库中的unliftio
。该库仅适用于以IO
为基础的单子(monad),并且在某种程度上与ReaderT env IO
同构。 MonadBaseControl
库中的monad-control
。该库的底部需要IO
,但允许使用非ReaderT。 现在进行权衡。
MonadUnliftIO
是最新版本,并且对库的支持最少。这意味着,除了单例可以成为实例的局限性之外,还没有编写许多好的实例。重要的问题是:为什么
MonadUnliftIO
对类似ReaderT
的东西提出了看似任意的要求?这是为了防止丢失单子(monad)状态的问题。例如,bracket_ (put 1) (put 2) (put 3)
的语义并不清楚,因此MonadUnliftIO
不允许StateT
实例。MonadBaseControl
放宽了ReaderT
的限制,并具有更广泛的库支持。内部它也被认为比其他两个更复杂,但是对于您的用法来说,这并不重要。并且它使您能够如上所述出错于单子(monad)状态。如果您在使用时很小心,那将无关紧要。MonadMask
允许使用完全纯净的变压器堆栈。我认为围绕纯堆栈中的异步异常进行建模的有用性值得商argument,但我知道这种方法有时是人们想要做的。为了换取更多实例,您仍然对单子(monad)状态有所限制,并且无法取消某些IO
控制动作,例如timeout
或forkIO
。我的建议:
MonadMask
,这是最常用的解决方案。 timeout
或withMVar
或其他操作,请使用MonadBaseControl
。 MonadUnliftIO
。