为特定的构造函数创建实例类

时间:2019-03-29 23:28:44

标签: haskell

说我有以下数据:

data A
  = B Int
  | C Float
  | D A

如果我想派生Eq,但更改输出,以便比较两个与构造函数D的项始终相等,是否有一种方法可以对所有其他构造函数都实现而不执行呢?对于其他情况,我想导出默认的Eq实现。

我想实现的目标是

instance Eq A where
  (D _) == (D _) = True
  _ == _ = undefined -- Use default eq

1 个答案:

答案 0 :(得分:2)

没有直接的方法将GHC生成的默认Eq A实例合并到您自己的Eq A实例中。问题在于,实例代码的生成与定义这些实例的过程有关-生成默认Eq A代码的唯一方法是实际生成唯一的Eq A实例,然后生成一次实例已生成,您无法真正更改它。我什至没有办法解决GHC“派生”相关的扩展。

但是,您可以使用由软件包Eq提供的默认generic-deriving实例的重新实现。带有一些序言:

{-# LANGUAGE DeriveGeneric #-}
import GHC.Generics
import Generics.Deriving.Eq  -- from package generic-deriving

使用派生的Generic实例定义数据类型:

data A
  = B Int
  | C Float
  | D A
  deriving (Generic)

然后,定义一个GEq实例,该实例为D构造函数实现您的特殊情况,同时将其余实例的默认实现推迟。

instance GEq A where
  D _ `geq` D _ = True
  x   `geq` y   = x `geqdefault` y

最后,定义一个Eq实例,并使用此泛型相等类。

instance Eq A where
  (==) = geq

在那之后,它应该都能按预期工作:

> D (B 10) == D (B 20)
True
> B 10 == B 20
False
> 

但是,在注释中采纳建议可能更合理,并且可以:

  1. 执行@malloy的建议。您尝试定义的操作实际上不是(==),所以为什么需要将其命名为(==)?只需派生通常的Eq实例并编写一个单独的函数即可避免不必要的递归:

    equalClasses :: A -> A -> Bool
    equalClasses (D _) (D _) =  True
    equalClasses x y = x == y
    
  2. 如果您真的想使用(==),我认为使用@luqui建议的newtype可能是最惯用的方法:

    data A'
      = B Int
      | C Float
      | D A'
      deriving (Eq)
    
    newtype A = A A'
    
    instance Eq A where
      A (D _) == A (D _) = True
      A x == A y = x == y