一年多以前,我问过问题How to use a proxy in Haskell,从那时起我就少量使用了RankNTypes GHC扩展。麻烦是每次我尝试使用它时,我最终都会收到奇怪的错误消息,并且会破解代码直到它们消失为止。或者我放弃了。
显然我并不真正理解Haskell中更高级别的多态性。为了解决这个问题,我决定直接找到我能做的最简单的例子,测试我所有的假设,看看我是否能让自己成为尤里卡时刻。
第一个假设 - 更高级别的多态性不是标准的Haskell 98(或2010?)功能的原因是,如果您接受许多程序员甚至不会注意到的一些不那么明显的限制,它就不是不需要。我可以定义等级1和等级2的多态函数,这些函数初看起来是等价的。如果我将它们加载到GHCi并使用相同的参数调用它们,它们将给出相同的结果。
所以 - 简单的示例函数......
{-# LANGUAGE RankNTypes #-}
rank0 :: [Int] -> Bool
rank0 x = null x
rank1a :: [a] -> Bool
rank1a x = null x
rank1b :: forall a. [a] -> Bool
rank1b x = null x
rank2 :: (forall a. [a]) -> Bool
rank2 x = null x
和GHCi会议......
GHCi, version 7.4.2: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude> :load example01
[1 of 1] Compiling Main ( example01.hs, interpreted )
Ok, modules loaded: Main.
*Main>
到目前为止没有错误 - 良好的开端。接下来,使用空列表参数测试每个函数...
*Main> rank0 []
True
*Main> rank1a []
True
*Main> rank1b []
True
*Main> rank2 []
True
*Main>
说实话,我对rank1a
和rank1b
函数在这种情况下的工作感到有些惊讶。该列表不知道它包含哪些类型的元素,函数也不知道,但肯定必须决定类型才能进行调用?我原本希望在某处提供明确的签名。
虽然这不是ATM的问题,但结果似乎很有希望。接下来,非空列表...
*Main> rank0 [1,2,3]
False
*Main> rank1a [1,2,3]
False
*Main> rank1b [1,2,3]
False
*Main> rank2 [1,2,3]
<interactive>:10:8:
No instance for (Num a)
arising from the literal `1'
In the expression: 1
In the first argument of `rank2', namely `[1, 2, 3]'
In the expression: rank2 [1, 2, 3]
*Main>
哦亲爱的 - 当参数知道更多关于它的类型时,似乎排名2版本不喜欢它。不过,也许问题只是文字1
等是多态的......
*Main> rank2 ([1,2,3] :: [Int])
<interactive>:11:8:
Couldn't match type `a' with `Int'
`a' is a rigid type variable bound by
a type expected by the context: [a] at <interactive>:11:1
Expected type: [a]
Actual type: [Int]
In the first argument of `rank2', namely `([1, 2, 3] :: [Int])'
In the expression: rank2 ([1, 2, 3] :: [Int])
*Main>
错误不同,但仍然无效,我仍然不理解这些错误消息。
弄乱各种理论,我的一个想法是,我可能需要告诉GHC“忘记”列表的某些静态类型。在那个理论上,我尝试了各种各样的事情,包括......
*Main> [1,2,3] :: [a]
<interactive>:12:2:
No instance for (Num a1)
arising from the literal `1'
In the expression: 1
In the expression: [1, 2, 3] :: [a]
In an equation for `it': it = [1, 2, 3] :: [a]
*Main>
好的,GHCi不知道我在说什么。如果GHCi只需要确切地知道要忘记哪种类型,我也试过......
*Main> ([1,2,3] :: [Int]) :: [a]
<interactive>:15:2:
Couldn't match type `a1' with `Int'
`a1' is a rigid type variable bound by
an expression type signature: [a1] at <interactive>:15:1
Expected type: [a]
Actual type: [Int]
In the expression: ([1, 2, 3] :: [Int]) :: [a]
In an equation for `it': it = ([1, 2, 3] :: [Int]) :: [a]
*Main>
我希望得到一个错误,即GHCi不知道如何show
遗忘类型的值。我不知道如何构建一个带有“遗忘”静态类型的列表,我甚至不确定它是否有意义。
此时我并没有尝试使用更高级别的多态性做任何有用的事情。这里的要点只是能够使用非空列表调用rank2
函数,并理解为什么它与其他函数的工作方式不完全相同。我想继续自己逐步搞清楚这一点,但是现在我只是完全卡住了。
答案 0 :(得分:17)
让我们考虑一下rank2
的含义。
rank2 :: (forall a. [a]) -> Bool
rank2 x = null x
rank2
的第一个参数必须是forall a. [a]
类型的东西。这里最外面的forall
意味着获得这样的值的人可以选择a
。把它想象成一个额外的论点。
因此,为了给rank2
提供一些参数,它需要是一个列表,其元素可以是任何类型,rank2
的内部实现可能想要。由于无法想象出这种任意类型的值,唯一可能的输入是[]
或包含undefined
的列表。
将此与rank1b
:
rank1b :: forall a. [a] -> Bool
rank1b x = null x
此处forall
已经位于最外层,因此使用rank1b
本身的任何人都可以选择该类型。
工作的变体将是这样的:
rank2b :: (forall a. Num a => [a]) -> Bool
rank2b x = null x
现在,您可以向其传递一个数字文字列表,这些文字对所有Num
个实例都是多态的。另一种选择是这样的:
rank2c :: (forall a. [a -> a]) -> Bool
rank2c x = null x
这是有效的,因为你确实可以想象forall a. a -> a
类型的值,特别是函数id
。
答案 1 :(得分:5)
让我们比较那些明确的forall
类型签名:
rank1b :: forall a. [a] -> Bool
rank1b x = null x
这意味着forall a.([a]->Bool)
,因此无论在什么类型上都可以正常运行,并且返回Bool
。
rank2
只能接受多态列表:rank2 :: (forall a. [a]) -> Bool
rank2 x = null x
现在这是不同的 - 这意味着参数x
本身必须是多态的。 []
是多态的,因为它可以是任何类型:
>:t []
[] :: [a]
秘密地表示forall a.[a]
,但rank2
将不接受['2']
,因为其类型为[Char]
。
rank2
只会接受真正属于forall a.[a]
类型的列表。
答案 2 :(得分:5)
此时我并没有尝试用更高等级做任何有用的事情 多态性。这里的要点就是能够调用rank2 具有非空列表的函数,并了解它为什么不起作用 与其他功能完全相同。我想继续搞清楚 我自己一步一步地走出去,但是现在我只是完全卡住了。
我不太确定更高等级的多态性是你认为的。 我认为这个概念只对函数类型有意义。
例如:
reverse :: forall a. [a] -> [a]
tail :: forall a. [a] -> [a]
告诉我们,无论list元素的类型如何,reverse
和tail
都能正常工作。
现在,鉴于此功能:
foo f = (f [1,2], f [True, False])
foo
的类型是什么?
标准HM推断无法找到类型。具体而言,它无法推断f
的类型。我们必须在这里帮助类型检查器并承诺我们只传递不关心列表元素类型的函数:
foo :: (forall a. [a] -> [a]) -> ([Int], [Bool])
现在我们可以
foo reverse
foo tail
因此具有可用的rank-2类型。 请注意,类型签名禁止传递类似:
foo (map (1+))
因为传递的函数并不完全独立于元素类型:它需要Num元素。