首页 > 解决方案 > 为特定构造函数创建实例类

问题描述

假设我有以下数据:

data A
  = B Int
  | C Float
  | D A

如果我想派生Eq,但更改输出以便将两个项目与构造函数进行比较D总是相等的,有没有办法在不为所有其他构造函数实现它的情况下这样做?对于其他情况,我想派生默认Eq实现。

我想要达到的目标是

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

标签: haskell

解决方案


没有直接的方法可以将Eq AGHC 生成的默认实例合并到您自己的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. 如果您真的想使用 a (==),我认为使用newtype@luqui 建议的 a 可能是最惯用的方法:

    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
    

推荐阅读