【问题标题】:Inconsistent results for Data.Set and custom Ord instanceData.Set 和自定义 Ord 实例的结果不一致
【发布时间】:2018-12-31 04:33:24
【问题描述】:

这是我的数据结构

data Ex =
  P String
  | (:←) Ex

它具有p == ←←p 的属性。我的自定义 Eq 和 Ord 实例尝试定义相同的。但是,我看到 test3(从 [p,←←p, ←p] 创建的集合)和 test4(从 [p, ←p, ←←p] 创建的集合)的结果不一致。结果如下图:

*Test> test3
fromList [←q,←←q]
*Test> test4
fromList [q,←q,←←q]

请注意,test3 和 test4 仅在创建集合的元素顺序上有所不同。然而,结果却不同。

我认为使用 Data.Set.fromList 创建集合的顺序并不重要。有人可以帮我找出我的 Eq 或 Ord 实例的错误吗?完整代码如下,使用 GHC 8.4.3 编译。

module Test where    
import Data.Set as S


data Ex =
  P String
  | (:←) Ex

instance Show Ex where
  show (P s) = s
  show ((:←) e) = "←" ++ (show e)

instance Eq Ex where
  (P s1) == (P s2) = s1 == s2
  (:←) e1 == (:←) e2
    | e1 == e2 = True
    | otherwise = False
  e1 == (:←) e2
    | e1 == e2 = False
    | (:←) e1 == e2 = True
    | otherwise = False
  (:←) e1 == e2
    | e1 == e2 = False
    | e1 == (:←) e2 = True
    | otherwise = False

elength :: Ex   -> Int
elength (P s) = length s
elength ((:←) e) = elength e + 1

instance Ord Ex where
  compare e1 e2
    | e1 == e2 = EQ
    | otherwise = if (elength e1) <= (elength e2) then LT
      else GT

-- Check that ←q == ←←q                                                                                               
test2 = S.fromList [(:←) ((:←) (P "q")), P "q"]
-- output should be : {←←q, ←q}                                                                                       
test3 = S.fromList [P "q",  (:←) ((:←) (P "q")), (:←) (P "q")]  
-- output should be same as that of test3 : {←←q, ←q}                                                                 
test4 = S.fromList [P "q", (:←) (P "q"), (:←) ((:←) (P "q"))]

编辑:

  1. 请注意,如果我修改elength 定义来处理这种情况,那么不一致就会消失。

    elength ((:←) ((:←) e)) = elength e

  2. q←←q 的情况下,我的elength 度量和== 定义可能不一致。我还是想知道他们到底哪里出了问题

【问题讨论】:

  • 为什么要使用这个:← 构造函数?使用一元中缀运算符比使用简单的 Not 构造函数要痛苦得多。
  • 也许是这样。这是一个更大的项目的一部分,其中还有其他几个一元运算符,在代码中以视觉方式区分它们会有所帮助。

标签: haskell typeclass


【解决方案1】:

您的 Eq 实例在我看来确实很奇怪。我会一次解开两个取消的配对,而不是零碎:

instance Eq Ex where
  (P s1) == (P s2)     = s1 == s2
  ((:←) e1) == (:←) e2 = e1 == e2
  e1 == (:←) ((:←) e2) = e1 == e2
  (:←) ((:←) e1) == e2 = e1 == e2
  _ == _ = False

也许这和你写的一样;这很难说,因为您的模式匹配与您的目标不一致。

您的 Ord 实例也是一个问题,因为您没有定义一致的顺序。对于x y z 的任何一组项目,其中x &lt; y &amp;&amp; y &lt; z,应该是x &lt; z 的情况。但是,根据您的规则,有一些简单的反例:

x = P "a"
y = (P "b" :←)
z = ((P "a" :←) :←)

这里是x == z,尽管y 介于两者之间。

解决这两个问题的一种方法是编写一个simplify 函数来删除所有取消构造函数对,并在EqOrd 实例中使用它。简化每个参数,以便您知道它们每个都有 0 或 1 级否定。从那里开始,Eq 很容易,在定义Ord 之前,您需要做的就是确定一个否定值应该小于还是大于非否定值。你不能选择让它们相等,因为这又破坏了传递性。

如果您确实写了simplify,那么每次您触摸其中一个对象时调用简化都会浪费很多工作,因为您会立即丢弃简化。我选择不导出这种类型的构造函数,而是提供一个智能构造函数,在返回值之前进行简化。然后消费者就会知道所有事情都被否定一次或根本不被否定。

【讨论】:

  • 感谢 Eq 实例和反例帮助我更多地理解问题。接受这个作为答案。但是,你能告诉我在定义 Eq 实例时,我们是否应该注意对称属性?也就是说,我们应该同时定义 (e1 ==
  • 是的,当然(==) 应该是对称的。
  • Eq 实例的定义应该明确吗?
  • 我不明白这个问题。我的 Eq 实例处理这两种情况。您认为“显式”定义与其他定义有什么区别?
  • 抱歉,我的问题不清楚。我问的是在您的 Eq 实例中,我们是否必须同时提供这两种情况,还是定义一种情况就足够了?我正在研究定义 Eq 实例的最低要求。这是一个普遍的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-07-06
  • 2012-04-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-12
  • 2012-05-31
相关资源
最近更新 更多