我在声明以下类型类的实例时出现问题。我试图遵循ghci编译器的错误消息中的建议,但仍然无法获取编译代码。任何帮助将不胜感激。
class TF p where
valid :: p -> Bool
lequiv :: p -> p -> Bool
instance TF Bool
where
valid = id
lequiv f g = f == g
instance TF p => TF (Bool -> p)
where
valid f = valid (f True) && valid (f False)
lequiv f g = (f True) `lequiv` (g True)
&& (f False) `lequiv` (g False)
我得到的错误是:
Illegal instance declaration for ‘TF (Bool -> p)’
(All instance types must be of the form (T a1 ... an)
where a1 ... an are *distinct type variables*,
and each type variable appears at most once in the instance head.
Use FlexibleInstances if you want to disable this.)
In the instance declaration for ‘TF (Bool -> p)’
答案 0 :(得分:6)
这里的问题是你有一个类型构造函数(->
)应用于那些不是类型变量的东西。有很多方法可以解决这个问题:
FlexibleInstances
。这放松了这个假设(在Haskell的早期阶段,当时还没有明确实现类型类的难度)。这根本不是很有争议。另一方面,它与类型推断并不能很好地发挥作用:只有当我们知道我们正在提供形状Bool -> p
的某些内容时才会选择您的实例 - 特别是第一个参数中的多态性与该形状不匹配。因此,如果没有进一步的注释,valid id
将不会进行类型检查。 TypeFamilies
。这使我们(除其他外)访问要求两个特定类型相等的约束。所以使用此扩展,您可以编写
instance (bool ~ Bool, TF p) => TF (bool -> p) where ...
现在,只要我们提供的东西具有形状bool -> p
- 即任何函数 - 并且只有在我们选择了这个实例后才会检查(事实上,强制执行)参数类型为Bool
。这意味着valid id
将进行类型检查;另一方面,它也意味着你不能为任何其他参数类型声明实例。
添加类型类。事实上,你唯一真正关心的是你可以在不太多的时间内列出Bool
的所有居民。所以你可以改为声明一个类型类,比如,Finite
,你将在这些类型中实例化,并使用 it 作为参数类型的约束。因此:
instance (Finite arg, TF p) => TF (arg -> p) where
valid f = all (valid . f) universe
lequiv f g = all (\x -> f x `lequiv` g x) universe
-- could also spell that lambda "liftA2 lequiv f g"
然后,您希望为Finite
提供Bool
个实例(幸运的是,universe
包中已经提供了这个实例)。这很好,因为它结合了前两种方法的优点:只要我们知道参数是一个函数,就会选择这个实例,并且可以通过为它们添加Finite
实例来为多个参数类型声明实例。