我一直在探索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
的可接受类型。
答案 0 :(得分:10)
对于所有类型a
和b
,类型forall a. forall b. a -> b
的函数必须能够获取类型a
的值并生成类型{{1}的值}。因此,例如,它必须能够放入b
并获得Int
。
如果您输入String
,则无法从String
中获得id
- 您只能获得与之相同的类型。所以Int
不属于id
类型。事实上,如果没有类型类约束,那么就没有该类型的总函数。
事实证明,你可以使用ConstraintKinds做一些与你想要的东西很接近的东西,但它既不好写也不好用:
我们的想法是使用约束来forall a. forall b. a -> b
参数化,这些约束指定g
,x
,y
和u
需要满足哪些条件以及需要v
和x
之间以及u
和y
之间的关系。由于我们在所有情况下都不需要所有这些约束,我们还引入了两个虚拟类型类(一个用于约束单个参数,一个用于“关系约束”),因此我们可以将它们用作不需要约束的约束(如果我们自己没有指定约束,GHC无法推断约束。
一些示例约束使这更清楚:
v
作为函数,则id
必须等于x
且u
必须等于y
。 v
,x
,y
或u
没有任何限制。v
,则show
和x
必须是y
和Show
的实例,u
必须等于{ {1}}。 v
和String
或x
和u
之间的关系没有任何限制。y
,则v
和read . show
需要成为x
和y
的实例,而Show
需要是实例u
。再次没有限制他们之间的关系。v
并且我们传递了Read
,那么我们需要Convert a b where 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)
使用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
返回类型是固定的(它将与输入值的类型相同),因此错误。