【问题标题】:Why can't the type of id be specialised to (forall a. a -> a) -> (forall b. b -> b)?为什么 id 的类型不能专门化为 (forall a.a -> a) -> (forall b.b -> b)?
【发布时间】:2011-10-05 07:09:23
【问题描述】:

以 Haskell 中不起眼的恒等函数为例,

id :: forall a. a -> a

鉴于 Haskell 据称支持隐含多态性,我应该能够通过类型归属将 id“限制”为 (forall a. a -> a) -> (forall b. b -> b) 类型似乎是合理的。但这不起作用:

Prelude> id :: (forall a. a -> a) -> (forall b. b -> b)

<interactive>:1:1:
    Couldn't match expected type `b -> b'
                with actual type `forall a. a -> a'
    Expected type: (forall a. a -> a) -> b -> b
      Actual type: (forall a. a -> a) -> forall a. a -> a
    In the expression: id :: (forall a. a -> a) -> (forall b. b -> b)
    In an equation for `it':
        it = id :: (forall a. a -> a) -> (forall b. b -> b)

当然可以使用所需的签名定义新的受限形式的身份函数:

restrictedId :: (forall a. a -> a) -> (forall b. b -> b)
restrictedId x = x

但是用一般的id 定义它是行不通的:

restrictedId :: (forall a. a -> a) -> (forall b. b -> b)
restrictedId = id -- Similar error to above

那么这里发生了什么?似乎这可能与不可预测性的困难有关,但启用-XImpredicativeTypes 没有任何区别。

【问题讨论】:

  • 我知道这有点晚了,但是......对于它的价值,在现代 GHC 中,id :: (forall a. a -&gt; a) -&gt; (forall b. b -&gt; b) 确实会进行类型检查。使用TypeApplications,您可以使用id @(forall a. a -&gt; a)(可能)避免ImpredicativeTypes 类型检查的脆弱版本特定行为。 (不过,您仍然需要 ImpredicativeTypes 扩展。)

标签: haskell polymorphism impredicativetypes ascription


【解决方案1】:

为什么它期待(forall a. a -&gt; a) -&gt; b -&gt; b的类型

我认为forall b.(forall a. a -&gt; a) -&gt; b -&gt; b 类型等同于你给的类型。它只是它的规范表示,其中 forall 尽可能向左移动。

而且它不起作用的原因是给定类型实际上比 id :: forall c 的类型更具多态性。 c -> c,它要求参数和返回类型相等。但是你的类型中的 forall a 有效地禁止 a 与任何其他类型统一。

【讨论】:

  • 嗯,我认为(forall a. a -&gt; a) -&gt; b -&gt; b 等同于我给出的类型是对的……但是为什么编译器不能识别呢?此外,如果我更改为“规范形式”,我仍然会收到给定的错误消息。至于第二点,怎么多态?在forall a. a -&gt; a 处实例化类型变量c 会产生我给出的类型。
  • 看起来是这样,但是请注意,在以规范形式编写类型之后,b 是单态的,因此 (forall a.a -> a) 永远不能代替 (b -> b)。
  • 好吧,在这种情况下,forall b. (forall a. a -&gt; a) -&gt; b -&gt; b 似乎 等同于(forall a. a -&gt; a) -&gt; (forall b. b -&gt; b),您不同意吗?明天当我的大脑还新鲜时,我将不得不更多地考虑这个......顺便说一句,弗雷格看起来真的很酷:)
  • @pelotom - 您可能还想阅读 Simon Peyton Jones 的“任意等级类型的实用类型推断”论文,该论文链接在我的博客阅读列表中。他讨论了几种使更高等级的类型“cannonic”的方法并给出了理由。您可以使用 Frege 尝试这些东西,或者您发现编译器错误或收到另一条错误消息,并且 Frege 编译器有一个 -explain 选项,可以跟踪类型推断的一部分,这有助于更好地理解。 (还没有解释器,你必须编译一个文件)
  • 感谢您的论文参考...我会看看它是否带来启发,如果有,请接受您的回答。 +1
【解决方案2】:

forall b. (forall a. a -&gt; a) -&gt; b -&gt; b 不等同于(forall a. a -&gt; a) -&gt; (forall b. b -&gt; b),您是绝对正确的。

除非另有说明,否则类型变量在最外层进行量化。所以(a -&gt; a) -&gt; b -&gt; b(forall a. (forall b. (a -&gt; a) -&gt; b -&gt; b)) 的简写。在 System F 中,类型抽象和应用程序是明确的,这描述了一个类似f = Λa. Λb. λx:(a -&gt; a). λy:b. x y 的术语。为了让不熟悉该符号的人清楚,Λ 是一个将类型作为参数的 lambda,而 λ 将术语作为参数。

f 的调用者首先提供了一个类型参数a,然后提供了一个类型参数b,然后提供了两个值xy,它们与所选类型一致。需要注意的重要一点是调用者选择了ab。因此调用者可以执行类似f String Int length 的应用程序,例如生成一个术语String -&gt; Int

