【问题标题】:Inconsistent Eq and Ord instances?Eq 和 Ord 实例不一致?
【发布时间】:2013-06-14 18:19:55
【问题描述】:

我有一个运行缓慢的大型 Haskell 程序。分析和测试表明,大部分时间都花在比较非常重要的特定大型数据类型的相等性和排序上。相等是一种有用的操作(这是状态空间搜索,图搜索比树搜索更可取),但我只需要此类的 Ord 实例即可使用 Maps。所以我想做的就是说

instance Eq BigThing where
(==) b b' = name b == name b' &&
            firstPart b == firstPart b' &&
            secondPart b == secondPart b' &&
            {- ...and so on... -}

instance Ord BigThing where
compare b b' = compare (name b) (name b')

但由于不同对象的名称可能并不总是不同,这可能会产生奇怪的情况,即两个 BigThings 根据 == 可能不相等,但比较它们会产生 EQ。

这会导致 Haskell 库出现问题吗?有没有另一种方法可以满足详细的相等操作但订购便宜的要求?

【问题讨论】:

  • 我已经完成了,但要小心你使用的库。
  • 您需要按照name订购,还是可以接受任何一种一致的订购方式,这样您就可以使用Maps而且速度很快?
  • 任何订单都可以接受。
  • 如果您需要对所有字段进行比较,我曾经见过一次或两次的有趣技术是计算哈希并将其包含在数据结构中,然后在哈希上进行比较。

标签: haskell typeclass


【解决方案1】:

首先,使用TextByteString 代替String 可以有很大帮助,而无需更改任何其他内容。

通常我不建议创建与Ord 不一致的Eq 实例。图书馆可以正确地依赖它,你永远不知道它会导致什么样的奇怪问题。 (例如,您确定Map 不使用EqOrd 之间的关系吗?)


如果你根本不需要Eq 实例,你可以简单地定义

instance Eq BigThing where
    x == y  =  compare x y == EQ

那么相等将与比较一致。不要求相等的值必须使所有字段都相等。


如果您需要一个比较所有字段的Eq 实例,那么您可以通过将BigThing 包装到newtype 中来保持一致,为其定义上述EqOrd,并在您的当您需要根据name订购时的算法:

newtype BigThing' a b c = BigThing' (BigThing a b c)
instance Eq BigThing' where
    x == y  =  compare x y == EQ
instance Ord BigThing' where
    compare (BigThing b) (BigThing b') = compare (name b) (name b')

更新:既然你说任何排序都是可以接受的,你可以使用散列来发挥你的优势。为此,您可以使用 hashable 包。这个想法是您在数据创建时预先计算哈希值并在比较值时使用它们。如果两个值不同,则几乎可以肯定它们的哈希值会有所不同,并且您只比较它们的哈希值(两个整数),仅此而已。它可能看起来像这样:

module BigThing
    ( BigThing()
    , bigThing
    , btHash, btName, btSurname
    )
where

import Data.Hashable

data BigThing = BigThing { btHash :: Int,
                           btName :: String,
                           btSurname :: String } -- etc
  deriving (Eq, Ord)
-- Since the derived Eq/Ord instances compare fields lexicographically and
-- btHash is the first, they'll compare the hash first and continue with the
-- other fields only if the hashes are equal.
-- See http://www.haskell.org/onlinereport/derived.html#sect10.1
--
-- Alternativelly, you can create similar Eq/Ord instances yourself, if for any
-- reason you don't want the hash to be the first field.

-- A smart constructor for creating instances. Your module will not export the
-- BigThing constructor, it will export this function instead:
bigThing :: String -> String -> BigThing
bigThing nm snm = BigThing (hash (nm, snm)) nm snm

请注意,使用此解决方案,排序似乎是随机的,与字段没有明显关系。

您也可以将此解决方案与之前的解决方案结合使用。或者,您可以创建一个小模块,以使用其预先计算的散列包装任何类型(包装的值必须具有与其 Hashable 实例一致的 Eq 实例)。

module HashOrd
    ( Hashed()
    , getHashed
    , hashedHash
    )
where

import Data.Hashable

data Hashed a = Hashed { hashedHash :: Int, getHashed :: a }
  deriving (Ord, Eq, Show, Read, Bounded)

hashed :: (Hashable a) => a -> Hashed a
hashed x = Hashed (hash x) x

instance Hashable a => Hashable (Hashed a) where
    hashWithSalt salt (Hashed _ x) = hashWithSalt salt x

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-11-21
    • 1970-01-01
    • 1970-01-01
    • 2012-05-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多