【问题标题】:Avoid tedious implementation of Ord避免繁琐的 Ord 实现
【发布时间】:2018-01-28 14:47:31
【问题描述】:

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

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

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

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

但现在 Haskell 不能为我手动推导出 Foo 上的 EqOrd。现在,碰巧我对如何对使用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 情况下进行比较,但以某种方式在其他构造函数上获得“默认”行为?

【问题讨论】:

  • 只需给a -> Bool 一个手动的Ord 实例(但最好先将它包装在newtype 中)。
  • 说得好!
  • 很抱歉,根据您的情况,我认为您需要一些更复杂的东西:创建一个 data X a = X a (a->Bool),使其成为 Ord 并使用 ... | C X

标签: haskell typeclass algebraic-data-types


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    函数的这个“包”及其参数(称为Store comonad(.. 没有Eq 实例))可以是您寻找的EqOrd 的实例

    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)
    

    【讨论】:

      【解决方案3】:

      由于您了解这些函数的一些特别之处,因此请考虑存储有关该特别之处的信息,并仅在必要时重新解释为函数域。例如,也许您知道它们是部分应用的(&lt;);然后是这样的:

      {-# 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 本身应该是可推导相等/可排序的。

      【讨论】:

      • 如果这个答案适用,这就是 imo 的方式
      猜你喜欢
      • 2011-02-11
      • 1970-01-01
      • 2011-03-19
      • 2016-04-14
      • 1970-01-01
      • 2015-01-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多