liftBase
MonadBase
是任何基本单子的概括的一部分,MonadIO
并且正如您所说,MonadBase IO
提供与MonadIO
.
然而,MonadBaseControl
是一个更复杂的野兽。在MonadBaseControl IO m
你有
liftBaseWith :: ((forall a. m a -> IO (StM m a)) -> IO a) -> m a
restoreM :: StM m a -> m a
通过查看示例最容易了解实际用途。例如,bracket
frombase
有签名
bracket :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c
只需MonadBase IO m
(或MonadIO m
),您就可以将主要bracket
调用提升到m
,但括号操作仍然需要在普通的 oldIO
中。
throw
并且catch
可能是更好的例子:
throw :: Exception e => e -> a
catch :: Exception e => IO a -> (e -> IO a) -> IO a
您可以轻松地从任何地方抛出异常,MonadIO m
并且可以从IO a
内部捕获异常,MonadIO m
但同样,正在运行的操作catch
和异常处理程序本身都必须IO a
不是m a
.
现在MonadBaseControl IO
可以编写bracket
,并且catch
允许参数操作也具有类型m a
,而不是仅限于基本 monad。上述功能(以及许多其他功能)的通用实现可以在包中找到lifted-base
。例如:
catch :: (MonadBaseControl IO m, Exception e) => m a -> (e -> m a) -> m a
bracket :: MonadBaseControl IO m => m a -> (a -> m b) -> (a -> m c) -> m c
编辑:现在我实际上正确地重新阅读了你的问题......
不,我看不出签名需要两者的任何理由MonadIO m
,MonadBaseControl IO m
因为MonadBaseControl IO m
应该暗示MonadBase IO m
它启用了完全相同的功能。所以也许它只是一些旧版本的遗留物。
查看源代码,可能只是因为runTCPClient
调用sourceSocket
和sinkSocket
内部以及那些需要MonadIO
. 我猜测包中的所有函数不简单使用MonadBase IO
的原因MonadIO
是人们更熟悉,并且大多数 monad 转换器都定义了一个实例,MonadIO m => MonadIO (SomeT m)
但用户可能必须为MonadBase IO
.