在Haskell中提升高阶函数

时间:2012-02-11 19:11:30

标签: haskell higher-order-functions lifting

我正在尝试构造一个类型的函数:

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魔法库,但无济于事。我怎么得到它 是的,或者在某个我找不到的地方有一个现成的解决方案?

1 个答案:

答案 0 :(得分:24)

由于MonadIO类型处于否定位置,因此无法在所有IO个实例上进行此操作。 hackage上有一些库可以针对特定实例执行此操作(monad-controlmonad-peel),但是关于它们是否在语义上是合理的,尤其是关于它们如何处理异常和类似的奇怪{ {1}}事情。

编辑:有些人似乎对积极/消极的地位差异感兴趣。实际上,没有什么可说的(你可能已经听过了,但用不同的名字)。术语来自子类型世界。

子类型背后的直觉是“IOa的子类型(我会写ba <= b可以在任何地方使用a b预期1}}“。在很多情况下,确定子类型很简单;对于产品,(a1, a2) <= (b1, b2)每当a1 <= b1a2 <= b2时,这是一个非常简单的规则。但是有一些棘手的案例;例如,我们什么时候应该决定a1 -> a2 <= b1 -> b2

好吧,我们有一个函数f :: a1 -> a2和一个期望函数类型为b1 -> b2的上下文。因此,上下文将使用f的返回值,就像它是b2一样,因此我们必须要求a2 <= b2。棘手的是,上下文将为f提供b1,即使f将使用它就像a1一样。因此,我们必须要求b1 <= a1 - 从您猜测的内容向后看!我们说a2b2是“协变”,或者出现在“正面位置”,a1b1是“逆变”,或者出现在“负面立场“。

(撇开:为什么“积极”和“消极”?它的动机是乘法。考虑这两种类型:

f1 :: ((a1 -> b1) -> c1) -> (d1 -> e1)
f2 :: ((a2 -> b2) -> c2) -> (d2 -> e2)

f1的类型何时应该是f2类型的子类型?我陈述了这些事实(练习:使用上面的规则检查):

  • 我们应该e1 <= e2
  • 我们应该d2 <= d1
  • 我们应该c2 <= c1
  • 我们应该b1 <= b2
  • 我们应该a2 <= a1

e1d1 -> e1中处于积极的位置,而f1的类型也处于积极的位置;此外,e1f1整体类型中处于积极的位置(因为根据上述事实,它是协变的)。它在整个术语中的位置是其在每个子项中的位置的乘积:正*正=正。同样,d1d1 -> e1处于负位置,在整个类型中处于积极位置。负*正=负,d变量确实是逆变的。 b1a1 -> 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 - 我们需要一个更有趣的类。