我正在用论文Practical type inference for arbitrary-rank types学习类型推理,并且我一直坚持到底。我基本上对比关系更多态的概念感到困惑,因此无法继续。
在第3.3节中,它指出:
如果函数的类型比函数的参数类型更具多态性,则函数可以接受该参数。
根据我的理解,要说T1
比T2
更具多态性,就是说类型为T2
的任何实例都必须满足类型T1
。因此,根据我的定义,forall a. a
比Int
更具多态性。 forall a b. a -> b -> b
比forall 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)
则不能。我不知道为什么这种情况就是这样。
无论我的错误理解如何,我仍然希望对多态性比关系更好的解释。谢谢:))
答案 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 <= Q
和P
有Q
,我们必须找到某些满足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变量做两件事:
skolem a <= skolem a
,i。即他们把自己包括在内。skolem a <= tvar b
或tvar b <= skolem a
。 我们已在示例中看到(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.
我的前两个例子没有嵌套forall
- s,i。即他们只有排名第一的类型。你可能已经注意到翻转的包含没有任何区别!如果我们在那里假设(a -> b) <= (a' -> b') iff b <= b' and a <= a'
,两个例子都会很好地检查,因为我们只需要检查类型变量,skolems和单态类型的包含。
因此,协方差 - 逆差区分仅与更高级别的类型相关(这也在OP的引文第3.3节中暗示过)。虽然,如果我没记错的话,HMF system也忽略了函数方差自旋,尽管它是更高级别的。