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

och*_*les 12 haskell monad-transformers

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

inSpan :: Tracer -> Text -> IO a -> IO a
Run Code Online (Sandbox Code Playgroud)

Tracer想把它变成一个monad,给我一个更像的签名

inSpan :: MonadTracer m => Text -> m a -> m a 
Run Code Online (Sandbox Code Playgroud)

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

class MonadUnliftIO m => MonadTracer m
Run Code Online (Sandbox Code Playgroud)

要么

class MonadMask m => MonadTracer m
Run Code Online (Sandbox Code Playgroud)

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

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

Mic*_*man 27

让我们首先列出选项(在此过程中重复您的一些问题):

  • MonadMask来自exceptions图书馆.这可以适用于各种单子和变换器,并且不需要基本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控制动作,比如timeoutforkIO.

我的建议:

  • 如果你想要匹配大多数人今天做事的方式,最好选择MonadMask,这是最好的解决方案.
  • 如果你想要的目标,你也需要做timeoutwithMVar什么的,使用MonadBaseControl.
  • 如果您知道有一组特定的monad需要兼容,并且希望编译时保证代码的正确性与monadic状态一致,请使用MonadUnliftIO.