使用-XRankNTypes,您可以通过显式放置全称量词来注释术语,它不必位于最外层。类型为(forall a. a -&gt; a) -&gt; (forall b. b -&gt; b)restrictedId 术语在系统F 中可以粗略地举例为g = λx:(forall a. a -&gt; a). if (x Int 0, x Char 'd') &gt; (0, 'e') then x else id。请注意被调用者 g 如何通过首先使用类型实例化 x 来将 0'e' 应用到它。

但在这种情况下,调用者无法像之前使用f 那样选择类型参数。您会注意到 lambda 中的应用程序 x Intx Char。这会强制调用者提供一个多态函数,所以像g length 这样的术语是无效的,因为length 不适用于IntChar

另一种思考方式是将fg 的类型绘制为树。 f 的树有一个通用量词作为根,而g 的树有一个箭头作为根。为了到达f 中的箭头,调用者实例化了两个量词。使用g,它已经是箭头类型,调用者无法控制实例化。这会强制调用者提供多态参数。

最后,请原谅我做作的例子。 Gabriel Scherer 在Moderately Practical uses of System F over ML 中描述了高阶多态性的一些更实际的用途。您还可以查阅 TAPL 的第 23 章和第 30 章,或浏览编译器扩展的文档,以找到更详细或更好的高级多态性的实际示例。

【讨论】:

  • 您似乎回答的问题与我问的不同;我熟悉你解释的所有概念。我的问题是,为什么我不能用(forall a. a -&gt; a) -&gt; (forall a. a -&gt; a) 类型注释id,类型为forall a. a-&gt; a?在不可预测的系统 F 中,我当然可以证明任何具有 ∀a. a -&gt; a 类型的术语 also 都具有 (∀a. a -&gt; a) -&gt; (∀a. a -&gt; a) 类型;只需使用∀a. a -&gt; a 实例化第一种类型中的a。据说 Haskell 在一定程度上支持隐含多态性,但它似乎对这个特定的例子有问题。
  • 此外,虽然forall b. (forall a. a -&gt; a) -&gt; b -&gt; b(forall a. a -&gt; a) -&gt; (forall b. b -&gt; b) 不一样,但它们 等价的(同构)并且理想情况下应该由类型系统互换处理。请参阅@Ingo 提到的 SPJ 的论文。
  • 糟糕,我误会了。您的示例是有道理的,我的 ghci 版本(6.12.1)对此没有任何问题,但是我收到警告“-XImpredicativeTypes 已弃用:在 GHC 6.14 中将简化或删除禁止性多态性”。我怀疑这可能与您的示例有关。
  • 有趣,我使用的是 GHC 7.2.2。无论您使用-XImpredicativeTypes,还是使用任何一种方式,它在 6.12.1 中是否有任何区别?
  • 这种类型在 6.x 而不是 7.x 中检查的事实让我怀疑它可能与导致 this oddity 的同一潜在问题有关。
【解决方案3】:

我不是非预测类型方面的专家,所以这既是一个潜在的答案,也是尝试从 cmets 学习一些东西的尝试。

专业化是没有意义的

\/ a . a -> a                       (1)

(\/ a . a -> a) -> (\/ b . b -> b)  (2)

而且我不认为不可预测的类型是允许它的理由。量词通常具有使 (2) 不等价集的左侧和右侧表示的类型的效果。然而,(1) 中的a -&gt; a 暗示左侧和右侧是等价的集合。

例如您可以将 (2) 具体化为 (int -> int) -> (string -> string)。但是通过任何系统,我知道这不是由 (1) 表示的类型。

错误消息看起来像是 Haskel 类型推断器尝试统一 id 的类型的结果

\/ a . a -> a

你给的类型

\/ c . (c -> c) -> \/ d . (d -> d)

为了清楚起见,我在这里对量化变量进行了统一化。

类型推断器的工作是为acd 找到一个最一般的赋值,使两个表达式在语法上相等。最终发现需要统一cd。由于它们是单独量化的,所以它处于死胡同并退出。

您可能会问这个问题,因为基本类型推断器——带有(c -&gt; c) -&gt; (d -&gt; d) 的归属——只会向前推进并设置c == d。结果类型将是

(c -> c) -> (c -> c)

这只是

的简写
\/c . (c -> c) -> (c -> c)

这可证明是x = x 类型的最不通用类型(类型理论最小上限)表达式,其中x 被限制为具有相同域和共域的函数。

给定的“restrictedId”类型实际上过于笼统。虽然它永远不会导致运行时类型错误,但您给它的表达式描述了许多类型 - 就像前面提到的 (int -&gt; int) -&gt; (string -&gt; string) - 即使您的类型允许它们在操作上也是不可能的。

【讨论】:

  • forall a. a -&gt; a 专门化为(forall a. a -&gt; a) -&gt; (forall a. a -&gt; a) 确实有意义。您声称可以将(2)具体化为(Int -&gt; Int) -&gt; (String -&gt; String) 是您反驳的错误部分。结果可以具体化为String -&gt; String,但参数不能以这种方式具体化为Int -&gt; Int:在这种其他多态类型上实现id的人可以选择如何具体化参数,而不是打电话给另一个id的人。请注意,forall a. (a -&gt; a) -&gt; B(forall a. a -&gt; a) -&gt; B 是完全不同的类型。
猜你喜欢
  • 1970-01-01
  • 2019-11-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-12
  • 1970-01-01
相关资源
最近更新 更多