避免繁琐的Ord实现

时间:2018-01-28 14:47:31

标签: haskell typeclass algebraic-data-types

我有一个带有相当多构造函数的数据类型,所有这些都很简单,Haskell可以自动派生Ord实例。如:

data Foo a
  = A a
  | B Int
  deriving (Eq, Ord)

现在我想添加第三个构造函数:

data Foo a
  = A a
  | B Int
  | C a (a -> Bool)

但是现在Haskell无法为我Eq手动派生OrdFoo。现在,碰巧我有一些关于如何使用C构造的两个值的特定领域知识:

instance Eq a => Eq (Foo a) where
  -- Boilerplate I don't want to write
  A x == A y = x == y
  B x == B y = x == y
  -- This is the case I really care about
  C x f == C y g = x == y && f x == g y
  _ == _ = False

instance Ord a => Ord (Foo a) where
  -- Boilerplate I don't want to write
  A x `compare` A y = x `compare` y
  A x `compare` _ = LT

  B x `compare` B y = x `compare` y
  B x `compare` A _ = GT
  B _ `compare` _ = LT

  -- This is the case I really care about
  C x f `compare` C y g
    | x == y = f x `compare` g y
    | otherwise = x `compare` y
  C{} `compare` _ = GT

但是为了做到这一点,我还必须手动实现A和B值的排序,这非常繁琐(特别是对于Ord实例)。

是否有任何技巧可以让我实现(==)并仅在C情况下进行比较,但不知何故得到"默认"其他构造函数的行为?

3 个答案:

答案 0 :(得分:5)

您可以使用特殊情况的类型参数化Foo,然后分别为newtypes实现特殊实例:

data Foo' c a
  = A a
  | B Int
  | C (c a)
  deriving (Eq, Ord)

data C a = MkC a (a -> Bool)

instance Eq a => Eq (C a) where
  MkC x f == MkC y g = x == y && f x == g y

instance Ord a => Ord (C a) where
  MkC x f `compare` MkC y g
    | x == y = f x `compare` g y
    | otherwise = x `compare` y

type Foo = Foo' C

答案 1 :(得分:3)

这个"包"函数的一个参数(称为Store comonad(..没有Eq实例))可以是你寻求的EqOrd的实例/ p>

data Store s a = Store (s -> a) s

instance (Eq s, Eq a) => Eq (Store s a) where
  (==) :: Store s a -> Store s a -> Bool
  Store f s == Store f' s' =
    (s == s')
    &&
    (f s == f' s')

instance (Ord s, Ord a) => Ord (Store s a) where
  compare :: Store s a -> Store s a -> Ordering
  Store f s `compare` Store f' s' = 
    (compare s s')
    <>
    (f s `compare` f' s')

现在,您可以导出EqOrd

data Foo a
  = A a
  | B Int
  | C (Store a Bool)
  deriving (Eq, Ord)

答案 2 :(得分:2)

由于您对这些功能有所了解,因此请考虑将有关该功能的信息存储起来,并在必要时重新解释为功能。例如,也许您所知道的是它们是部分应用的(<);那样的话:

{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ViewPatterns #-}

data Foo a
    = A a
    | B Int
    | C_ a a
    deriving (Eq, Ord, Read, Show)

pattern C a f <- C_ a ((<) -> f)

当然,如果你有一种比仅添加更复杂的表达式语言,你可能想要为AST定义一个单独的类型,以及一个用于评估AST的单独函数;但AST本身应该在大多数简单的情况下,你提出的比较规则是正确的,可以相等/可订购。