Haskell-为什么我们要对单类型输出使用RankNTypes?

Haskell-为什么我们要对单类型输出使用RankNTypes?,haskell,Haskell,我尝试了以下代码: printing = print $ rank2 (+1) rank2 :: Num n => (n -> n) -> Double rank2 f = f 1.0 它抛出了错误: Couldn't match expected type ‘Double’ with actual type ‘n’ ‘n’ is a rigid type variable bound by the type signature 如果我将Double更改为Int,错误也会从

我尝试了以下代码:

printing = print $ rank2 (+1)
rank2 :: Num n => (n -> n) -> Double
rank2 f = f 1.0
它抛出了错误:

Couldn't match expected type ‘Double’ with actual type ‘n’
‘n’ is a rigid type variable bound by the type signature
如果我将
Double
更改为
Int
,错误也会从'Double'更改为'Int'

仅当我使用
RankNTypes
GHC语言扩展时,此错误才得以解决:

{-# LANGUAGE RankNTypes #-}
printing = print $ rank2 (+1)
rank2 :: (forall n. Num n => n -> n) -> Double
rank2 f = f 1.0
我已在以下网址阅读答案:

但是我不太明白为什么当我不使用元组作为输出时会出现这个错误,比如
forall n。Num n=>(n->n)->(Int,Double)
,其中元组是
(Int,Double)
。如果元组是输出,函数
f
在选择类型
Int
Double
时可能会遇到冲突。但在本例中,我使用的是单一类型的输出

这是因为函数
f
是严格多态的吗?如果是这样,这难道不意味着多态性是毫无意义的吗?该函数无法使自身适应
双精度
Int

因此您需要

rank1 :: Num n => (n -> n) -> Double
rank1 f = f 1.0
如果它进行了类型检查,则必须能够使用它,例如,如下所示:

bad :: Double
bad = rank1 (\n -> n + (sqrt (-1) :: Complex Double))
但这显然是行不通的,因为现在你把一个虚数加到一个实数上,结果应该仍然是一个实数
双数

您可以阻止这种用法,因为作为参数传递的函数必须能够用于任意数字类型,因此我不可能在混合中添加具体的
复杂双精度
,只能使用泛型
Num
操作:

allright :: Double
allright = rank2 (\n -> n + abs (-1))
所以你想要

rank1 :: Num n => (n -> n) -> Double
rank1 f = f 1.0
如果它进行了类型检查,则必须能够使用它,例如,如下所示:

bad :: Double
bad = rank1 (\n -> n + (sqrt (-1) :: Complex Double))
但这显然是行不通的,因为现在你把一个虚数加到一个实数上,结果应该仍然是一个实数
双数

您可以阻止这种用法,因为作为参数传递的函数必须能够用于任意数字类型,因此我不可能在混合中添加具体的
复杂双精度
,只能使用泛型
Num
操作:

allright :: Double
allright = rank2 (\n -> n + abs (-1))
这个定义,

rank2 :: Num n => (n -> n) -> Double
rank2 f = f 1.0
不起作用,因为Haskell中的标准rank-1多态性表示调用者可以选择类型变量的含义。使用类型为
Int->Int
的参数
f
调用该类型的函数是有效的。这不适用于该定义,因为
f
需要接受并返回一个
Double

当您将其级别提高时,您可以通过要求
f
在所有
n
上具有多态性来解决这一问题,而不是处理调用方选择的某种类型
n
。现在可以在body中的type
Double->Double
处使用
f
,并获得type检查的结果

在这种情况下,这种更高级别的类型并不是完全无用的-可以防止传入的函数在
Num
类型类之外执行任何操作。例如,它可以加法、乘法、使用文字和其他一些东西,但不能除法或使用trig函数

有时,对输入功能的这种限制非常重要,因此较高级别的类型提供了价值。是一个使用更高级别类型来防止输入值做会破坏Haskell的评估模型并导致错误程序的事情的示例

但我不确定在这种情况下你是否真的会关心这种事情。最简单的方法可能是使用排名1的类型,它可以更直接地支持您正在做的事情。

这个定义

rank2 :: Num n => (n -> n) -> Double
rank2 f = f 1.0
不起作用,因为Haskell中的标准rank-1多态性表示调用者可以选择类型变量的含义。使用类型为
Int->Int
的参数
f
调用该类型的函数是有效的。这不适用于该定义,因为
f
需要接受并返回一个
Double

当您将其级别提高时,您可以通过要求
f
在所有
n
上具有多态性来解决这一问题,而不是处理调用方选择的某种类型
n
。现在可以在body中的type
Double->Double
处使用
f
,并获得type检查的结果

在这种情况下,这种更高级别的类型并不是完全无用的-可以防止传入的函数在
Num
类型类之外执行任何操作。例如,它可以加法、乘法、使用文字和其他一些东西,但不能除法或使用trig函数

有时,对输入功能的这种限制非常重要,因此较高级别的类型提供了价值。是一个使用更高级别类型来防止输入值做会破坏Haskell的评估模型并导致错误程序的事情的示例

但我不确定在这种情况下你是否真的会关心这种事情。可能最简单的方法是使用排名1的类型,它可以更直接地支持您正在做的事情。

这:

rank1 :: Num n => (n -> n) -> Double
是这个的缩写:

rank1 :: forall n. Num n => (n -> n) -> Double
您可以使用
{-#Language ExplicitForAll}
在类型签名的顶层为all编写一个显式
。当然,
RankNTypes
也允许这样做,但这是一种排名1的类型

这是具有以下参数的多态函数类型:

  • 一种类型
    n
    ,由类型推断隐式提供或显式地由
    {-#Language TypeApplications}
    提供

  • 该类型
    n
    Num
    实例,也由编译器隐式提供;及

  • n
    n
    的函数(
    f

rank1
的调用者指定每个参数的参数值

rank1
的定义中,它可以调用
f
,但是