2017-09-26 70 views
8

我目前正在建设一个新的API,以及它目前提供的功能之一是:我应该更喜欢MonadUnliftIO还是MonadMask来支持类似的功能?

inSpan :: Tracer -> Text -> IO a -> IO a 

我期待的是Tracer进入一个单子,让我签名更像

inSpan :: MonadTracer m => Text -> m a -> m a 

inSpan实现使用bracket,这意味着我有两个主要选择:

class MonadUnliftIO m => MonadTracer m 

class MonadMask m => MonadTracer m 

但我应该更喜欢哪个?请注意,我控制了我提到的所有类型,这使我略微倾向于MonadMask,因为它不会在底部强制执行IO(也就是说,我们可能有一个纯粹的MonadTracer实例)。

还有什么我应该考虑的吗?

回答

19

让我们铺陈的选项第一(重复你的一些问题在这个过程中):

  • exceptionsMonadMask。这可以在广泛的monad和变压器上工作,并且不要求基本monad是IO
  • MonadUnliftIO来自unliftio-core(或unliftio)库。这个图书馆只适用于在其基地有IO的单子,并且它以某种方式同构于ReaderT env IO
  • MonadBaseControl来自monad-control库。这个库将需要在基地IO,但将允许非ReaderT。

现在权衡。 MonadUnliftIO是最新的加法,并有最不发达的图书馆支持。这意味着,除了monads可以作为实例的限制之外,许多好的实例还没有被写入。

重要的问题是:为什么MonadUnliftIO这个看似武断的要求围绕ReaderT这样的东西?这是为了防止丢失monadic状态的问题。例如,bracket_ (put 1) (put 2) (put 3)的语义不是很清楚,因此MonadUnliftIO不允许使用StateT实例。

MonadBaseControl放宽ReaderT限制,并有更广泛的图书馆支持。它在内部也被认为比其他两个更复杂,但对于您的使用应该不重要。如上所述,它可以让你在单子状态下犯错误。如果您在使用时非常小心,这无关紧要。

MonadMask允许完全纯变压器堆栈。我认为围绕在纯粹的堆栈中建立异步异常的有用性有一个很好的理由,但我理解这种方法有时候是人们想要做的。为了获得更多的实例,您仍然有一个单一状态的限制,再加上无法解除某些控制动作,如timeoutforkIO

我的建议:

  • 如果你想现在大多数人都在做事情的方式相匹配,这可能是最好的选择MonadMask,这是最幸福采用的解决方案。
  • 如果你想要那个目标,但是你还需要做一个timeoutwithMVar什么的,用MonadBaseControl
  • 如果您知道您需要兼容并且希望编译时间能够保证您的代码相对于一元状态的正确性,请使用MonadUnliftIO
相关问题