“MonadIO m”和“MonadBaseControl IO m”有什么区别吗?

时间:2014-01-05 07:42:45

标签: haskell monads conduit

来自 network-conduit 的函数runTCPClient具有以下签名:

runTCPClient :: (MonadIO m, MonadBaseControl IO m)
             => ClientSettings m -> Application m -> m ()

MonadIO m提供了

liftIO :: IO a -> m a

MonadBaseControl IO m提供

liftBase :: IO a -> m a

没有明显的区别。它们提供相同的功能吗?如果是,为什么类型签名中的重复?如果没有,那有什么区别?

1 个答案:

答案 0 :(得分:12)

liftBaseMonadBase的一部分,它是MonadIO对任何基础monad的推广,正如您所说,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

通过查看示例,最容易看到实际用途。例如,base中的bracket具有签名

bracket ::  IO a -> (a -> IO b) -> (a -> IO c) -> IO c

只需MonadBase IO m(或MonadIO m)即可将主bracket次调用解除为m,但包围操作仍需要保持原状{{1} }。

IOthrow可能是更好的例子:

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类型的方式编写bracketcatch,而不是限制为基数单子。可以在包lifted-base中找到上述函数(以及许多其他函数)的通用实现。例如:

m a

编辑:现在我确实正确地重新阅读了您的问题......

不,我认为签名不需要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在内部调用了runTCPClientsourceSocket,而这些需要sinkSocket。我猜测包中所有函数不仅仅使用MonadIO的原因是人们更熟悉MonadBase IO,大多数monad变换器都为MonadIO定义了一个实例但是用户可能必须为MonadIO m => MonadIO (SomeT m)编写自己的实例。