这不能普遍地完成MonadIO
实例是因为IO
键入负数位置。 hackage 上有一些库可以针对特定实例执行此操作(单子控制, 单子皮),但是关于它们在语义上是否合理存在一些争论,特别是关于它们如何处理异常和类似的奇怪问题IO
你的事情。
编辑:有些人似乎对积极/消极立场的区别感兴趣。实际上,没什么可说的(您可能已经听过它,但名称不同)。该术语来自子类型领域。
子类型化背后的直觉是“a
是一个子类型b
(我会写a <= b
)当a
可以在任何地方使用b
相反,“是预期的”。在很多情况下,决定子类型很简单;对于产品,(a1, a2) <= (b1, b2)
每当a1 <= b1
and a2 <= b2
例如,这是一个非常简单的规则。但也有一些棘手的情况;例如,我们什么时候应该决定a1 -> a2 <= b1 -> b2
?
好吧,我们有一个函数f :: a1 -> a2
和一个期望类型函数的上下文b1 -> b2
。所以上下文将使用f
的返回值就好像它是b2
,因此我们必须要求a2 <= b2
。棘手的是上下文将提供f
with a b1
, 虽然f
将像使用它一样使用它a1
。因此,我们必须要求b1 <= a1
——这与你可能猜到的相反!我们这么说a2
and b2
是“协变的”,或者出现在“正位置”,并且a1
and 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:
class Monad m => MonadIO m where
liftIO :: IO a -> m a
我们可以将其视为子类型的显式声明:我们提供了一种方法来使IO a
是一个子类型m a
对于一些混凝土m
。我们立刻就知道我们可以使用任何值IO
构造函数处于积极位置并将它们变成m
s。但仅此而已:我们没有办法变得消极IO
构造函数进入m
s——我们需要一个更有趣的类。