对于所有类型a 和b,forall a. forall b. a -> b 类型的函数必须能够获取a 类型的值并产生b 类型的值。因此,例如,必须可以输入Int 并输出String。
如果输入Int,则无法从id 中获得String - 您只能返回与输入相同的类型。所以id 不是forall a. forall b. a -> b 类型。事实上,没有类型类约束就不可能有该类型的全部功能。
事实证明,你可以使用 ConstraintKinds 做一些接近(ish)你想要的事情,但它写起来既不美观也不美观:
这个想法是用约束参数化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 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:
> (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 正确推断约束(或者至少我不知道如何)。