Skip to content

Commit 9d67eab

Browse files
committed
Make Strict.traverseWithKey actually strict
Fixes #192
1 parent cba2e43 commit 9d67eab

File tree

5 files changed

+105
-12
lines changed

5 files changed

+105
-12
lines changed

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
* Add `HashMap.keysSet`.
66

7+
* Make `HashMap.Strict.traverseWithKey` force the results before
8+
installing them in the map.
9+
710
## 0.2.9.0
811

912
* Add `Ord/Ord1/Ord2` instances. (Thanks, Oleg Grenrus)

Data/HashMap/Array.hs

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,16 @@ module Data.HashMap.Array
4646
, map
4747
, map'
4848
, traverse
49+
, traverse'
4950
, filter
5051
, toList
52+
, fromList
5153
) where
5254

53-
import qualified Data.Traversable as Traversable
54-
#if __GLASGOW_HASKELL__ < 709
55-
import Control.Applicative (Applicative)
55+
#if !MIN_VERSION_base(4,8,0)
56+
import Control.Applicative (Applicative (..), (<$>))
5657
#endif
58+
import Control.Applicative (liftA2)
5759
import Control.DeepSeq
5860
import GHC.Exts(Int(..), Int#, reallyUnsafePtrEquality#, tagToEnum#, unsafeCoerce#, State#)
5961
import GHC.ST (ST(..))
@@ -473,11 +475,42 @@ fromList n xs0 =
473475
toList :: Array a -> [a]
474476
toList = foldr (:) []
475477

478+
data SList a = SCons !a (SList a) | SNil
479+
480+
traverseToSList
481+
:: Applicative f
482+
=> (a -> f b) -> [a] -> f (SList b)
483+
traverseToSList f = go
484+
where
485+
go (a : as) = liftA2 SCons (f a) (go as)
486+
go [] = pure SNil
487+
488+
_slength :: SList a -> Int
489+
_slength = go 0 where
490+
go !acc SNil = acc
491+
go acc (SCons _ xs) = go (acc + 1) xs
492+
493+
fromSList :: Int -> SList a -> Array a
494+
fromSList n xs0 =
495+
CHECK_EQ("fromSList", n, _slength xs0)
496+
run $ do
497+
mary <- new_ n
498+
go xs0 mary 0
499+
where
500+
go SNil !mary !_ = return mary
501+
go (SCons x xs) mary i = do write mary i x
502+
go xs mary (i + 1)
503+
476504
traverse :: Applicative f => (a -> f b) -> Array a -> f (Array b)
477505
traverse f = \ ary -> fromList (length ary) `fmap`
478506
Traversable.traverse f (toList ary)
479507
{-# INLINE [1] traverse #-}
480508

509+
traverse' :: Applicative f => (a -> f b) -> Array a -> f (Array b)
510+
traverse' f = \ary -> fromSList (length ary) `fmap`
511+
traverseToSList f (toList ary)
512+
{-# INLINE [1] traverse' #-}
513+
481514
-- Traversing in ST, we don't need to make a list; we
482515
-- can just do it directly.
483516
traverseST :: (a -> ST s b) -> Array a -> ST s (Array b)
@@ -498,6 +531,26 @@ traverseST f = \ ary0 ->
498531
"traverse/ST" forall f. traverse f = traverseST f
499532
#-}
500533

534+
-- Traversing in ST, we don't need to make a list; we
535+
-- can just do it directly.
536+
traverseST' :: (a -> ST s b) -> Array a -> ST s (Array b)
537+
traverseST' f = \ ary0 ->
538+
let
539+
!len = length ary0
540+
go k mary
541+
| k == len = return mary
542+
| otherwise = do
543+
x <- indexM ary0 k
544+
!y <- f x
545+
write mary k y
546+
go (k + 1) mary
547+
in new_ len >>= (go 0 >=> unsafeFreeze)
548+
{-# INLINE traverseST' #-}
549+
550+
{-# RULES
551+
"traverse'/ST" forall f. traverse' f = traverseST' f
552+
#-}
553+
501554
filter :: (a -> Bool) -> Array a -> Array a
502555
filter p = \ ary ->
503556
let !n = length ary

Data/HashMap/Base.hs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1432,18 +1432,25 @@ map f = mapWithKey (const f)
14321432
-- TODO: We should be able to use mutation to create the new
14331433
-- 'HashMap'.
14341434

1435-
-- | /O(n)/ Transform this map by accumulating an Applicative result
1436-
-- from every value.
1437-
traverseWithKey :: Applicative f => (k -> v1 -> f v2) -> HashMap k v1
1438-
-> f (HashMap k v2)
1435+
-- | /O(n)/ Perform an 'Applicative' action for each key-value pair
1436+
-- in a 'HashMap' and produce a 'HashMap' of all the results.
1437+
--
1438+
-- Note: the order in which the actions occur is unspecified. In particular,
1439+
-- when the map contains hash collisions, the order in which the actions
1440+
-- associated with the keys involved will depend in an unspecified way on
1441+
-- their insertion order.
1442+
traverseWithKey
1443+
:: Applicative f
1444+
=> (k -> v1 -> f v2)
1445+
-> HashMap k v1 -> f (HashMap k v2)
14391446
traverseWithKey f = go
14401447
where
14411448
go Empty = pure Empty
14421449
go (Leaf h (L k v)) = Leaf h . L k <$> f k v
14431450
go (BitmapIndexed b ary) = BitmapIndexed b <$> A.traverse go ary
14441451
go (Full ary) = Full <$> A.traverse go ary
14451452
go (Collision h ary) =
1446-
Collision h <$> A.traverse (\ (L k v) -> L k <$> f k v) ary
1453+
Collision h <$> A.traverse' (\ (L k v) -> L k <$> f k v) ary
14471454
{-# INLINE traverseWithKey #-}
14481455

14491456
------------------------------------------------------------------------

Data/HashMap/Strict/Base.hs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ import qualified Data.HashMap.Base as HM
104104
import Data.HashMap.Base hiding (
105105
alter, alterF, adjust, fromList, fromListWith, insert, insertWith,
106106
differenceWith, intersectionWith, intersectionWithKey, map, mapWithKey,
107-
mapMaybe, mapMaybeWithKey, singleton, update, unionWith, unionWithKey)
107+
mapMaybe, mapMaybeWithKey, singleton, update, unionWith, unionWithKey,
108+
traverseWithKey)
108109
import Data.HashMap.Unsafe (runST)
109110
#if MIN_VERSION_base(4,8,0)
110111
import Data.Functor.Identity
@@ -522,8 +523,31 @@ mapMaybe :: (v1 -> Maybe v2) -> HashMap k v1 -> HashMap k v2
522523
mapMaybe f = mapMaybeWithKey (const f)
523524
{-# INLINE mapMaybe #-}
524525

525-
526-
-- TODO: Should we add a strict traverseWithKey?
526+
-- | /O(n)/ Perform an 'Applicative' action for each key-value pair
527+
-- in a 'HashMap' and produce a 'HashMap' of all the results. Each 'HashMap'
528+
-- will be strict in all its values.
529+
--
530+
-- @
531+
-- traverseWithKey f = fmap ('map' id) . "Data.HashMap.Lazy".'Data.HashMap.Lazy.traverseWithKey' f
532+
-- @
533+
--
534+
-- Note: the order in which the actions occur is unspecified. In particular,
535+
-- when the map contains hash collisions, the order in which the actions
536+
-- associated with the keys involved will depend in an unspecified way on
537+
-- their insertion order.
538+
traverseWithKey
539+
:: Applicative f
540+
=> (k -> v1 -> f v2)
541+
-> HashMap k v1 -> f (HashMap k v2)
542+
traverseWithKey f = go
543+
where
544+
go Empty = pure Empty
545+
go (Leaf h (L k v)) = leaf h k <$> f k v
546+
go (BitmapIndexed b ary) = BitmapIndexed b <$> A.traverse' go ary
547+
go (Full ary) = Full <$> A.traverse' go ary
548+
go (Collision h ary) =
549+
Collision h <$> A.traverse' (\ (L k v) -> (L k $!) <$> f k v) ary
550+
{-# INLINE traverseWithKey #-}
527551

528552
------------------------------------------------------------------------
529553
-- * Difference and intersection
@@ -643,5 +667,5 @@ updateOrSnocWithKey f k0 v0 ary0 = go k0 v0 ary0 0 (A.length ary0)
643667
-- inserted into the constructor.
644668

645669
leaf :: Hash -> k -> v -> HashMap k v
646-
leaf h k !v = Leaf h (L k v)
670+
leaf h k = \ !v -> Leaf h (L k v)
647671
{-# INLINE leaf #-}

tests/HashMapProperties.hs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,11 @@ pUnions xss = M.toAscList (M.unions (map M.fromList xss)) ==
246246
pMap :: [(Key, Int)] -> Bool
247247
pMap = M.map (+ 1) `eq_` HM.map (+ 1)
248248

249+
pTraverse :: [(Key, Int)] -> Bool
250+
pTraverse xs =
251+
L.sort (fmap (L.sort . M.toList) (M.traverseWithKey (\_ v -> [v + 1, v + 2]) (M.fromList (take 10 xs))))
252+
== L.sort (fmap (L.sort . HM.toList) (HM.traverseWithKey (\_ v -> [v + 1, v + 2]) (HM.fromList (take 10 xs))))
253+
249254
------------------------------------------------------------------------
250255
-- ** Difference and intersection
251256

@@ -382,6 +387,7 @@ tests =
382387
, testProperty "unions" pUnions
383388
-- Transformations
384389
, testProperty "map" pMap
390+
, testProperty "traverse" pTraverse
385391
-- Folds
386392
, testGroup "folds"
387393
[ testProperty "foldr" pFoldr

0 commit comments

Comments
 (0)