Skip to content

Commit cecfe9b

Browse files
authored
Merge pull request #140 from phadej/functor-classes
Add instance for Data.Functor.Classes
2 parents 27e0c47 + 080c4db commit cecfe9b

File tree

7 files changed

+165
-32
lines changed

7 files changed

+165
-32
lines changed

.travis.yml

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,18 @@ before_cache:
1313

1414
matrix:
1515
include:
16-
- env: CABALVER=1.16 GHCVER=7.4.2
16+
- env: CABALVER=1.24 GHCVER=7.4.2
1717
compiler: ": #GHC 7.4.2"
18-
addons: {apt: {packages: [cabal-install-1.16,ghc-7.4.2], sources: [hvr-ghc]}}
19-
- env: CABALVER=1.16 GHCVER=7.6.3
18+
addons: {apt: {packages: [cabal-install-1.24,ghc-7.4.2], sources: [hvr-ghc]}}
19+
- env: CABALVER=1.24 GHCVER=7.6.3
2020
compiler: ": #GHC 7.6.3"
21-
addons: {apt: {packages: [cabal-install-1.16,ghc-7.6.3], sources: [hvr-ghc]}}
22-
- env: CABALVER=1.18 GHCVER=7.8.4
21+
addons: {apt: {packages: [cabal-install-1.24,ghc-7.6.3], sources: [hvr-ghc]}}
22+
- env: CABALVER=1.24 GHCVER=7.8.4
2323
compiler: ": #GHC 7.8.4"
24-
addons: {apt: {packages: [cabal-install-1.18,ghc-7.8.4], sources: [hvr-ghc]}}
25-
- env: CABALVER=1.22 GHCVER=7.10.3
24+
addons: {apt: {packages: [cabal-install-1.24,ghc-7.8.4], sources: [hvr-ghc]}}
25+
- env: CABALVER=1.24 GHCVER=7.10.3
2626
compiler: ": #GHC 7.10.3"
27-
addons: {apt: {packages: [cabal-install-1.22,ghc-7.10.3], sources: [hvr-ghc]}}
27+
addons: {apt: {packages: [cabal-install-1.24,ghc-7.10.3], sources: [hvr-ghc]}}
2828
- env: CABALVER=1.24 GHCVER=8.0.1
2929
compiler: ": #GHC 8.0.1"
3030
addons: {apt: {packages: [cabal-install-1.24,ghc-8.0.1], sources: [hvr-ghc]}}
@@ -43,7 +43,7 @@ install:
4343
fi
4444
- travis_retry cabal update -v
4545
- sed -i 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config
46-
- cabal install --only-dependencies --enable-tests --dry -v > installplan.txt
46+
- cabal install --only-dependencies --enable-tests --disable-benchmarks --dry -v > installplan.txt
4747
- sed -i -e '1,/^Resolving /d' installplan.txt; cat installplan.txt
4848

4949
# check whether current requested install-plan matches cached package-db snapshot
@@ -57,7 +57,7 @@ install:
5757
echo "cabal build-cache MISS";
5858
rm -rf $HOME/.cabsnap;
5959
mkdir -p $HOME/.ghc $HOME/.cabal/lib $HOME/.cabal/share $HOME/.cabal/bin;
60-
cabal install --only-dependencies --enable-tests;
60+
cabal install --only-dependencies --enable-tests --disable-benchmarks;
6161
fi
6262

6363
# snapshot package-db on cache miss
@@ -73,7 +73,7 @@ install:
7373
# any command which exits with a non-zero exit code causes the build to fail.
7474
script:
7575
- if [ -f configure.ac ]; then autoreconf -i; fi
76-
- cabal configure --enable-tests -v2 # -v2 provides useful information for debugging
76+
- cabal configure --enable-tests --disable-benchmarks -v2 # -v2 provides useful information for debugging
7777
- cabal build # this builds all libraries and executables (including tests)
7878
- cabal test
7979
- cabal sdist # tests that a source-distribution can be generated

CHANGES.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## 0.2.8.0
2+
3+
* Add `Eq1/2`, `Show1/2`, `Read1` instances with `base-4.9`
4+
5+
* `Eq (HashSet a)` doesn't require `Hashable a` anymore, only `Eq a`.
6+
7+
* Add `Hashable1/2` with `hashable-1.2.6.0`
8+
19
## 0.2.7.2
210

311
* Don't use -fregs-graphs

Data/HashMap/Base.hs

