理解Haskell';兰奇佩斯酒店
在处理GHC扩展时,我遇到了以下示例:理解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的函数,用于一些
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
“someNum 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类型最好的直觉泵之一。