RankNTypes:导致此错误的原因是什么?

时间:2012-10-16 10:33:42

标签: haskell types ghc

我一直在探索Rank2Types和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

我很难理解为什么a->a不是预期a->b的可接受类型。

2 个答案:

答案 0 :(得分:10)

对于所有类型ab,类型forall a. forall b. a -> b的函数必须能够获取类型a的值并生成类型{{1}的值}。因此,例如,它必须能够放入b并获得Int

如果您输入String,则无法从String中获得id - 您只能获得与之相同的类型。所以Int不属于id类型。事实上,如果没有类型类约束,那么就没有该类型的总函数。


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

我们的想法是使用约束来forall a. forall b. a -> b参数化,这些约束指定gxyu需要满足哪些条件以及需要vx之间以及uy之间的关系。由于我们在所有情况下都不需要所有这些约束,我们还引入了两个虚拟类型类(一个用于约束单个参数,一个用于“关系约束”),因此我们可以将它们用作不需要约束的约束(如果我们自己没有指定约束,GHC无法推断约束。

一些示例约束使这更清楚:

  • 如果我们传递v作为函数,则id必须等于xu必须等于yvxyu没有任何限制。
  • 如果我们传入v,则showx必须是yShow的实例,u必须等于{ {1}}。 vStringxu之间的关系没有任何限制。
  • 如果我们传入y,则vread . show需要成为xy的实例,而Show需要是实例u。再次没有限制他们之间的关系。
  • 如果我们有一个类型类v并且我们传递了Read,那么我们需要Convert a b where convert :: a -> bconvert,并且不会对各个参数进行约束。

所以这是实现这个的代码:

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)

使用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)

正如您所看到的,这是非常冗长且难以理解的,因为每次使用时都需要为show指定签名。如果没有这个,我认为GHC无法正确推断出约束(或者至少我不知道如何)。

答案 1 :(得分:4)

当您查看函数forall a. forall b. a -> b时,它意味着它采用任何类型的值并且可以返回任何类型的值。假设f是这样一个函数,那么你可以将f 1提供给任何函数或f "hello"给任何函数,因为f的返回类型仍然是多态的,而另一方面,一旦你给出一个值到id返回类型是固定的(它将与输入值的类型相同),因此错误。