当我们说T1比T2更具多态性时,我们的意思是什么?

时间:2014-06-06 14:53:37

标签: haskell type-inference higher-rank-types

我正在用论文Practical type inference for arbitrary-rank types学习类型推理,并且我一直坚持到底。我基本上对关系更多态的概念感到困惑,因此无法继续。

在第3.3节中,它指出:

  

如果函数的类型比函数的参数类型更具多态性,则函数可以接受该参数。

根据我的理解,要说T1T2更具多态性,就是说类型为T2的任何实例都必须满足类型T1。因此,根据我的定义,forall a. aInt更具多态性。 forall a b. a -> b -> bforall a. a -> a -> a更具多态性。

据我了解,存在冲突。给出:

f :: (forall a. a) -> Int
k :: (forall a. a -> a)

(f k)显然是有效的[1]。然后根据文章中的引文,forall a. a -> a应该比forall a. a更具多态性。但是,举一个例子,文字1将满足forall a. a,而显然它不是forall a. a -> a,因此根据我的定义,forall a. a应该比{{1}更具多态性}}。这与关系更多元的文章中的描述相矛盾。

我正在寻找关于这种关系究竟是什么的例子的清晰解释。感谢。

更新

[1]:与评论d8d0d65b3f7cf42一样,注意到我forall a. a -> a(forall a. a)不兼容。应该有我理解的问题。我注意到(forall a. a -> a)可以接受类型为(forall a. a -> Int)的参数,而(forall a. a -> a)则不能。我不知道为什么这种情况就是这样。

无论我的错误理解如何,我仍然希望对多态性比关系更好的解释。谢谢:))

1 个答案:

答案 0 :(得分:8)

正如对OP的评论所指出的那样,(undefined :: (forall a . a) -> Int) ( undefined :: (forall a. a -> a) )没有进行类型检查,事实上,forall a. a -> a并不比forall a. a更具多态性,因此这里没有矛盾。

另一方面,(undefined :: (forall a. a -> Int)) (undefined :: (forall a. a -> a))类型检查,因此forall a. a -> a必须包含函数类型中的a,对吧?这可能看似违反直觉,但事实恰恰如此。让我解释一下。

包容和类型变量

包容(或者多态子类型)意味着“至少是多态的”,因此它更像是<=而不是<的类比。在更正的OP示例中,a归入forall a. a -> a,因为该上下文中的 a是一个非刚性类型变量,它可以是统一与其他类型。因此,forall a. a -> a <= typeVar b被认为是真的,同时也产生约束typeVar b = forall a. a -> a(或者可能是不同的约束,这取决于我们对类型推理系统的选择。实际步骤可能与我在下面的示例中的草图有很大不同)。

一个简单的分步示例:

-- goal 
(forall a. a -> Int) <= (Int -> Int)
-- instantiate variable on the left hand side with a fresh type variable.
(tvar a' -> Int) <= (Int -> Int)
-- check return type subsumption
Int <= Int -- OK
-- check argument type subsumption
Int <= tvar a' -- OK, add "tvar b = Int" to the set of constraints.
-- Done.

一般情况下,如果某个forall a. P <= QPQ,我们必须找到某些满足a的特定实例包容关系。这意味着我们使用灵活的类型变量实例化a,然后从那里开始。在这种情况下,我们执行特定类型的搜索,并且我们可以继续细化类型变量。

另一方面,如果我们有P <= forall a. Q,则包含右侧的{em>所有可能实例化a的包含必须保留。在这种情况下,我们通常将a实例化为刚性(或 skolem )类型变量。刚性类型变量并非真正“可变”;相反,它代表一些任意(未知)固定类型,我们无法改进它。带有刚性变量的示例:

-- goal
(forall a. a -> Int) <= (forall b. b -> Int)
-- instantiate variable on the left
(tvar a' -> Int) <= (forall b. b -> Int)
-- instantiate variable on the right
(tvar a' -> Int) <= (skolem b' -> Int)
-- check return types
Int <= Int -- OK
-- check argument types
skolem b' <= tvar a' -- OK, record the "tvar a' = skolem b'" constraint 
-- Done. 

基本上,我们只能用skolem变量做两件事:

  1. 我们可以自由地总结skolem a <= skolem a,i。即他们把自己包括在内。
  2. 我们可以用灵活的变量统一它们,i。即我们可以skolem a <= tvar btvar b <= skolem a
  3. 旋转函数类型

    我们已在示例中看到(a -> b) <= (a' -> b') iff b <= b'a' <= a<=在参数类型上被翻转。这是为什么?

    假设我们的输入上下文需要一些a -> b类型的函数。此函数(通常作为函数)使用类型a的值并生成类型b的值。有点隐喻,上下文对b - s有一个 demand ,它也能够提供 a - s到我们的函数。如果函数返回的类型比b更通用,那很好,从那以后它也可以满足上下文对b的需求。但是,如果函数需要比a更通用的类型,则上下文会受到影响。不良背景只有b - s有库存,它可以专门化它们,但不能概括它们。如果只有函数不太挑剔关于它接受的类型!

    根据行话,函数类型构造函数在参数类型中是逆变(或负),在返回类型中是协变(或正)。有关Wikipedia方差的讨论很好。它更关心OOP类层次结构的子类型而不是多态子类型,但它也应该对后者提供有用的见解。

    让我们看一下包含翻转更为突出的例子:

    -- goal 
    ((forall a. a -> a) -> Int) <= ((forall a. a -> Int) -> Int)
    -- remember: the context can supply "forall a. a -> Int"-s and demands "Int"-s. 
    -- check return types
    Int <= Int -- OK
    -- check argument types
    (forall a. a -> Int) <= (forall a. a -> a)
    -- instantiate on the left
    (tvar a' -> Int) <= (forall a. a -> a)
    -- instantiate on the right
    (tvar a' -> Int) <= (skolem a'' -> skolem a'')
    -- check return types
    Int <= skolem a'' -- FAIL: this clearly does not hold.
    -- Abort.
    

    排名-1与排名较高的设置中的函数类型差异

    我的前两个例子没有嵌套forall - s,i。即他们只有排名第一的类型。你可能已经注意到翻转的包含没有任何区别!如果我们在那里假设(a -> b) <= (a' -> b') iff b <= b' and a <= a',两个例子都会很好地检查,因为我们只需要检查类型变量,skolems和单态类型的包含。

    因此,协方差 - 逆差区分仅与更高级别的类型相关(这也在OP的引文第3.3节中暗示过)。虽然,如果我没记错的话,HMF system也忽略了函数方差自旋,尽管它是更高级别的。