为什么id的类型不能专门用于(forall a.a - > a) - > (forall b.b - > b)?

时间:2011-10-05 07:09:23

标签: haskell polymorphism impredicativetypes ascription

在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没有任何区别。

3 个答案:

答案 0 :(得分:9)

  

为什么期望某种(forall a. a -> a) -> b -> b

我认为类型forall b.(forall a. a -> a) -> b -> b等同于您提供的类型。它只是它的规范表示,其中forall尽可能向左移动。

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

答案 1 :(得分:2)

forall b. (forall a. a -> a) -> b -> b(forall a. a -> a) -> (forall b. b -> b)不等。

,这是完全正确的

除非另有注释,否则类型变量在最外层进行量化。因此(a -> a) -> b -> b(forall a. (forall b. (a -> a) -> b -> b))的简写。在系统F中,类型抽象和应用程序是明确的,这描述了像f = Λa. Λb. λx:(a -> a). λy:b. x y这样的术语。对于不熟悉符号的人来说,Λ是一个将类型作为参数的lambda,与λ不同,它将一个术语作为参数。

f的来电者首先提供类型参数a,然后提供类型参数b,然后提供两个值xy的值选择的类型。需要注意的重要事项是来电者选择ab。因此,调用者可以执行类似f String Int length的应用程序,以生成术语String -> Int

使用-XRankNTypes您可以通过显式放置通用量词来注释术语,它不必位于最外层。类型为restrictedId的{​​{1}}字词可以在系统F中大致举例为(forall a. a -> a) -> (forall b. b -> b)。请注意g = λx:(forall a. a -> a). if (x Int 0, x Char 'd') > (0, 'e') then x else id(被调用者)如何通过首先使用类型实例化gx {/ 1}}。

但在这种情况下,调用者不能像以前那样使用0选择类型参数。您将在lambda中注意应用程序'e'f。这会强制调用者提供多态函数,因此像x Int这样的术语无效,因为x Char不适用于g lengthlength

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

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

答案 2 :(得分:-2)

我不是关于预测类型的专家,所以这既是一个潜在的答案,也是尝试从评论中学习的东西。

专门化

是没有意义的
\/ a . a -> a                       (1)

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

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

E.g。你可以将(2)具体化为(int - &gt; int) - &gt; (string - &gt; string)。但是通过任何系统,我知道这不是由(1)表示的类型。

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

\/ a . a -> a

你给的类型

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

为了清楚起见,我在这里将量化变量统一起来。

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

您可能会问这个问题,因为基本类型推理器 - 带有归属(c -> c) -> (d -> d) - 只会向前推进并设置c == d。结果类型为

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

这只是

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

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

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