Haskell 兰克恩泰普斯:是什么导致了这个错误?

Haskell 兰克恩泰普斯:是什么导致了这个错误?,haskell,types,ghc,Haskell,Types,Ghc,我只是在探索RANK2类型和RankNTypes,试图熟悉它们。但是我不明白为什么下面的方法不起作用 g :: (forall a. forall b. a -> b) -> x -> y -> (u,v) g p x y = (p x, p y) 编译器接受此定义,但尝试使用时失败: ghci> g id 1 2 <interactive>:35:3: Couldn't match type `a' with `b' `a' i

我只是在探索RANK2类型和RankNTypes,试图熟悉它们。但是我不明白为什么下面的方法不起作用

g :: (forall a. forall b. a -> b) -> x -> y -> (u,v)
g p x y = (p x, p y)
编译器接受此定义,但尝试使用时失败:

ghci> g id 1 2

<interactive>:35:3:
    Couldn't match type `a' with `b'
      `a' is a rigid type variable bound by
          a type expected by the context: a -> b at <interactive>:35:1
      `b' is a rigid type variable bound by
          a type expected by the context: a -> b at <interactive>:35:1
    Expected type: a -> b
      Actual type: a -> a
    In the first argument of `g', namely `id'
    In the expression: g id 1 2
ghci>gid12
:35:3:
无法将类型“a”与“b”匹配
`a'是一个刚性类型变量,由
上下文所需的类型:a->b at:35:1
`b'是一个刚性类型变量,由
上下文所需的类型:a->b at:35:1
预期类型:a->b
实际类型:a->a
在'g'的第一个参数中,即'id'
在表达式中:gid12

我很难理解为什么
a->a
对于所有类型
a
b
对于所有a的
a
b
类型的函数来说都是不可接受的类型。福尔b。a->b必须能够获取
a
类型的值并生成
b
类型的值。因此,例如,必须能够输入一个
Int
,然后输出一个
字符串

如果输入
Int
,则无法从
id
中获取
字符串
——您只能返回输入的相同类型。因此,
id
不是a的类型。福尔b。a->b
。事实上,如果没有类型类约束,就不可能有该类型的全部函数


事实证明,你可以使用ConstraintKinds做一些接近你想要做的事情,但它既不好写,也不好用:

其思想是使用约束来参数化
g
,这些约束指定
x
y
u
v
需要满足哪些条件,以及
x
u
之间以及
y
v
之间需要什么关系。因为我们在所有情况下都不需要所有这些约束,所以我们还引入了两个伪类型类(一个用于单个参数的约束,一个用于“关系约束”),这样我们就可以在不需要约束的情况下使用它们作为约束(如果我们不自己指定约束,GHC无法推断约束)

一些示例约束使这一点更加明确:

  • 如果我们将
    id
    作为函数传入,
    x
    必须等于
    u
    y
    必须等于
    v
    。对
    x
    y
    u
    v
    分别没有任何约束
  • 如果我们传入
    show
    x
    y
    必须是
    show
    u
    的实例,
    v
    必须等于
    String
    。对
    x
    u
    y
    v
    之间的关系没有任何约束
  • 如果我们传入
    read.show
    x
    y
    需要是
    show
    的实例,
    u
    v
    需要是
    read
    的实例。同样,它们之间的关系没有约束
  • 如果我们有一个类型类
    Convert a b,其中Convert::a->b
    并传入
    Convert
    ,那么我们需要
    Convert x u
    Convert y v
    ,并且对单个参数没有约束
下面是实现此功能的代码:

{-# LANGUAGE Rank2Types, ConstraintKinds, FlexibleInstances, MultiParamTypeClasses #-}

class Dummy a
instance Dummy a

class Dummy2 a b
instance Dummy2 a b

g :: forall c. forall d. forall e. forall x. forall y. forall u. forall v.
     (c x, c y, d u, d v, e x u, e y v) =>
     (forall a. forall b. (c a, d b, e a b) => a -> b) -> x -> y -> (u,v)
g p x y = (p x, p y)
下面是如何使用它:

ghci> g id 1 2

<interactive>:35:3:
    Couldn't match type `a' with `b'
      `a' is a rigid type variable bound by
          a type expected by the context: a -> b at <interactive>:35:1
      `b' is a rigid type variable bound by
          a type expected by the context: a -> b at <interactive>:35:1
    Expected type: a -> b
      Actual type: a -> a
    In the first argument of `g', namely `id'
    In the expression: g id 1 2
使用
show.read
在不同类型的数字之间转换:

> (g :: (Show x, Show y, Read u, Read v, Dummy2 x u, Dummy2 y v) => (forall a. forall b. (Show a, Read b, Dummy2 a b) => a -> b) -> x -> y -> (u,v)) (read . show) 1 2 :: (Double, Int)
(1.0,2)
使用
id

> (g :: (Dummy x, Dummy y, x~u, y~v) => (forall a. forall b. (Dummy a, Dummy b, a~b) => a -> b) -> x -> y -> (u,v)) id 1 2.0
(1,2.0)
使用
显示

> (g :: (Show x, Show y, String~u, String~v, Dummy2 x u, Dummy2 x y) => (forall a. forall b. (Show a, String~b, Dummy2 a b) => a -> b) -> x -> y -> (u,v)) show 1 2.0
("1","2.0")

如您所见,这非常冗长且不可读,因为每次使用时都需要为
g
指定一个签名。没有这一点,我认为GHC不可能正确推断约束(或者至少我不知道如何).

当你看一个函数时,
用于所有a.用于所有b.a->b
意味着它接受任何类型的值,并且可以返回任何类型的值。假设
f
是这样一个函数,那么你可以将say
f1
馈送给任何函数或
f“hello”“
到任何函数,因为f的返回类型仍然是多态的,而另一方面,一旦您给
id
一个值,返回类型是固定的(它将与输入值的类型相同),因此会出现错误

对于所有
a
b
,您需要类型为
a->b
的函数。因为
id
是所有a的
类型。a->a
(即,如果
a~b
,它只满足第一个签名)它不是
g
的有效输入。那么,如何表示
p
的类型可以采用任何类型并返回任何类型?也就是说,它可以是
id
show
(<10)
等。给定函数的实现,它的类型应该是什么?@PeterHall你不能。另外,无论哪种方式,您都无法传入
show
,因为
x
y
不是
show
的实例。啊,我现在已经理解了其中的一部分。如果我需要参数是
Int
Bool
,但结果是
(Int,String)
,那么没有这样的函数可以满足这一点。编译器无法推断出这一事实,但它将拒绝我尝试的所有内容。@PeterHall没有完全可以满足这一点的函数。像
fx=fx
(或者干脆
未定义的
)这样的东西可以很容易地满足这一点。如果添加一些类型类约束,甚至可以得到满足它的全部函数,例如
read。如果
x
y
show
的实例,则显示
;如果
u
v
Read
的实例,则显示
。使用ConstraintKinds,您甚至可以使其具有某种通用性/实用性。不过,你仍然无法让它实现你的想法。@PeterHall发现,有了KindConstraints,b,你可以做你想做的事情