理解Haskell';兰奇佩斯酒店

理解Haskell';兰奇佩斯酒店,haskell,types,Haskell,Types,在处理GHC扩展时,我遇到了以下示例: 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的函数,用于一些

在处理GHC扩展时,我遇到了以下示例:

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
“some
Num n
”。有人能详细说明一下吗?你如何“读”这个以前的签名,使它听起来像它的意思?后一个签名是否与简单的
numn=>(n->n)->(Int,Double)
相同,而不需要
forall

你如何“读”这个以前的签名,使它听起来像它的意思

你可以阅读

rankN :: (forall n. Num n => n -> n) -> (Int, Double)
正如“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)

后一个签名是否与简单的
numn=>(n->n)->(Int,Double)
相同,而不需要
forall


是的,默认情况下,所有的所有都隐式放置在最外层位置(导致秩-1类型)。

在rankN案例中,
f
必须是一个多态函数,它对所有数值类型都有效

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)
也就是说,我们对Int和Double都应用
f
。如果不使用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
在正常情况下(
forall n.Num n=>(n->n)->(Int,Double)
),我们首先选择一个
n
,然后提供一个函数。所以我们可以传入一个类型为
Int->Int
Double->Double
Rational->Rational
的函数,等等

在秩2的情况下(
(forall n.Num n=>n->n)->(Int,Double)
),我们必须在知道
n
之前提供函数。这意味着该函数必须适用于任何
n
;我为上一个示例列出的示例都不起作用

对于给定的示例代码,我们需要这个函数,因为传入的函数
f
应用于两种不同的类型:一种是
Int
,另一种是
Double
。因此,它必须对他们两人都有效

第一种情况是正常的,因为默认情况下类型变量就是这样工作的。如果您对所有的都没有一个
,那么您的类型签名就相当于一开始就有了它。因此
numn=>(n->n)->(Int,Double)
for all n隐式相同。Num n=>(n->n)->(Int,Double)


适用于任何
n
的函数类型是什么?这正是所有n的
。Num n=>n->n

谢谢。在“秩N”中,秩是什么意思?秩N指的是什么?
N
指的是所有
内部函数(
->
)的嵌套深度。在本例中,它嵌套在单个函数中,因此它将是秩2。Haskell过去只支持Rank2多态性,但由于它现在支持任意秩,这更像是一个历史记录。为什么要使用rankN的情况?需要多态参数有什么好处?请将函数体想象为:
(f(1::Int),f(1.0::Double))
。您不能对此使用后一种类型的签名。在
Num n=>n->n->(Int,Double)
中,您将得到
n
必须同时是
Int
Double
。使用
(forall n.Num n=>n->n)->(Int,Double)
可以将函数
f
应用于不同的类型,因此它是一个很好的类型。五年多过去了,但我想告诉你,这个解释是RANK2类型最好的直觉泵之一。