考虑以下类型:
import Data.Set as Set
-- http://json.org/
type Key = String
data Json = JObject Key (Set JValue)
| JArray JArr
deriving Show
data JObj = JObj Key JValue
deriving Show
data JArr = Arr [JValue] deriving Show
data Null = Null deriving Show
data JValue = Num Double
| S String
| B Bool
| J JObj
| Array JArr
| N Null
deriving Show
我使用单个元素创建了JObject Key (Set Value)
:
ghci> JObject "foo" (Set.singleton (B True))
JObject "foo" (fromList [B True])
但是,当我尝试创建一个2元素集时,我遇到了一个编译时错误:
ghci> JObject "foo" (Set.insert (Num 5.5) $ Set.singleton (B True))
<interactive>:159:16:
No instance for (Ord JValue) arising from a use of ‘insert’
In the expression: insert (Num 5.5)
In the second argument of ‘JObject’, namely
‘(insert (Num 5.5) $ singleton (B True))’
In the expression:
JObject "foo" (insert (Num 5.5) $ singleton (B True))
所以我问道,“为什么JValue
有必要实现Ord
类型类?”
Data.Set上的文档回答了这个问题。
Set的实现基于大小平衡的二叉树(或有界平衡树)
但是,是否有一个类似Set的,即非有序的数据结构,不需要Ord
的实现,我可以使用?
答案 0 :(得分:8)
您几乎总是需要至少Eq
来实现一个集合(或至少能力来编写Eq
实例,无论其是否存在)。只有 Eq
会给你一个可怕的低效率。您可以使用Ord
或Hashable
来改善这一点。
你可能想要做的一件事是使用一个trie,它可以让你利用嵌套结构,而不是经常对抗它。
您可以先查看generic-trie。这似乎不会为您的Array
件提供任何内容,因此您可能需要添加一些内容。
Eq
不够好实现集合的最简单方法是使用列表:
type Set a = [a]
member a [] = False
member (x:xs) | a == x = True
| otherwise = member a xs
insert a xs | member a xs = xs
| otherwise = a:xs
这是不好的(除非元素很少),因为你可能必须遍历整个列表以查看是否有成员。
为了改善问题,我们需要使用某种树:
data Set a = Node a (Set a) (Set a) | Tip
我们可以制作许多不同种类的树,但为了使用它们,我们必须能够在每个节点决定采用哪些树枝。如果我们只有Eq
,则无法选择正确的选项。如果我们有Ord
(或Hashable
),则会为我们提供一种选择方式。
trie方法根据数据结构构造树。如果您的类型是深层嵌套的(列表记录数组列表...),则散列或比较可能非常昂贵,因此特里可能会更好。
Ord
虽然我认为你不应该在这里使用Ord
方法,但它通常是正确的方法。在某些情况下,您的特定类型可能不会具有自然排序,但有一些有效的方式来对其元素进行排序。在这种情况下,您可以使用newtype
:
newtype WrappedThing = Wrap Thing
instance Ord WrappedThing where
....
newtype ThingSet = ThingSet (Set WrappedThing)
insertThing thing (ThingSet s) = ThingSet (insert (Wrap thing) s)
memberThing thing (ThingSet s) = member (WrapThing) s
...
在某些情况下,另一种方法是定义一个“基类型”,它是一个Ord
实例,但只导出一个newtype
包装器;您可以对所有内部函数使用基类型,但导出的类型是完全抽象的(而不是Ord
实例)。