在通过GHC扩展工作的同时,我遇到了RankNTypes
at the School of Haskell,其中有以下示例:
main = print $ rankN (+1)
rankN :: (forall n. Num n => n -> n) -> (Int, Double)
rankN f = (f 1, f 1.0)
该文章为rankN
提供了另一种类型:
rankN :: forall n. Num n => (n -> n) -> (Int, Double)
区别的解释是"后一个签名需要从n到n的函数,用于某些Num n;前一个签名要求每个Num n都有一个从n到n的函数。"
我可以理解前一种类型需要签名是括号中的内容或更一般的内容。我不理解后一个签名只需要一个函数n -> n
来解释"某些Num n
"。有人可以详细阐述和澄清吗?你如何阅读"这个以前的签名让它听起来像是什么意思?后一个签名是否与简单Num n => (n -> n) -> (Int, Double)
相同而不需要forall
?
答案 0 :(得分:8)
在正常情况下(forall n. Num n => (n -> n) -> (Int, Double)
),我们首先选择n
,然后提供功能。所以我们可以传递Int -> Int
,Double -> Double
,Rational -> Rational
类型的函数,等等。
在Rank 2案例((forall n. Num n => n -> n) -> (Int, Double)
)中,我们必须提供之前的函数我们知道n
。这意味着该函数必须适用于任何 n
;我在上一个例子中列出的所有例子都不起作用。
我们需要为给出的示例代码提供此代码,因为传入的函数f
适用于两种不同的类型:Int
和Double
。所以它必须适用于他们两个。
第一种情况是正常的,因为默认情况下类型变量是如何工作的。如果您根本没有forall
,那么您的类型签名就相当于一开始就拥有它。 (这称为prenex表单。)因此Num n => (n -> n) -> (Int, Double)
隐含与forall n. Num n => (n -> n) -> (Int, Double)
相同。
适用于任何 n
的功能的类型是什么?它完全是forall n. Num n => n -> n
。
答案 1 :(得分:3)
在rankN
情况下,f
必须是一个多态函数,对所有数字类型n
都有效。
在rank1
案例f
中,只需要为单个数字类型定义。
以下是一些说明这一点的代码:
{-# LANGUAGE RankNTypes #-}
rankN :: (forall n. Num n => n -> n) -> (Int, Double)
rankN = undefined
rank1 :: forall n. Num n => (n -> n) -> (Int, Double)
rank1 = undefined
foo :: Int -> Int -- monomorphic
foo n = n + 1
test1 = rank1 foo -- OK
test2 = rankN foo -- does not type check
test3 = rankN (+1) -- OK since (+1) is polymorphic
<强>更新强>
在评论中回答@ helpwithhaskell的问题......
考虑这个功能:
bar :: (forall n. Num n => n -> n) -> (Int, Double) -> (Int, Double)
bar f (i,d) = (f i, f d)
也就是说,我们将f
应用于Int和Double。如果不使用RankNTypes,它就不会进行类型检查:
-- doesn't work
bar' :: ??? -> (Int, Double) -> (Int, Double)
bar' f (i,d) = (f i, f d)
以下任何签名均不适用于???:
Num n => (n -> n)
Int -> Int
Double -> Double
答案 2 :(得分:2)
你如何“阅读”这个以前的签名,这听起来像它的意思?
你可以阅读
rankN :: (forall n. Num n => n -> n) -> (Int, Double)
as“rankN采用参数f :: Num n => n -> n
”并返回(Int, Double)
,其中f :: Num n => n -> n
可以读作“对于任何数字类型n
,f
可以取n
并返回n
“。
排名第一的定义
rank1 :: forall n. Num n => (n -> n) -> (Int, Double)
将被理解为“对于任何数字类型n
,rank1
接受参数f :: n -> n
并返回(Int, Double)
”。
后一个签名是否与简单
Num n => (n -> n) -> (Int, Double)
相同而不需要forall
?
是的,默认情况下,所有forall
都隐式放在最外面的位置(导致排名为1的类型)。