2012-02-11 101 views
13

我试图构造类型的函数:提升高阶函数在Haskell

liftSumthing :: ((a -> m b) -> m b) -> (a -> t m b) -> t m b 

其中t是一个单子转换。具体而言,我有兴趣这样做:

liftSumthingIO :: MonadIO m => ((a -> IO b) -> IO b) -> (a -> m b) -> m b 

我摆弄了一些哈斯克尔魔法库,但无济于事。我如何获得它 是正确的,或者有一个我找不到的现成解决方案?

+1

嘎!为什么所有的Haskell问题都这么难?这里没有简单的一点:-( – drozzy 2012-02-11 20:35:12

+3

@drozzy:这个标签实际上[每个答案的平均数最高的一个] [http://data.stackexchange.com/stackoverflow/query/61353/tags-with-highest-average因为尽管它们可能并不总是那么容易,但人们确实因为努力而获得奖励。 – hammar 2012-02-12 00:02:24

回答

18

由于IO类型处于负值,因此无法对所有MonadIO实例进行一般化处理。关于hackage,有一些库针对特定实例执行此操作(monad-control,),但是他们是否在语义上听起来有些争议,尤其是关于它们如何处理异常和类似奇怪的事情。

编辑:有些人似乎对正面/负面的立场区分感兴趣。实际上,没有什么可说的了(你可能已经听说过了,但是以不同的名字)。术语来自分类的世界。

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

那么,我们有一个功能f :: a1 -> a2和一个预期功能类型为b1 -> b2的上下文。所以上下文将使用f的返回值,就好像它是一个b2,因此我们必须要求a2 <= b2。棘手的是,上下文将提供fb1,即使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

e1处于d1 -> e1正位置,其又在的f1类型的正位置;此外,e1总体上处于f1类型的正面位置(因为它是协变的,根据上述事实)。它在整个学期中的位置是它在每个子项中的位置的结果:positive * positive = positive。类似地,d1d1 -> 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 - 我们需要一个更有趣的类。

+7

“由于”IO“类型处于负面状态“ - 你能详细说明这意味着什么以及它为什么重要吗? – 2012-02-11 21:36:40

+2

@丹伯顿我已经写了一些关于它的文章 – 2012-02-12 01:04:59

+0

这当然有帮助,我认为monad-control可以让我做足够的修补。在这种情况下,没有看到从“ma”到“tma”的上下文变化会如何破坏任何东西。由于缺少其他答案,我将其设置为接受。 – zeus 2012-02-16 21:37:36