我正在尝试构造一个类型的函数:
liftSumthing :: ((a -> m b) -> m b) -> (a -> t m b) -> t m b
其中t
是monad变换器。具体来说,我有兴趣这样做:
liftSumthingIO :: MonadIO m => ((a -> IO b) -> IO b) -> (a -> m b) -> m b
我摆弄了一些Haskell魔法库,但无济于事。我怎么得到它 是的,或者在某个我找不到的地方有一个现成的解决方案?
答案 0 :(得分:24)
由于MonadIO
类型处于否定位置,因此无法在所有IO
个实例上进行此操作。 hackage上有一些库可以针对特定实例执行此操作(monad-control,monad-peel),但是关于它们是否在语义上是合理的,尤其是关于它们如何处理异常和类似的奇怪{ {1}}事情。
编辑:有些人似乎对积极/消极的地位差异感兴趣。实际上,没有什么可说的(你可能已经听过了,但用不同的名字)。术语来自子类型世界。
子类型背后的直觉是“IO
是a
的子类型(我会写b
)a <= b
可以在任何地方使用a
b
预期1}}“。在很多情况下,确定子类型很简单;对于产品,(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
构造函数置于正位置,并将它们转换为m
s。但就是这样:我们无法将负IO
构造函数转换为m
s - 我们需要一个更有趣的类。