@@ -254,6 +254,25 @@ data HashMap k v
254254 -- * No two keys stored in a 'Collision' can be equal according to their
255255 -- 'Eq' instance. (INV10)
256256
257+ {-
258+ Note [Canonical form]
259+ ~~~~~~~~~~~~~~~~~~~~~~~
260+
261+ The invariants above imply that HashMaps have a canonical form: two
262+ HashMaps that contain the same key-value pairs have the same tree
263+ structure, modulo the order of keys within a Collision node -- regardless
264+ of the order in which they were constructed. This is because each key's
265+ hash fully determines the path to its leaf, while INV3, INV5 and INV8
266+ rule out alternative encodings of the same sub-tree (e.g. a redundant
267+ BitmapIndexed node wrapping a single child, or a BitmapIndexed node that
268+ could be a Full node).
269+
270+ Several functions rely on this. They walk the leaves of two trees in tree
271+ order and compare or combine them pairwise, treating only Collision nodes
272+ as unordered -- for example, equal1. Without a canonical form these could
273+ give inconsistent results for maps with equal contents.
274+ -}
275+
257276type role HashMap nominal representational
258277
259278-- | @since 0.2.17.0
@@ -428,6 +447,7 @@ instance Eq k => Eq1 (HashMap k) where
428447instance (Eq k , Eq v ) => Eq (HashMap k v ) where
429448 (==) = equal1 (==)
430449
450+ -- See Note [Canonical form]
431451equal1 :: Eq k
432452 => (v -> v' -> Bool )
433453 -> HashMap k v -> HashMap k v' -> Bool
@@ -444,6 +464,7 @@ equal1 eq = go
444464
445465 leafEq (L k1 v1) (L k2 v2) = k1 == k2 && eq v1 v2
446466
467+ -- See Note [Canonical form]
447468equal2 :: (k -> k' -> Bool ) -> (v -> v' -> Bool )
448469 -> HashMap k v -> HashMap k' v' -> Bool
449470equal2 eqk eqv t1 t2 = go (leavesAndCollisions t1 [] ) (leavesAndCollisions t2 [] )
@@ -478,6 +499,7 @@ instance Ord k => Ord1 (HashMap k) where
478499instance (Ord k , Ord v ) => Ord (HashMap k v ) where
479500 compare = cmp compare compare
480501
502+ -- See Note [Canonical form]
481503cmp :: (k -> k' -> Ordering ) -> (v -> v' -> Ordering )
482504 -> HashMap k v -> HashMap k' v' -> Ordering
483505cmp cmpk cmpv t1 t2 = go (leavesAndCollisions t1 [] ) (leavesAndCollisions t2 [] )
@@ -504,6 +526,7 @@ cmp cmpk cmpv t1 t2 = go (leavesAndCollisions t1 []) (leavesAndCollisions t2 [])
504526equalKeys1 :: (k -> k' -> Bool ) -> HashMap k v -> HashMap k' v' -> Bool
505527equalKeys1 eq t1 t2 = go (leavesAndCollisions t1 [] ) (leavesAndCollisions t2 [] )
506528 where
529+ -- See Note [Canonical form]
507530 go (Leaf k1 l1 : tl1) (Leaf k2 l2 : tl2)
508531 | k1 == k2 && leafEq l1 l2
509532 = go tl1 tl2
@@ -520,6 +543,7 @@ equalKeys1 eq t1 t2 = go (leavesAndCollisions t1 []) (leavesAndCollisions t2 [])
520543equalKeys :: Eq k => HashMap k v -> HashMap k v' -> Bool
521544equalKeys = go
522545 where
546+ -- See Note [Canonical form]
523547 go :: Eq k => HashMap k v -> HashMap k v' -> Bool
524548 go Empty Empty = True
525549 go (BitmapIndexed bm1 ary1) (BitmapIndexed bm2 ary2)
@@ -532,6 +556,7 @@ equalKeys = go
532556
533557 leafEq (L k1 _) (L k2 _) = k1 == k2
534558
559+ -- See Note [Canonical form]
535560instance Hashable2 HashMap where
536561 liftHashWithSalt2 hk hv salt hm = go salt (leavesAndCollisions hm [] )
537562 where
@@ -558,6 +583,7 @@ instance Hashable2 HashMap where
558583instance (Hashable k ) => Hashable1 (HashMap k ) where
559584 liftHashWithSalt = H. liftHashWithSalt2 H. hashWithSalt
560585
586+ -- See Note [Canonical form]
561587instance (Hashable k , Hashable v ) => Hashable (HashMap k v ) where
562588 hashWithSalt salt hm = go salt hm
563589 where
0 commit comments