Skip to content

Commit 4bd9f7a

Browse files
authored
Fix Foldable instance for IntMap (fixes #579) (#593)
* Fix Foldable instance for IntMap. As reported in #579 the Foldable instance for IntMap is unlawful and internally inconsistent. This was caused as a result of the internal representation used by IntMap. More specifically, `fold`, `foldMap`, and `traverse` (via `traverseWithKey`) always placed positively keyed entries before negative keyed ones. To fix this we need to check to see if the mask is positive or negative. Tested by adding new property tests, verifying they failed with the implementation at HEAD, and then passed after the changes.
1 parent 3a343a3 commit 4bd9f7a

File tree

3 files changed

+40
-4
lines changed

3 files changed

+40
-4
lines changed

Data/IntMap/Internal.hs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,9 @@ instance Foldable.Foldable IntMap where
441441
fold = go
442442
where go Nil = mempty
443443
go (Tip _ v) = v
444-
go (Bin _ _ l r) = go l `mappend` go r
444+
go (Bin _ m l r)
445+
| m < 0 = go r `mappend` go l
446+
| otherwise = go l `mappend` go r
445447
{-# INLINABLE fold #-}
446448
foldr = foldr
447449
{-# INLINE foldr #-}
@@ -450,7 +452,9 @@ instance Foldable.Foldable IntMap where
450452
foldMap f t = go t
451453
where go Nil = mempty
452454
go (Tip _ v) = f v
453-
go (Bin _ _ l r) = go l `mappend` go r
455+
go (Bin _ m l r)
456+
| m < 0 = go r `mappend` go l
457+
| otherwise = go l `mappend` go r
454458
{-# INLINE foldMap #-}
455459
foldl' = foldl'
456460
{-# INLINE foldl' #-}
@@ -2416,7 +2420,9 @@ traverseWithKey f = go
24162420
where
24172421
go Nil = pure Nil
24182422
go (Tip k v) = Tip k <$> f k v
2419-
go (Bin p m l r) = liftA2 (Bin p m) (go l) (go r)
2423+
go (Bin p m l r)
2424+
| m < 0 = liftA2 (Bin p m) (go r) (go l)
2425+
| otherwise = liftA2 (Bin p m) (go l) (go r)
24202426
{-# INLINE traverseWithKey #-}
24212427

24222428
-- | /O(n)/. The function @'mapAccum'@ threads an accumulating
@@ -2875,7 +2881,9 @@ foldMapWithKey f = go
28752881
where
28762882
go Nil = mempty
28772883
go (Tip kx x) = f kx x
2878-
go (Bin _ _ l r) = go l `mappend` go r
2884+
go (Bin _ m l r)
2885+
| m < 0 = go r `mappend` go l
2886+
| otherwise = go l `mappend` go r
28792887
{-# INLINE foldMapWithKey #-}
28802888

28812889
{--------------------------------------------------------------------

changelog.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog for [`containers` package](http://github.com/haskell/containers)
22

3+
## 0.6.0.2
4+
5+
* Fix Foldable instance for IntMap, which previously placed positively
6+
keyed entries before negatively keyed ones for `fold`, `foldMap`, and
7+
`traverse`.
8+
39
## 0.6.0.1
410

511
* Released with GHC 8.6

tests/intmap-properties.hs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import Data.Monoid
1212
import Data.Maybe hiding (mapMaybe)
1313
import qualified Data.Maybe as Maybe (mapMaybe)
1414
import Data.Ord
15+
import Data.Foldable (foldMap)
1516
import Data.Function
17+
import Data.Traversable (Traversable(traverse), foldMapDefault)
1618
import Prelude hiding (lookup, null, map, filter, foldr, foldl)
1719
import qualified Prelude (map)
1820

@@ -25,6 +27,7 @@ import Test.Framework.Providers.QuickCheck2
2527
import Test.HUnit hiding (Test, Testable)
2628
import Test.QuickCheck
2729
import Test.QuickCheck.Function (Fun(..), apply)
30+
import Test.QuickCheck.Poly (A, B)
2831

2932
default (Int)
3033

@@ -176,6 +179,13 @@ main = defaultMain
176179
, testProperty "foldr'" prop_foldr'
177180
, testProperty "foldl" prop_foldl
178181
, testProperty "foldl'" prop_foldl'
182+
, testProperty "foldr==foldMap" prop_foldrEqFoldMap
183+
, testProperty
184+
"foldrWithKey==foldMapWithKey"
185+
prop_foldrWithKeyEqFoldMapWithKey
186+
, testProperty
187+
"prop_FoldableTraversableCompat"
188+
prop_FoldableTraversableCompat
179189
, testProperty "keysSet" prop_keysSet
180190
, testProperty "fromSet" prop_fromSet
181191
, testProperty "restrictKeys" prop_restrictKeys
@@ -1156,6 +1166,18 @@ prop_foldl' n ys = length ys > 0 ==>
11561166
foldlWithKey' (\b k _ -> k + b) n m == List.foldr (+) n (List.map fst xs) &&
11571167
foldlWithKey' (\xs k x -> (k,x):xs) [] m == reverse (List.sort xs)
11581168

1169+
prop_foldrEqFoldMap :: IntMap Int -> Property
1170+
prop_foldrEqFoldMap m =
1171+
foldr (:) [] m === Data.Foldable.foldMap (:[]) m
1172+
1173+
prop_foldrWithKeyEqFoldMapWithKey :: IntMap Int -> Property
1174+
prop_foldrWithKeyEqFoldMapWithKey m =
1175+
foldrWithKey (\k v -> ((k,v):)) [] m === foldMapWithKey (\k v -> ([(k,v)])) m
1176+
1177+
prop_FoldableTraversableCompat :: Fun A [B] -> IntMap A -> Property
1178+
prop_FoldableTraversableCompat fun m = foldMap f m === foldMapDefault f m
1179+
where f = apply fun
1180+
11591181
prop_keysSet :: [(Int, Int)] -> Bool
11601182
prop_keysSet xs =
11611183
keysSet (fromList xs) == IntSet.fromList (List.map fst xs)

0 commit comments

Comments
 (0)