这不能在所有MonadIO 实例上通用,因为IO 类型处于负位置。有一些关于 hackage 的库可以针对特定实例执行此操作(monad-control、monad-peel),但是关于它们在语义上是否合理存在一些争论,尤其是关于它们如何处理异常和类似的奇怪 IOy 事情.
编辑:有些人似乎对正/负位置的区别感兴趣。实际上,没什么好说的(你可能已经听说过,只是用了不同的名字)。该术语来自子类型世界。
子类型背后的直觉是“a 是b 的子类型(我将写成a <= b),而a 可以在任何需要b 的地方使用”。在很多情况下,确定子类型很简单。对于产品,(a1, a2) <= (b1, b2) 每当 a1 <= b1 和 a2 <= b2 时,例如,这是一个非常简单的规则。但是有一些棘手的情况;例如,我们什么时候应该决定a1 -> a2 <= b1 -> b2?
好吧,我们有一个函数f :: a1 -> a2 和一个期望b1 -> b2 类型函数的上下文。所以上下文将使用f 的返回值,就好像它是b2,因此我们必须要求a2 <= b2。棘手的是上下文将为f 提供b1,即使f 会像使用a1 一样使用它。因此,我们必须要求 b1 <= a1 - 从您可能猜到的内容向后看!我们说a2 和b2 是“协变的”,或者出现在“正位置”,而a1 和b1 是“逆变的”,或者出现在“负位置”。
(顺便说一句:为什么是“正”和“负”?它的动机是乘法。考虑这两种类型:
f1 :: ((a1 -> b1) -> c1) -> (d1 -> e1)
f2 :: ((a2 -> b2) -> c2) -> (d2 -> e2)
什么时候f1 的类型应该是f2 的类型的子类型?我陈述了这些事实(练习:使用上面的规则检查):
- 我们应该有
e1 <= e2。
- 我们应该有
d2 <= d1。
- 我们应该有
c2 <= c1。
- 我们应该有
b1 <= b2。
- 我们应该有
a2 <= a1。
e1在d1 -> e1中处于正位,而在f1类型中又处于正位;此外,e1 总体上在f1 类型中处于正位置(因为它是协变的,根据上述事实)。它在整个术语中的位置是它在每个子术语中的位置的乘积:正 * 正 = 正。同样,d1在d1 -> e1中处于负数位置,在整个类型中处于正数位置。负 * 正 = 负,d 变量确实是逆变的。 b1在a1 -> b1类型中处于正位,在(a1 -> b1) -> c1中处于负位,在整个类型中处于负位。正 * 负 * 负 = 正,它是协变的。你明白了。)
现在,让我们看看MonadIO 类:
class Monad m => MonadIO m where
liftIO :: IO a -> m a
我们可以将其视为子类型的显式声明:我们正在提供一种方法,使IO a 成为m a 的子类型,用于某些具体的m。马上我们就知道我们可以用IO 构造函数在正位置上取任何值并将它们转换为ms。仅此而已:我们无法将否定的 IO 构造函数转换为 ms —— 我们需要一个更有趣的类。