让我们血腥。我们必须量化一切,并给出量化的领域。值有类型;类型级别的事物有种类;种住在BOX
。
f1 :: forall (k :: BOX).
(forall (a :: k) (m :: k -> *). m a -> Int)
-> Int
f2 :: (forall (k :: BOX) (a :: k) (m :: k -> *). m a -> Int)
-> Int
现在,在这两个示例中,类型都没有被k
明确量化,因此 ghc 正在forall (k :: BOX)
根据是否k
提到以及提到的位置来决定将它放在哪里。我不完全确定我理解或愿意为所述政策辩护。
Ørjan 给出了一个很好的例子来说明实践中的差异。让我们也为此感到血腥。我将写出/\ (a :: k). t
明确对应的抽象forall
,以及f @ type
对应的应用程序。游戏是我们可以选择@
-ed 参数,但我们必须准备好忍受/\
魔鬼可能选择的任何 -ed 参数。
我们有
x :: forall (a :: *) (m :: * -> *). m a -> Int
并且可能因此发现那f1 x
真的是
f1 @ * (/\ (a :: *) (m :: * -> *). x @ a @ m)
但是,如果我们尝试给予f2 x
相同的处理,我们会看到
f2 (/\ (k :: BOX) (a :: k) (m :: k -> *). x @ ?m0 @ ?a0)
?m0 :: *
?a0 :: * -> *
where m a = m0 a0
Haskell 类型系统将类型应用视为纯语法,因此求解方程的唯一方法是识别函数并识别参数
(?m0 :: * -> *) = (m :: k -> *)
(?a0 :: *) = (a :: k)
但是这些方程式甚至都不是很好,因为k
它不能自由选择:它是/\
-ed而不是@
-ed。
一般来说,要掌握这些超级多态类型,最好写出所有量词,然后弄清楚它们是如何变成你对抗魔鬼的游戏的。谁选择什么,以什么顺序。在参数类型中移动 aforall
会改变它的选择器,并且通常可以决定胜负。