类型类TF的非法实例声明

时间:2014-05-28 11:57:04

标签: haskell

我在声明以下类型类的实例时出现问题。我试图遵循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)’

1 个答案:

答案 0 :(得分:6)

这里的问题是你有一个类型构造函数(->)应用于那些不是类型变量的东西。有很多方法可以解决这个问题:

  1. FlexibleInstances。这放松了这个假设(在Haskell的早期阶段,当时还没有明确实现类型类的难度)。这根本不是很有争议。另一方面,它与类型推断并不能很好地发挥作用:只有当我们知道我们正在提供形状Bool -> p的某些内容时才会选择您的实例 - 特别是第一个参数中的多态性与该形状不匹配。因此,如果没有进一步的注释,valid id将不会进行类型检查。
  2. TypeFamilies。这使我们(除其他外)访问要求两个特定类型相等的约束。所以使用此扩展,您可以编写

    instance (bool ~ Bool, TF p) => TF (bool -> p) where ...
    

    现在,只要我们提供的东西具有形状bool -> p - 即任何函数 - 并且只有在我们选择了这个实例后才会检查(事实上,强制执行)参数类型为Bool。这意味着valid id将进行类型检查;另一方面,它也意味着你不能为任何其他参数类型声明实例。

  3. 添加类型类。事实上,你唯一真正关心的是你可以在不太多的时间内列出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实例来为多个参数类型声明实例。