Skip to content

Commit 70834f2

Browse files
authored
Make Map an instance of Bifoldable (#714)
* Make Map an instance of Bifoldable * Replace the Arbitrary instance for Map with an adaptation of the one from Set * Use a Map and toList instead of a list and fromList
1 parent 0061862 commit 70834f2

File tree

2 files changed

+230
-61
lines changed

2 files changed

+230
-61
lines changed

containers-tests/tests/map-properties.hs

Lines changed: 205 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,19 @@ import Data.Map.Internal (Map (..), link2, link, bin)
1111
import Data.Map.Internal.Debug (showTree, showTreeWith, balanced)
1212

1313
import Control.Applicative (Const(Const, getConst), pure, (<$>), (<*>))
14-
import Data.Functor.Identity (Identity(runIdentity))
14+
import Control.Monad.Trans.State.Strict
15+
import Control.Monad.Trans.Class
16+
import Control.Monad (liftM4)
17+
import Data.Functor.Identity (Identity(Identity, runIdentity))
1518
import Data.Monoid
1619
import Data.Maybe hiding (mapMaybe)
1720
import qualified Data.Maybe as Maybe (mapMaybe)
1821
import Data.Ord
1922
import Data.Function
23+
import qualified Data.Foldable as Foldable
24+
#if MIN_VERSION_base(4,10,0)
25+
import qualified Data.Bifoldable as Bifoldable
26+
#endif
2027
import Prelude hiding (lookup, null, map, filter, foldr, foldl, take, drop, splitAt)
2128
import qualified Prelude
2229

@@ -212,10 +219,25 @@ main = defaultMain
212219
, testProperty "fmap" prop_fmap
213220
, testProperty "mapkeys" prop_mapkeys
214221
, testProperty "split" prop_splitModel
222+
, testProperty "fold" prop_fold
223+
, testProperty "foldMap" prop_foldMap
224+
, testProperty "foldMapWithKey" prop_foldMapWithKey
215225
, testProperty "foldr" prop_foldr
226+
, testProperty "foldrWithKey" prop_foldrWithKey
216227
, testProperty "foldr'" prop_foldr'
228+
, testProperty "foldrWithKey'" prop_foldrWithKey'
217229
, testProperty "foldl" prop_foldl
230+
, testProperty "foldlWithKey" prop_foldlWithKey
218231
, testProperty "foldl'" prop_foldl'
232+
, testProperty "foldlWithKey'" prop_foldlWithKey'
233+
#if MIN_VERSION_base(4,10,0)
234+
, testProperty "bifold" prop_bifold
235+
, testProperty "bifoldMap" prop_bifoldMap
236+
, testProperty "bifoldr" prop_bifoldr
237+
, testProperty "bifoldr'" prop_bifoldr'
238+
, testProperty "bifoldl" prop_bifoldl
239+
, testProperty "bifoldl'" prop_bifoldl'
240+
#endif
219241
, testProperty "keysSet" prop_keysSet
220242
, testProperty "fromSet" prop_fromSet
221243
, testProperty "takeWhileAntitone" prop_takeWhileAntitone
@@ -229,29 +251,92 @@ main = defaultMain
229251
]
230252

