【问题标题】:Lifting a higher order function in Haskell在 Haskell 中提升高阶函数
【发布时间】:2012-02-12 09:04:09
【问题描述】:

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

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

我摆弄了一些 Haskell 魔法库,但无济于事。我如何得到它 对,或者也许有一个我没有找到的现成解决方案?

【问题讨论】:

  • 啊!为什么所有 Haskell 问题都这么难?这里没有简单的点:-(
  • @drozzy:这个标签实际上是has one of the highest average number upvotes per answer,所以虽然他们可能并不总是那么容易,但人们确实会因为他们的努力而得到回报。

标签: haskell higher-order-functions lifting


【解决方案1】:

这不能在所有MonadIO 实例上通用,因为IO 类型处于负位置。有一些关于 hackage 的库可以针对特定实例执行此操作(monad-controlmonad-peel),但是关于它们在语义上是否合理存在一些争论,尤其是关于它们如何处理异常和类似的奇怪 IOy 事情.

编辑:有些人似乎对正/负位置的区别感兴趣。实际上,没什么好说的(你可能已经听说过,只是用了不同的名字)。该术语来自子类型世界。

子类型背后的直觉是“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。棘手的是上下文将为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类型中又处于正位;此外,e1 总体上在f1 类型中处于正位置(因为它是协变的,根据上述事实)。它在整个术语中的位置是它在每个子术语中的位置的乘积:正 * 正 = 正。同样,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 构造函数在正位置上取任何值并将它们转换为ms。仅此而已:我们无法将否定的 IO 构造函数转换为 ms —— 我们需要一个更有趣的类。

【讨论】:

  • “因为 IO 类型处于否定位置” - 你能详细说明这意味着什么以及为什么它很重要吗?
  • @DanBurton 我已经写了一些。
  • 这当然很有帮助。我认为 monad-control 可能允许我通过足够的修补来做到这一点。在这种情况下,我看不出从m at m a 的上下文变化会如何破坏任何东西。由于缺乏任何其他答案,我将其设置为已接受。
  • 快速提问:为什么liftSumthing f = (>>=) . liftIO . f $ return 无效?毕竟Monad m => (a -> m b) -> m b 几乎等同于Monad m => m a 使用双射(>>=) :: Monad m => m a -> (forall b. (a -> m b) -> m b)\f -> f return :: Monad m => (forall b. (a -> m b) -> m b) -> m a。我猜这与那里的forall 有关。
  • @M.Aroosi 确实,你对你的 foralls 不小心。 forall a b. ((a -> IO b) -> IO b) -> ((a -> m b) -> m b)forall a. (forall b. (a -> IO b) -> IO b) -> (forall b. (a -> m b) -> m b) 之间有很大的不同。特别是:在前者(我们正在尝试编写的那个)中,调用者可以选择b,并且可以选择它与a 不同,在这种情况下return 不是正确的参数(a -> IO b) -> IO b 函数。
猜你喜欢
  • 2017-04-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-13
  • 2012-10-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多