Haskell 对于类似括号的函数,我应该更喜欢Monadenliftio还是Monademask?

Haskell 对于类似括号的函数,我应该更喜欢Monadenliftio还是Monademask?,haskell,monad-transformers,Haskell,Monad Transformers,我目前正在构建一个新的API,它目前提供的功能之一是: inSpan :: Tracer -> Text -> IO a -> IO a 我希望将跟踪器移动到单子中,给我一个更像 inSpan :: MonadTracer m => Text -> m a -> m a inSpan的实现使用了括号,这意味着我有两个主要选项: class MonadUnliftIO m => MonadTracer m 或 但是我更喜欢哪一个呢?请注意,我控制着

我目前正在构建一个新的API,它目前提供的功能之一是:

inSpan :: Tracer -> Text -> IO a -> IO a
我希望将
跟踪器
移动到单子中,给我一个更像

inSpan :: MonadTracer m => Text -> m a -> m a 
inSpan
的实现使用了
括号
,这意味着我有两个主要选项:

class MonadUnliftIO m => MonadTracer m

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


<> P>我们还有什么要考虑的吗?

< P>让我们先列出选项(在过程中重复一些问题):

  • 异常
    库中的
    MonadMask
    。这可以在广泛的monad和transformer上工作,并且不需要基本monad是
    IO
  • unliftio
    (或
    unliftio
    )库中的
    MonadUnliftIO
    。此库仅适用于底部带有
    IO
    的单子,并且在某种程度上与
    ReaderT env IO
    同构
  • 来自
    monad控件库的
    MonadBaseControl
    。此库需要基本的
    IO
    ,但允许非ReaderT
现在是权衡
Monaduliftio
是最新加入的竞争对手,拥有最不发达的库支持。这意味着,除了monad可以作为实例的限制之外,许多好的实例还没有被编写出来

重要的问题是:
monaduliftio
为什么对
ReaderT
这样的东西提出这种看似武断的要求?这是为了防止失去一元状态的问题。例如,
方括号(put 1)(put 2)(put 3)
的语义并不十分清楚,因此
monadulliftio
不允许使用
StateT
实例

MonadBaseControl
放松了对
ReaderT
的限制,并具有更广泛的库支持。它在内部也被认为比其他两个更复杂,但是对于你的用法来说,这并不重要。它允许你犯上述一元状态的错误。如果你小心使用,这没关系

MonadMask
允许完全纯变压器堆栈。我认为围绕在纯堆栈中建模异步异常的有用性有一个很好的论据,但我知道这种方法有时是人们想要做的。作为获得更多实例的交换,您仍然有关于一元状态的限制,加上无法解除某些
IO
控制操作,如
timeout
forkIO

我的建议是:

  • 如果您想与当今大多数人的做事方式相匹配,最好选择
    MonadMask
    ,这是最常用的解决方案
  • 如果您想要实现该目标,但还需要使用MVAR执行
    timeout
    操作,请使用
    MonadBaseControl
  • 如果您知道有一组特定的monad需要与之兼容,并且希望在编译时保证代码相对于monadic状态的正确性,请使用
    monadnliftio
class MonadMask m => MonadTracer m