我是否应该更喜欢MonadUnliftIO或MonadMask来支持类似功能?

时间:2017-09-26 11:20:01

标签: haskell monad-transformers

我目前正在构建一个新的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实例)。

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

1 个答案:

答案 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状态进行限制,并且无法解除IOtimeout之类的forkIO控件操作。

我的建议:

  • 如果您希望与当今大多数人的工作方式相匹配,最好选择MonadMask,这是最常用的解决方案。
  • 如果你想要这个目标,你还需要timeoutwithMVar或其他什么,请使用MonadBaseControl
  • 如果您知道有一组特定的monad需要兼容,并希望编译时保证代码与monadic状态的正确性,请使用MonadUnliftIO