Lines changed: 112 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ module Data.HashMap.Base
8989
, updateOrConcatWith
9090
, updateOrConcatWithKey
9191
, filterMapAux
92+
, equalKeys
9293
) where
9394

9495
#if __GLASGOW_HASKELL__ < 710
@@ -125,7 +126,15 @@ import GHC.Exts (isTrue#)
125126
import qualified GHC.Exts as Exts
126127
#endif
127128

129+
#if MIN_VERSION_base(4,9,0)
130+
import Data.Functor.Classes
131+
#endif
132+
133+
#if MIN_VERSION_hashable(1,2,5)
134+
import qualified Data.Hashable.Lifted as H
135+
#endif
128136

137+
-- | A set of values. A set cannot contain duplicate values.
129138
------------------------------------------------------------------------
130139

131140
-- | Convenience function. Compute a hash value for the given value.
@@ -203,6 +212,25 @@ type Hash = Word
203212
type Bitmap = Word
204213
type Shift = Int
205214

215+
#if MIN_VERSION_base(4,9,0)
216+
instance Show2 HashMap where
217+
liftShowsPrec2 spk slk spv slv d m =
218+
showsUnaryWith (liftShowsPrec sp sl) "fromList" d (toList m)
219+
where
220+
sp = liftShowsPrec2 spk slk spv slv
221+
sl = liftShowList2 spk slk spv slv
222+
223+
instance Show k => Show1 (HashMap k) where
224+
liftShowsPrec = liftShowsPrec2 showsPrec showList
225+
226+
instance (Eq k, Hashable k, Read k) => Read1 (HashMap k) where
227+
liftReadsPrec rp rl = readsData $
228+
readsUnaryWith (liftReadsPrec rp' rl') "fromList" fromList
229+
where
230+
rp' = liftReadsPrec rp rl
231+
rl' = liftReadList rp rl
232+
#endif
233+
206234
instance (Eq k, Hashable k, Read k, Read e) => Read (HashMap k e) where
207235
readPrec = parens $ prec 10 $ do
208236
Ident "fromList" <- lexP
@@ -218,26 +246,102 @@ instance (Show k, Show v) => Show (HashMap k v) where
218246
instance Traversable (HashMap k) where
219247
traverse f = traverseWithKey (const f)
220248

249+
#if MIN_VERSION_base(4,9,0)
250+
instance Eq2 HashMap where
251+
liftEq2 = equal
252+
253+
instance Eq k => Eq1 (HashMap k) where
254+
liftEq = equal (==)
255+
#endif
256+
221257
instance (Eq k, Eq v) => Eq (HashMap k v) where
222-
(==) = equal
258+
(==) = equal (==) (==)
223259

224-
equal :: (Eq k, Eq v) => HashMap k v -> HashMap k v -> Bool
225-
equal t1 t2 = go (toList' t1 []) (toList' t2 [])
260+
equal :: (k -> k' -> Bool) -> (v -> v' -> Bool)
261+
-> HashMap k v -> HashMap k' v' -> Bool
262+
equal eqk eqv t1 t2 = go (toList' t1 []) (toList' t2 [])
226263
where
227264
-- If the two trees are the same, then their lists of 'Leaf's and
228265
-- 'Collision's read from left to right should be the same (modulo the
229266
-- order of elements in 'Collision').
230267

231268
go (Leaf k1 l1 : tl1) (Leaf k2 l2 : tl2)
232-
| k1 == k2 && l1 == l2
269+
| k1 == k2 && leafEq l1 l2
270+
= go tl1 tl2
271+
go (Collision k1 ary1 : tl1) (Collision k2 ary2 : tl2)
272+
| k1 == k2 && A.length ary1 == A.length ary2 &&
273+
isPermutationBy leafEq (A.toList ary1) (A.toList ary2)
274+
= go tl1 tl2
275+
go [] [] = True
276+
go _ _ = False
277+
278+
leafEq (L k v) (L k' v') = eqk k k' && eqv v v'
279+
280+
-- Note: previous implemenation isPermutation = null (as // bs)
281+
-- was O(n^2) too.
282+
--
283+
-- This assumes lists are of equal length
284+
isPermutationBy :: (a -> b -> Bool) -> [a] -> [b] -> Bool
285+
isPermutationBy f = go
286+
where
287+
f' = flip f
288+
289+
go [] [] = True
290+
go (x : xs) (y : ys)
291+
| f x y = go xs ys
292+
| otherwise = go (deleteBy f' y xs) (deleteBy f x ys)
293+
go [] (_ : _) = False
294+
go (_ : _) [] = False
295+
296+
-- Data.List.deleteBy :: (a -> a -> Bool) -> a -> [a] -> [a]
297+
deleteBy :: (a -> b -> Bool) -> a -> [b] -> [b]
298+
deleteBy _ _ [] = []
299+
deleteBy eq x (y:ys) = if x `eq` y then ys else y : deleteBy eq x ys
300+
301+
-- Same as 'equal' but doesn't compare the values.
302+
equalKeys :: (k -> k' -> Bool) -> HashMap k v -> HashMap k' v' -> Bool
303+
equalKeys eq t1 t2 = go (toList' t1 []) (toList' t2 [])
304+
where
305+
go (Leaf k1 l1 : tl1) (Leaf k2 l2 : tl2)
306+
| k1 == k2 && leafEq l1 l2
233307
= go tl1 tl2
234308
go (Collision k1 ary1 : tl1) (Collision k2 ary2 : tl2)
235309
| k1 == k2 && A.length ary1 == A.length ary2 &&
236-
L.null (A.toList ary1 L.\\ A.toList ary2)
310+
isPermutationBy leafEq (A.toList ary1) (A.toList ary2)
237311
= go tl1 tl2
238312
go [] [] = True
239313
go _ _ = False
240314

315+
leafEq (L k _) (L k' _) = eq k k'
316+
317+
#if MIN_VERSION_hashable(1,2,5)
318+
instance H.Hashable2 HashMap where
319+
liftHashWithSalt2 hk hv salt hm = go salt (toList' hm [])
320+
where
321+
-- go :: Int -> [HashMap k v] -> Int
322+
go s [] = s
323+
go s (Leaf _ l : tl)
324+
= s `hashLeafWithSalt` l `go` tl
325+
-- For collisions we hashmix hash value
326+
-- and then array of values' hashes sorted
327+
go s (Collision h a : tl)
328+
= (s `H.hashWithSalt` h) `hashCollisionWithSalt` a `go` tl
329+
go s (_ : tl) = s `go` tl
330+
331+
-- hashLeafWithSalt :: Int -> Leaf k v -> Int
332+
hashLeafWithSalt s (L k v) = (s `hk` k) `hv` v
333+
334+
-- hashCollisionWithSalt :: Int -> A.Array (Leaf k v) -> Int
335+
hashCollisionWithSalt s
336+
= L.foldl' H.hashWithSalt s . arrayHashesSorted s
337+
338+
-- arrayHashesSorted :: Int -> A.Array (Leaf k v) -> [Int]
339+
arrayHashesSorted s = L.sort . L.map (hashLeafWithSalt s) . A.toList
340+
341+
instance (Hashable k) => H.Hashable1 (HashMap k) where
342+
liftHashWithSalt = H.liftHashWithSalt2 H.hashWithSalt
343+
#endif
344+
241345
instance (Hashable k, Hashable v) => Hashable (HashMap k v) where
242346
hashWithSalt salt hm = go salt (toList' hm [])
243347
where
@@ -256,13 +360,10 @@ instance (Hashable k, Hashable v) => Hashable (HashMap k v) where
256360

257361
hashCollisionWithSalt :: Int -> A.Array (Leaf k v) -> Int
258362
hashCollisionWithSalt s
259-
= L.foldl' H.hashWithSalt s . arrayHashesSorted
260-
261-
arrayHashesSorted :: A.Array (Leaf k v) -> [Int]
262-
arrayHashesSorted = L.sort . L.map leafValueHash . A.toList
363+
= L.foldl' H.hashWithSalt s . arrayHashesSorted s
263364

264-
leafValueHash :: Leaf k v -> Int
265-
leafValueHash (L _ v) = H.hash v
365+
arrayHashesSorted :: Int -> A.Array (Leaf k v) -> [Int]
366+
arrayHashesSorted s = L.sort . L.map (hashLeafWithSalt s) . A.toList
266367

267368
-- Helper to get 'Leaf's and 'Collision's as a list.
268369
toList' :: HashMap k v -> [HashMap k v] -> [HashMap k v]

Data/HashSet.hs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ module Data.HashSet
7474

7575
import Control.DeepSeq (NFData(..))
7676
import Data.Data hiding (Typeable)
77-
import Data.HashMap.Base (HashMap, foldrWithKey)
77+
import Data.HashMap.Base (HashMap, foldrWithKey, equalKeys)
7878
import Data.Hashable (Hashable(hashWithSalt))
7979
#if __GLASGOW_HASKELL__ >= 711
8080
import Data.Semigroup (Semigroup(..), Monoid(..))
@@ -93,6 +93,14 @@ import Text.Read
9393
import qualified GHC.Exts as Exts
9494
#endif
9595

96+
#if MIN_VERSION_base(4,9,0)
97+
import Data.Functor.Classes
98+
#endif
99+
100+
#if MIN_VERSION_hashable(1,2,5)
101+
import qualified Data.Hashable.Lifted as H
102+
#endif
103+
96104
-- | A set of values. A set cannot contain duplicate values.
97105
newtype HashSet a = HashSet {
98106
asMap :: HashMap a ()
@@ -106,12 +114,15 @@ instance (NFData a) => NFData (HashSet a) where
106114
rnf = rnf . asMap
107115
{-# INLINE rnf #-}
108116

109-
instance (Hashable a, Eq a) => Eq (HashSet a) where
110-
-- This performs two passes over the tree.
111-
a == b = foldr f True b && size a == size b
112-
where f i = (&& i `member` a)
117+
instance (Eq a) => Eq (HashSet a) where
118+
HashSet a == HashSet b = equalKeys (==) a b
113119
{-# INLINE (==) #-}
114120

121+
#if MIN_VERSION_base(4,9,0)
122+
instance Eq1 HashSet where
123+
liftEq eq (HashSet a) (HashSet b) = equalKeys eq a b
124+
#endif
125+
115126
instance Foldable.Foldable HashSet where
116127
foldr = Data.HashSet.foldr
117128
{-# INLINE foldr #-}
@@ -140,6 +151,12 @@ instance (Eq a, Hashable a, Read a) => Read (HashSet a) where
140151

141152
readListPrec = readListPrecDefault
142153

154+
#if MIN_VERSION_base(4,9,0)
155+
instance Show1 HashSet where
156+
liftShowsPrec sp sl d m =
157+
showsUnaryWith (liftShowsPrec sp sl) "fromList" d (toList m)
158+
#endif
159+
143160
instance (Show a) => Show (HashSet a) where
144161
showsPrec d m = showParen (d > 10) $
145162
showString "fromList " . shows (toList m)
@@ -153,6 +170,11 @@ instance (Data a, Eq a, Hashable a) => Data (HashSet a) where
153170
dataTypeOf _ = hashSetDataType
154171
dataCast1 f = gcast1 f
155172

173+
#if MIN_VERSION_hashable(1,2,6)
174+
instance H.Hashable1 HashSet where
175+
liftHashWithSalt h s = H.liftHashWithSalt2 h hashWithSalt s . asMap
176+
#endif
177+
156178
instance (Hashable a) => Hashable (HashSet a) where
157179
hashWithSalt salt = hashWithSalt salt . asMap
158180

tests/HashMapProperties.hs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,9 @@ pHashable :: [(Key, Int)] -> [Int] -> Int -> Property
5454
pHashable xs is salt =
5555
x == y ==> hashWithSalt salt x === hashWithSalt salt y
5656
where
57-
ys = shuffle is xs
58-
x = HM.fromList xs
57+
xs' = L.nubBy (\(k,_) (k',_) -> k == k') xs
58+
ys = shuffle is xs'
59+
x = HM.fromList xs'
5960
y = HM.fromList ys
6061
-- Shuffle the list using indexes in the second
6162
shuffle :: [Int] -> [a] -> [a]

tests/HashSetProperties.hs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,9 @@ pHashable :: [Key] -> [Int] -> Int -> Property
5353
pHashable xs is salt =
5454
x == y ==> hashWithSalt salt x === hashWithSalt salt y
5555
where
56-
ys = shuffle is xs
57-
x = S.fromList xs
56+
xs' = L.nub xs
57+
ys = shuffle is xs'
58+
x = S.fromList xs'
5859
y = S.fromList ys
5960
shuffle idxs = L.map snd
6061
. L.sortBy (comparing fst)

unordered-containers.cabal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: unordered-containers
2-
version: 0.2.7.2
2+
version: 0.2.8.0
33
synopsis: Efficient hashing-based container types
44
description:
55
Efficient hashing-based container types. The containers have been

0 commit comments

Comments
 (0)