我应该更喜欢 MonadUnliftIO 还是 MonadMask 来像函数一样进行包围?
Should I prefer MonadUnliftIO or MonadMask for bracketting like functions?
我目前正在构建一个新的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
实例)。
还有什么我应该考虑的吗?
让我们先列出选项(在此过程中重复您的一些问题):
MonadMask
来自 exceptions
库。这可以在范围广泛的 monad 和 transformer 上工作,并且不需要基本 monad 是 IO
.
MonadUnliftIO
来自 unliftio-core
(或 unliftio
)库。该库仅适用于基数为 IO
的单子,并且在某种程度上与 ReaderT env IO
. 同构
MonadBaseControl
来自 monad-control
库。该库将需要 IO
作为基础,但将允许非 ReaderT。
现在权衡。 MonadUnliftIO
是最新加入的,并且具有最不发达的库支持。这意味着,除了 monad 可以作为实例的限制之外,许多好的实例还没有被编写出来。
重要的问题是:为什么 MonadUnliftIO
围绕 ReaderT
之类的事情提出这个看似武断的要求?这是为了防止丢失单子状态的问题。例如,bracket_ (put 1) (put 2) (put 3)
的语义不是很清楚,因此 MonadUnliftIO
不允许 StateT
实例。
MonadBaseControl
放宽了 ReaderT
限制并具有更广泛的库支持。它在内部也被认为比其他两个更复杂,但对于您的使用来说这并不重要。如上所述,它允许您在 monadic 状态上犯错误。如果您在使用时小心,这无关紧要。
MonadMask
允许完全纯变压器组。我认为围绕在纯堆栈中建模异步异常的有用性有一个很好的论据,但我知道这种方法有时是人们想要做的。作为获得更多实例的交换,您仍然对 monadic 状态有限制,并且无法解除某些 IO
控制操作,例如 timeout
或 forkIO
.
我的推荐:
- 如果你想匹配当今大多数人做事的方式,最好选择
MonadMask
,这是最常用的解决方案。
- 如果你想要那个目标,但是你还需要做一个
timeout
或withMVar
或其他东西,使用MonadBaseControl
。
- 如果您知道有一组特定的 monad 需要与之兼容,并且希望编译时保证您的代码相对于 monadic 状态的正确性,请使用
MonadUnliftIO
。
我目前正在构建一个新的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
实例)。
还有什么我应该考虑的吗?
让我们先列出选项(在此过程中重复您的一些问题):
MonadMask
来自exceptions
库。这可以在范围广泛的 monad 和 transformer 上工作,并且不需要基本 monad 是IO
.MonadUnliftIO
来自unliftio-core
(或unliftio
)库。该库仅适用于基数为IO
的单子,并且在某种程度上与ReaderT env IO
. 同构
MonadBaseControl
来自monad-control
库。该库将需要IO
作为基础,但将允许非 ReaderT。
现在权衡。 MonadUnliftIO
是最新加入的,并且具有最不发达的库支持。这意味着,除了 monad 可以作为实例的限制之外,许多好的实例还没有被编写出来。
重要的问题是:为什么 MonadUnliftIO
围绕 ReaderT
之类的事情提出这个看似武断的要求?这是为了防止丢失单子状态的问题。例如,bracket_ (put 1) (put 2) (put 3)
的语义不是很清楚,因此 MonadUnliftIO
不允许 StateT
实例。
MonadBaseControl
放宽了 ReaderT
限制并具有更广泛的库支持。它在内部也被认为比其他两个更复杂,但对于您的使用来说这并不重要。如上所述,它允许您在 monadic 状态上犯错误。如果您在使用时小心,这无关紧要。
MonadMask
允许完全纯变压器组。我认为围绕在纯堆栈中建模异步异常的有用性有一个很好的论据,但我知道这种方法有时是人们想要做的。作为获得更多实例的交换,您仍然对 monadic 状态有限制,并且无法解除某些 IO
控制操作,例如 timeout
或 forkIO
.
我的推荐:
- 如果你想匹配当今大多数人做事的方式,最好选择
MonadMask
,这是最常用的解决方案。 - 如果你想要那个目标,但是你还需要做一个
timeout
或withMVar
或其他东西,使用MonadBaseControl
。 - 如果您知道有一组特定的 monad 需要与之兼容,并且希望编译时保证您的代码相对于 monadic 状态的正确性,请使用
MonadUnliftIO
。