我目前正在构建一个新的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
实例)。
还有什么我应该考虑的吗?
答案 0 :(得分:25)
让我们首先列出选项(在此过程中重复您的一些问题):
MonadMask
库的exceptions
。这可以适用于各种monad和变换器,并且不要求基本monad为IO
。MonadUnliftIO
(或unliftio-core
)库的unliftio
。此库仅适用于基数为IO
的monad,它与ReaderT env IO
在某种程度上是同构的。MonadBaseControl
库的monad-control
。该库需要IO
在基础,但允许非ReaderT。现在进行权衡。 MonadUnliftIO
是该战斗的最新成员,并且拥有最不发达的图书馆支持。这意味着,除了monads可以作为实例的限制之外,许多好的实例还没有写好。
重要的问题是:为什么MonadUnliftIO
围绕ReaderT
提出看似随意的要求?这是为了防止失去一元状态的问题。例如,bracket_ (put 1) (put 2) (put 3)
的语义不是很清楚,因此MonadUnliftIO
不允许StateT
实例。
MonadBaseControl
放宽了ReaderT
限制,并获得了更广泛的图书馆支持。它内部也被认为比其他两个更复杂,但对于你的用法并不重要。如上所述,它允许你在monadic状态下犯错误。如果您在使用时要小心,这不重要。
MonadMask
允许完全纯粹的变压器堆栈。我认为有一个很好的论据可以解决在纯栈中建模异步异常的有用性,但我知道这种方法是人们有时想做的事情。作为获取更多实例的交换,您仍然可以围绕monadic状态进行限制,并且无法解除IO
或timeout
之类的forkIO
控件操作。
我的建议:
MonadMask
,这是最常用的解决方案。timeout
或withMVar
或其他什么,请使用MonadBaseControl
。MonadUnliftIO
。