231253
{--------------------------------------------------------------------
232-
Arbitrary trees
254+
Arbitrary, reasonably balanced trees
233255
--------------------------------------------------------------------}
234-
instance (Enum k,Arbitrary a) => Arbitrary (Map k a) where
235-
arbitrary = sized (arbtree 0 maxkey)
236-
where maxkey = 10^(5 :: Int)
237-
238-
arbtree :: (Enum k, Arbitrary a) => Int -> Int -> Int -> Gen (Map k a)
239-
arbtree lo hi n = do t <- gentree lo hi n
240-
if balanced t then return t else arbtree lo hi n
241-
where gentree lo hi n
242-
| n <= 0 = return Tip
243-
| lo >= hi = return Tip
244-
| otherwise = do{ x <- arbitrary
245-
; i <- choose (lo,hi)
246-
; m <- choose (1,70)
247-
; let (ml,mr) | m==(1::Int)= (1,2)
248-
| m==2 = (2,1)
249-
| m==3 = (1,1)
250-
| otherwise = (2,2)
251-
; l <- gentree lo (i-1) (n `div` ml)
252-
; r <- gentree (i+1) hi (n `div` mr)
253-
; return (bin (toEnum i) x l r)
254-
}
256+
257+
-- | The IsInt class lets us constrain a type variable to be Int in an entirely
258+
-- standard way. The constraint @ IsInt a @ is essentially equivalent to the
259+
-- GHC-only constraint @ a ~ Int @, but @ IsInt @ requires manual intervention
260+
-- to use. If ~ is ever standardized, we should certainly use it instead.
261+
-- Earlier versions used an Enum constraint, but this is confusing because
262+
-- not all Enum instances will work properly for the Arbitrary instance here.
263+
class (Show a, Read a, Integral a, Arbitrary a) => IsInt a where
264+
fromIntF :: f Int -> f a
265+
266+
instance IsInt Int where
267+
fromIntF = id
268+
269+
-- | Convert an Int to any instance of IsInt
270+
fromInt :: IsInt a => Int -> a
271+
fromInt = runIdentity . fromIntF . Identity
272+
273+
{- We don't actually need this, but we can add it if we ever do
274+
toIntF :: IsInt a => g a -> g Int
275+
toIntF = unf . fromIntF . F $ id
276+
277+
newtype F g a b = F {unf :: g b -> a}
278+
279+
toInt :: IsInt a => a -> Int
280+
toInt = runIdentity . toIntF . Identity -}
281+
282+
283+
-- How much the minimum key of an arbitrary map should vary
284+
positionFactor :: Int
285+
positionFactor = 1
286+
287+
-- How much the gap between consecutive keys in an arbitrary
288+
-- map should vary
289+
gapRange :: Int
290+
gapRange = 5
291+
292+
instance (IsInt k, Arbitrary v) => Arbitrary (Map k v) where
293+
arbitrary = sized (\sz0 -> do
294+
sz <- choose (0, sz0)
295+
middle <- choose (-positionFactor * (sz + 1), positionFactor * (sz + 1))
296+
let shift = (sz * (gapRange) + 1) `quot` 2
297+
start = middle - shift
298+
t <- evalStateT (mkArb step sz) start
299+
if valid t then pure t else error "Test generated invalid tree!")
300+
where
301+
step = do
302+
i <- get
303+
diff <- lift $ choose (1, gapRange)
304+
let i' = i + diff
305+
put i'
306+
pure (fromInt i')
307+
308+
class Monad m => MonadGen m where
309+
liftGen :: Gen a -> m a
310+
instance MonadGen Gen where
311+
liftGen = id
312+
instance MonadGen m => MonadGen (StateT s m) where
313+
liftGen = lift . liftGen
314+
315+
-- | Given an action that produces successively larger keys and
316+
-- a size, produce a map of arbitrary shape with exactly that size.
317+
mkArb :: (MonadGen m, Arbitrary v) => m k -> Int -> m (Map k v)
318+
mkArb step n
319+
| n <= 0 = return Tip
320+
| n == 1 = do
321+
k <- step
322+
v <- liftGen arbitrary
323+
return (singleton k v)
324+
| n == 2 = do
325+
dir <- liftGen arbitrary
326+
p <- step
327+
q <- step
328+
vOuter <- liftGen arbitrary
329+
vInner <- liftGen arbitrary
330+
if dir
331+
then return (Bin 2 q vOuter (singleton p vInner) Tip)
332+
else return (Bin 2 p vOuter Tip (singleton q vInner))
333+
| otherwise = do
334+
-- This assumes a balance factor of delta = 3
335+
let upper = (3*(n - 1)) `quot` 4
336+
let lower = (n + 2) `quot` 4
337+
ln <- liftGen $ choose (lower, upper)
338+
let rn = n - ln - 1
339+
liftM4 (\lt x v rt -> Bin n x v lt rt) (mkArb step ln) step (liftGen arbitrary) (mkArb step rn)
255340

256341
-- A type with a peculiar Eq instance designed to make sure keys
257342
-- come from where they're supposed to.
@@ -1365,46 +1450,105 @@ prop_splitModel n ys = length ys > 0 ==>
13651450
in toAscList l == sort [(k, v) | (k,v) <- xs, k < n] &&
13661451
toAscList r == sort [(k, v) | (k,v) <- xs, k > n]
13671452

1368-
prop_foldr :: Int -> [(Int, Int)] -> Property
1369-
prop_foldr n ys = length ys > 0 ==>
1370-
let xs = List.nubBy ((==) `on` fst) ys
1371-
m = fromList xs
1372-
in foldr (+) n m == List.foldr (+) n (List.map snd xs) &&
1373-
foldr (:) [] m == List.map snd (List.sort xs) &&
1374-
foldrWithKey (\_ a b -> a + b) n m == List.foldr (+) n (List.map snd xs) &&
1375-
foldrWithKey (\k _ b -> k + b) n m == List.foldr (+) n (List.map fst xs) &&
1376-
foldrWithKey (\k x xs -> (k,x):xs) [] m == List.sort xs
1453+
prop_fold :: Map Int A -> Property
1454+
prop_fold = \m -> Foldable.fold (f <$> m) === Foldable.fold (f <$> elems m)
1455+
where
1456+
f v = [v]
13771457

1458+
prop_foldMap :: Map Int A -> Property
1459+
prop_foldMap = \m -> Foldable.foldMap f m === Foldable.foldMap f (elems m)
1460+
where
1461+
f v = [v]
13781462

1379-
prop_foldr' :: Int -> [(Int, Int)] -> Property
1380-
prop_foldr' n ys = length ys > 0 ==>
1381-
let xs = List.nubBy ((==) `on` fst) ys
1382-
m = fromList xs
1383-
in foldr' (+) n m == List.foldr (+) n (List.map snd xs) &&
1384-
foldr' (:) [] m == List.map snd (List.sort xs) &&
1385-
foldrWithKey' (\_ a b -> a + b) n m == List.foldr (+) n (List.map snd xs) &&
1386-
foldrWithKey' (\k _ b -> k + b) n m == List.foldr (+) n (List.map fst xs) &&
1387-
foldrWithKey' (\k x xs -> (k,x):xs) [] m == List.sort xs
1388-
1389-
prop_foldl :: Int -> [(Int, Int)] -> Property
1390-
prop_foldl n ys = length ys > 0 ==>
1391-
let xs = List.nubBy ((==) `on` fst) ys
1392-
m = fromList xs
1393-
in foldl (+) n m == List.foldr (+) n (List.map snd xs) &&
1394-
foldl (flip (:)) [] m == reverse (List.map snd (List.sort xs)) &&
1395-
foldlWithKey (\b _ a -> a + b) n m == List.foldr (+) n (List.map snd xs) &&
1396-
foldlWithKey (\b k _ -> k + b) n m == List.foldr (+) n (List.map fst xs) &&
1397-
foldlWithKey (\xs k x -> (k,x):xs) [] m == reverse (List.sort xs)
1398-
1399-
prop_foldl' :: Int -> [(Int, Int)] -> Property
1400-
prop_foldl' n ys = length ys > 0 ==>
1401-
let xs = List.nubBy ((==) `on` fst) ys
1402-
m = fromList xs
1403-
in foldl' (+) n m == List.foldr (+) n (List.map snd xs) &&
1404-
foldl' (flip (:)) [] m == reverse (List.map snd (List.sort xs)) &&
1405-
foldlWithKey' (\b _ a -> a + b) n m == List.foldr (+) n (List.map snd xs) &&
1406-
foldlWithKey' (\b k _ -> k + b) n m == List.foldr (+) n (List.map fst xs) &&
1407-
foldlWithKey' (\xs k x -> (k,x):xs) [] m == reverse (List.sort xs)
1463+
prop_foldMapWithKey :: Map Int A -> Property
1464+
prop_foldMapWithKey = \m -> foldMapWithKey (curry f) m === Foldable.foldMap f (toList m)
1465+
where
1466+
f kv = [kv]
1467+
1468+
-- elems is implemented in terms of foldr, so we don't want to rely on it
1469+
-- when we're trying to test foldr.
1470+
prop_foldr :: Fun (A, B) B -> B -> [(Int, A)] -> Property
1471+
prop_foldr c n ys = foldr c' n m === Foldable.foldr c' n (snd <$> xs)
1472+
where
1473+
c' = curry (apply c)
1474+
xs = List.sortBy (comparing fst) (List.nubBy ((==) `on` fst) ys)
1475+
m = fromList xs
1476+
1477+
1478+
-- toList is implemented in terms of foldrWithKey, so we don't want to rely on it
1479+
-- when we're trying to test foldrWithKey.
1480+
prop_foldrWithKey :: Fun (Int, A, B) B -> B -> [(Int, A)] -> Property
1481+
prop_foldrWithKey c n ys = foldrWithKey c' n m === Foldable.foldr (uncurry c') n xs
1482+
where
1483+
c' k v acc = apply c (k, v, acc)
1484+
xs = List.sortBy (comparing fst) (List.nubBy ((==) `on` fst) ys)
1485+
m = fromList xs
1486+
1487+
prop_foldr' :: Fun (A, B) B -> B -> Map Int A -> Property
1488+
prop_foldr' c n m = foldr' c' n m === Foldable.foldr' c' n (elems m)
1489+
where
1490+
c' = curry (apply c)
1491+
1492+
prop_foldrWithKey' :: Fun (Int, A, B) B -> B -> Map Int A -> Property
1493+
prop_foldrWithKey' c n m = foldrWithKey' c' n m === Foldable.foldr' (uncurry c') n (toList m)
1494+
where
1495+
c' k v acc = apply c (k, v, acc)
1496+
1497+
prop_foldl :: Fun (B, A) B -> B -> Map Int A -> Property
1498+
prop_foldl c n m = foldl c' n m === Foldable.foldl c' n (elems m)
1499+
where
1500+
c' = curry (apply c)
1501+
1502+
prop_foldlWithKey :: Fun (B, Int, A) B -> B -> Map Int A -> Property
1503+
prop_foldlWithKey c n m = foldlWithKey c' n m === Foldable.foldl (uncurry . c') n (toList m)
1504+
where
1505+
c' acc k v = apply c (acc, k, v)
1506+
1507+
prop_foldl' :: Fun (B, A) B -> B -> Map Int A -> Property
1508+
prop_foldl' c n m = foldl' c' n m === Foldable.foldl' c' n (elems m)
1509+
where
1510+
c' = curry (apply c)
1511+
1512+
prop_foldlWithKey' :: Fun (B, Int, A) B -> B -> Map Int A -> Property
1513+
prop_foldlWithKey' c n m = foldlWithKey' c' n m === Foldable.foldl' (uncurry . c') n (toList m)
1514+
where
1515+
c' acc k v = apply c (acc, k, v)
1516+
1517+
#if MIN_VERSION_base(4,10,0)
1518+
prop_bifold :: Map Int Int -> Property
1519+
prop_bifold m = Bifoldable.bifold (mapKeys (:[]) ((:[]) <$> m)) === Foldable.fold ((\(k,v) -> [k,v]) <$> toList m)
1520+
1521+
prop_bifoldMap :: Map Int Int -> Property
1522+
prop_bifoldMap m = Bifoldable.bifoldMap (:[]) (:[]) m === Foldable.foldMap (\(k,v) -> [k,v]) (toList m)
1523+
1524+
prop_bifoldr :: Fun (Int, B) B -> Fun (A, B) B -> B -> Map Int A -> Property
1525+
prop_bifoldr ck cv n m = Bifoldable.bifoldr ck' cv' n m === Foldable.foldr c' n (toList m)
1526+
where
1527+
ck' = curry (apply ck)
1528+
cv' = curry (apply cv)
1529+
(k,v) `c'` acc = k `ck'` (v `cv'` acc)
1530+
1531+
prop_bifoldr' :: Fun (Int, B) B -> Fun (A, B) B -> B -> Map Int A -> Property
1532+
prop_bifoldr' ck cv n m = Bifoldable.bifoldr' ck' cv' n m === Foldable.foldr' c' n (toList m)
1533+
where
1534+
ck' = curry (apply ck)
1535+
cv' = curry (apply cv)
1536+
(k,v) `c'` acc = k `ck'` (v `cv'` acc)
1537+
1538+
prop_bifoldl :: Fun (B, Int) B -> Fun (B, A) B -> B -> Map Int A -> Property
1539+
prop_bifoldl ck cv n m = Bifoldable.bifoldl ck' cv' n m === Foldable.foldl c' n (toList m)
1540+
where
1541+
ck' = curry (apply ck)
1542+
cv' = curry (apply cv)
1543+
acc `c'` (k,v) = (acc `ck'` k) `cv'` v
1544+
1545+
prop_bifoldl' :: Fun (B, Int) B -> Fun (B, A) B -> B -> Map Int A -> Property
1546+
prop_bifoldl' ck cv n m = Bifoldable.bifoldl' ck' cv' n m === Foldable.foldl' c' n (toList m)
1547+
where
1548+
ck' = curry (apply ck)
1549+
cv' = curry (apply cv)
1550+
acc `c'` (k,v) = (acc `ck'` k) `cv'` v
1551+
#endif
14081552

14091553
prop_keysSet :: [(Int, Int)] -> Bool
14101554
prop_keysSet xs =

containers/src/Data/Map/Internal.hs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,9 @@ import qualified Data.Foldable as Foldable
391391
#if !MIN_VERSION_base(4,8,0)
392392
import Data.Foldable (Foldable())
393393
#endif
394+
#if MIN_VERSION_base(4,10,0)
395+
import Data.Bifoldable
396+
#endif
394397
import Data.Typeable
395398
import Prelude hiding (lookup, map, filter, foldr, foldl, null, splitAt, take, drop)
396399

@@ -4256,6 +4259,28 @@ instance Foldable.Foldable (Map k) where
42564259
{-# INLINABLE product #-}
42574260
#endif
42584261

4262+
#if MIN_VERSION_base(4,10,0)
4263+
instance Bifoldable Map where
4264+
bifold = go
4265+
where go Tip = mempty
4266+
go (Bin 1 k v _ _) = k `mappend` v
4267+
go (Bin _ k v l r) = go l `mappend` (k `mappend` (v `mappend` go r))
4268+
{-# INLINABLE bifold #-}
4269+
bifoldr f g z = go z
4270+
where go z' Tip = z'
4271+
go z' (Bin _ k v l r) = go (f k (g v (go z' r))) l
4272+
{-# INLINE bifoldr #-}
4273+
bifoldl f g z = go z
4274+
where go z' Tip = z'
4275+
go z' (Bin _ k v l r) = go (g (f (go z' l) k) v) r
4276+
{-# INLINE bifoldl #-}
4277+
bifoldMap f g t = go t
4278+
where go Tip = mempty
4279+
go (Bin 1 k v _ _) = f k `mappend` g v
4280+
go (Bin _ k v l r) = go l `mappend` (f k `mappend` (g v `mappend` go r))
4281+
{-# INLINE bifoldMap #-}
4282+
#endif
4283+
42594284
instance (NFData k, NFData a) => NFData (Map k a) where
42604285
rnf Tip = ()
42614286
rnf (Bin _ kx x l r) = rnf kx `seq` rnf x `seq` rnf l `seq` rnf r

0 commit comments

Comments
 (0)