在Eq实例实现中仅覆盖少量数据

时间:2018-05-15 12:12:03

标签: haskell

我有数据类型

data MyType = Any | A | B | C ...

和" Any"的语义是它应该等同于所有其他情况。我认为最优雅的方法是实现我自己的Eq实例,从

开始
instance Eq MyType where
    Any == _ = True
    _ == Any = True

但是现在我还没有找到避免重复和愚蠢的代码的方法,比如

    A == A = True
    B == B = True
    ...

我虽然"滥用" show函数,只做x == y = (show x) == (show y),但有更清洁的方法吗?

4 个答案:

答案 0 :(得分:13)

你也许可以这样做:

data Wildcard a = Any | Card a

data NewType = A | B | C | ... deriving Eq

type OldType = Wildcard NewType

instance (Eq a) => Eq (Wildcard a) where
  Any    == _      = True
  _      == Any    = True
  Card l == Card r = l == r

这样,编译器会自动为您导出Eq OldType,并且Eq NewType具有预期的语义。 (我们可以将Any应用于我们想要的任何类型......)

答案 1 :(得分:11)

您已经获得了一些解释如何执行此操作的好答案。现在我要解释为什么你绝对不应该这样做。

Haskell中的一个类不仅仅是一堆方法。它还附带法律。这些法则对于允许人们编写有意义的类多态函数至关重要。不幸的是,Prelude没有记录Eq类的任何法律。原因是人们希望使用==/=来实现浮点表示的便利性,浮点表示通常是“平等”的概念。不礼貌。我认为这是哈斯克尔委员会的一个严重错误。

那么Eq实例应遵守哪些法律?一个明显的问题涉及其方法:

a /= b = not (a == b)

其他重要的,普遍接受的法律是==描述equivalence relation所必需的法律。特别是,对于所有abc

  1. 反身法:a == a = True(见脚注)

  2. 对称法则:a == b = b == a

  3. 传递法:如果a == b && b == c = True,则a == c = True

  4. 还有(近似)替代性的一般假设。如果f是典型函数(不是一个奇怪的抽象破坏内部函数或调试函数),a == b = True,那么f a == f b = True。结合其他法则,这意味着==反映了类型所代表的基础模型的数学相等性。

    您对==的定义违反了传递性。特别是,

    A == Any && Any == B = True
    

    A == B = False
    

    这意味着如果您将类型与Eq实例上的任何函数一起使用多态,则必须阅读该函数的源代码以确定它是否会按您希望的方式运行。如果该函数的实现发生了变化,您将不得不再次阅读其源代码,以检查它是否仍然可以执行您想要的操作。在实践中,这非常糟糕。

    你如何解决这个问题?不要编写一个奇怪的Eq实例,而是编写自己的比较卡片的函数!

    data Wildcard a = Any | Card a
    
    data NonWild = A | B | C | ... deriving Eq
    
    matching :: Eq a => Wildcard a -> Wildcard a -> Bool
    matching (Card a) (Card b) = a == b
    matching _ _ = True
    

    脚注

    Haskell中的值可能是无限的或未完全定义的。因此,反身法可能不是严格正确的。例如,repeat 1 == repeat 1永远不会终止(它表示"底部"值)。因此,在实践中,适当的反身法律规定a == a不是False。一般认为==在其参数有限且定义明确时将终止。

答案 2 :(得分:6)

您还可以滥用其他类,例如Enum

data MyType = Any | A | B | C ...
  deriving Enum

instance Eq MyType where
  Any == _ = True
  _ == Any = True
  a == b = fromEnum a == fromEnum b

但是最好的'这种方法最终取决于您的实际用例。如果你只是有一些案例,我建议你手工写出来;有一个案例可以避免重复的代码,但不要过度使用,以至于你拒绝完全编写代码。

答案 3 :(得分:3)

您可以派生Enum并使用它来定义Eq类

data T = Any | A | B | C deriving (Enum, Show)

instance Eq T where
  a == b = fromEnum a == fromEnum b || fromEnum a == 0 || fromEnum b == 0