Skip to content

Commit 75a721b

Browse files
committed
Unboxing and streamlining Map maps
* Use an unboxed-sum version of `Maybe` to implement `mapMaybeWithKey`. This potentially (I suspect usually) allows all the `Maybe`s to be erased. * Comprehensive rewrite rules for both strict and lazy versions of `map`, `mapWithKey`, `mapMaybeWithKey`, and `filterWithKey` quickly get out of hand. Following `unordered-containers`, tame the mess by implementing both lazy and strict mapping functions in terms of versions that use unboxed results. Rewrite rules on these underlying functions will then apply uniformly. One concern: I found it a bit tricky to get the unfoldings I wanted; lots of things had to be marked `INLINABLE` explicitly.
1 parent 3db464d commit 75a721b

File tree

6 files changed

+293
-40
lines changed

6 files changed

+293
-40
lines changed

containers-tests/containers-tests.cabal

+2
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ library
104104
Utils.Containers.Internal.BitQueue
105105
Utils.Containers.Internal.BitUtil
106106
Utils.Containers.Internal.StrictPair
107+
Utils.Containers.Internal.UnboxedMaybe
108+
Utils.Containers.Internal.UnboxedSolo
107109
if impl(ghc >= 8.6.0)
108110
exposed-modules:
109111
Utils.NoThunks

containers/containers.cabal

+2
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ Library
6969
Utils.Containers.Internal.BitUtil
7070
Utils.Containers.Internal.BitQueue
7171
Utils.Containers.Internal.StrictPair
72+
Utils.Containers.Internal.UnboxedMaybe
73+
Utils.Containers.Internal.UnboxedSolo
7274

7375
other-modules:
7476
Prelude

containers/src/Data/Map/Internal.hs

+135-16
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
{-# LANGUAGE PatternGuards #-}
44
#if defined(__GLASGOW_HASKELL__)
55
{-# LANGUAGE DeriveLift #-}
6+
{-# LANGUAGE UnboxedTuples #-}
67
{-# LANGUAGE RoleAnnotations #-}
78
{-# LANGUAGE StandaloneDeriving #-}
89
{-# LANGUAGE Trustworthy #-}
@@ -236,7 +237,9 @@ module Data.Map.Internal (
236237
-- * Traversal
237238
-- ** Map
238239
, map
240+
, mapU
239241
, mapWithKey
242+
, mapWithKeyU
240243
, traverseWithKey
241244
, traverseMaybeWithKey
242245
, mapAccum
@@ -301,6 +304,7 @@ module Data.Map.Internal (
301304

302305
, mapMaybe
303306
, mapMaybeWithKey
307+
, mapMaybeWithKeyU
304308
, mapEither
305309
, mapEitherWithKey
306310

@@ -407,6 +411,8 @@ import Data.Data
407411
import qualified Control.Category as Category
408412
import Data.Coerce
409413
#endif
414+
import Utils.Containers.Internal.UnboxedMaybe
415+
import Utils.Containers.Internal.UnboxedSolo
410416

411417

412418
{--------------------------------------------------------------------
@@ -2849,6 +2855,7 @@ isProperSubmapOfBy f t1 t2
28492855
filter :: (a -> Bool) -> Map k a -> Map k a
28502856
filter p m
28512857
= filterWithKey (\_ x -> p x) m
2858+
{-# INLINE filter #-}
28522859

28532860
-- | \(O(n)\). Filter all keys\/values that satisfy the predicate.
28542861
--
@@ -2863,6 +2870,32 @@ filterWithKey p t@(Bin _ kx x l r)
28632870
| otherwise = link2 pl pr
28642871
where !pl = filterWithKey p l
28652872
!pr = filterWithKey p r
2873+
{-# NOINLINE [1] filterWithKey #-}
2874+
2875+
{-# RULES
2876+
"filterWK/filterWK" forall p q m. filterWithKey p (filterWithKey q m) =
2877+
filterWithKey (\k x -> q k x && p k x) m
2878+
"filterWK/mapU" forall p f m. filterWithKey p (mapU f m) =
2879+
mapMaybeWithKeyU (\k x -> case f x of
2880+
SoloU y
2881+
| p k y -> JustU y
2882+
| otherwise -> NothingU) m
2883+
"filterWK/mapWK#" forall p f m. filterWithKey p (mapWithKeyU f m) =
2884+
mapMaybeWithKeyU (\k x -> case f k x of
2885+
SoloU y
2886+
| p k y -> JustU y
2887+
| otherwise -> NothingU) m
2888+
"mapU/filterWK" forall f p m. mapU f (filterWithKey p m) =
2889+
mapMaybeWithKeyU (\k x ->
2890+
if p k x
2891+
then case f x of SoloU y -> JustU y
2892+
else NothingU) m
2893+
"mapWK#/filterWK" forall f p m. mapWithKeyU f (filterWithKey p m) =
2894+
mapMaybeWithKeyU (\k x ->
2895+
if p k x
2896+
then case f k x of SoloU y -> JustU y
2897+
else NothingU) m
2898+
#-}
28662899

28672900
-- | \(O(n)\). Filter keys and values using an 'Applicative'
28682901
-- predicate.
@@ -2977,17 +3010,60 @@ partitionWithKey p0 t0 = toPair $ go p0 t0
29773010

29783011
mapMaybe :: (a -> Maybe b) -> Map k a -> Map k b
29793012
mapMaybe f = mapMaybeWithKey (\_ x -> f x)
3013+
{-# INLINE mapMaybe #-}
29803014

29813015
-- | \(O(n)\). Map keys\/values and collect the 'Just' results.
29823016
--
29833017
-- > let f k _ = if k < 5 then Just ("key : " ++ (show k)) else Nothing
29843018
-- > mapMaybeWithKey f (fromList [(5,"a"), (3,"b")]) == singleton 3 "key : 3"
29853019

29863020
mapMaybeWithKey :: (k -> a -> Maybe b) -> Map k a -> Map k b
3021+
{-
29873022
mapMaybeWithKey _ Tip = Tip
29883023
mapMaybeWithKey f (Bin _ kx x l r) = case f kx x of
29893024
Just y -> link kx y (mapMaybeWithKey f l) (mapMaybeWithKey f r)
29903025
Nothing -> link2 (mapMaybeWithKey f l) (mapMaybeWithKey f r)
3026+
-}
3027+
mapMaybeWithKey f = \m ->
3028+
mapMaybeWithKeyU (\k x -> toMaybeU (f k x)) m
3029+
{-# INLINE mapMaybeWithKey #-}
3030+
3031+
mapMaybeWithKeyU :: (k -> a -> MaybeU b) -> Map k a -> Map k b
3032+
mapMaybeWithKeyU _ Tip = Tip
3033+
mapMaybeWithKeyU f (Bin _ kx x l r) = case f kx x of
3034+
JustU y -> link kx y (mapMaybeWithKeyU f l) (mapMaybeWithKeyU f r)
3035+
NothingU -> link2 (mapMaybeWithKeyU f l) (mapMaybeWithKeyU f r)
3036+
{-# NOINLINE [1] mapMaybeWithKeyU #-}
3037+
3038+
{-# RULES
3039+
"mapMaybeWK#/mapU" forall f g m. mapMaybeWithKeyU f (mapU g m) =
3040+
mapMaybeWithKeyU (\k x -> case g x of SoloU y -> f k y) m
3041+
"mapU/mapMaybeWK#" forall f g m. mapU f (mapMaybeWithKeyU g m) =
3042+
mapMaybeWithKeyU
3043+
(\k x -> case g k x of
3044+
NothingU -> NothingU
3045+
JustU y -> case f y of SoloU z -> JustU z) m
3046+
"mapMaybeWK#/mapWK#" forall f g m. mapMaybeWithKeyU f (mapWithKeyU g m) =
3047+
mapMaybeWithKeyU (\k x -> case g k x of SoloU y -> f k y) m
3048+
"mapWK#/mapMaybeWK#" forall f g m. mapWithKeyU f (mapMaybeWithKeyU g m) =
3049+
mapMaybeWithKeyU
3050+
(\k x -> case g k x of
3051+
NothingU -> NothingU
3052+
JustU y -> case f k y of SoloU z -> JustU z) m
3053+
"mapMaybeWK#/mapMaybeWK#" forall f g m. mapMaybeWithKeyU f (mapMaybeWithKeyU g m) =
3054+
mapMaybeWithKeyU
3055+
(\k x -> case g k x of
3056+
NothingU -> NothingU
3057+
JustU y -> f k y) m
3058+
"mapMaybeWK#/filterWK" forall f p m. mapMaybeWithKeyU f (filterWithKey p m) =
3059+
mapMaybeWithKeyU (\k x -> if p k x then f k x else NothingU) m
3060+
"filterWK/mapMaybeWK#" forall p f m. filterWithKey p (mapMaybeWithKeyU f m) =
3061+
mapMaybeWithKeyU (\k x -> case f k x of
3062+
NothingU -> NothingU
3063+
JustU y
3064+
| p k y -> JustU y
3065+
| otherwise -> NothingU) m
3066+
#-}
29913067

29923068
-- | \(O(n)\). Traverse keys\/values and collect the 'Just' results.
29933069
--
@@ -3045,17 +3121,41 @@ mapEitherWithKey f0 t0 = toPair $ go f0 t0
30453121
-- > map (++ "x") (fromList [(5,"a"), (3,"b")]) == fromList [(3, "bx"), (5, "ax")]
30463122

30473123
map :: (a -> b) -> Map k a -> Map k b
3124+
#ifdef __GLASGOW_HASKELL__
3125+
-- We define map using mapU solely to reduce the number of rewrite
3126+
-- rules we need.
3127+
map f = mapU (\x -> SoloU (f x))
3128+
-- We delay inlinability of map to support map/coerce. While a
3129+
-- mapU/coerce rule seems to work when everything is done just so,
3130+
-- it feels too brittle to me for now (GHC 9.4).
3131+
{-# INLINABLE [1] map #-}
3132+
#else
30483133
map f = go where
30493134
go Tip = Tip
30503135
go (Bin sx kx x l r) = Bin sx kx (f x) (go l) (go r)
3051-
-- We use a `go` function to allow `map` to inline. This makes
3052-
-- a big difference if someone uses `map (const x) m` instead
3053-
-- of `x <$ m`; it doesn't seem to do any harm.
3136+
#endif
30543137

30553138
#ifdef __GLASGOW_HASKELL__
3056-
{-# NOINLINE [1] map #-}
3139+
mapU :: (a -> SoloU b) -> Map k a -> Map k b
3140+
mapU f = go where
3141+
go Tip = Tip
3142+
go (Bin sx kx x l r)
3143+
| SoloU y <- f x
3144+
= Bin sx kx y (go l) (go r)
3145+
#if defined (__GLASGOW_HASKELL__) && (__GLASGOW_HASKELL__ >= 806) && (__GLASGOW_HASKELL__ < 810)
3146+
-- Something goes wrong checking SoloU completeness
3147+
-- in these versions
3148+
go _ = error "impossible"
3149+
#endif
3150+
-- We use a `go` function to allow `mapU` to inline. Without this,
3151+
-- we'd slow down both strict and lazy map, which wouldn't be great.
3152+
-- This also lets us avoid a custom implementation of <$
3153+
3154+
-- We don't let mapU inline until phase 0 because we need a step
3155+
-- after map inlines.
3156+
{-# NOINLINE [0] mapU #-}
30573157
{-# RULES
3058-
"map/map" forall f g xs . map f (map g xs) = map (f . g) xs
3158+
"mapU/mapU" forall f g xs . mapU f (mapU g xs) = mapU (\x -> case g x of SoloU y -> f y) xs
30593159
"map/coerce" map coerce = coerce
30603160
#-}
30613161
#endif
@@ -3066,21 +3166,38 @@ map f = go where
30663166
-- > mapWithKey f (fromList [(5,"a"), (3,"b")]) == fromList [(3, "3:b"), (5, "5:a")]
30673167

30683168
mapWithKey :: (k -> a -> b) -> Map k a -> Map k b
3169+
#ifdef __GLASGOW_HASKELL__
3170+
mapWithKey f = mapWithKeyU (\k a -> SoloU (f k a))
3171+
{-# INLINABLE mapWithKey #-}
3172+
#else
30693173
mapWithKey _ Tip = Tip
30703174
mapWithKey f (Bin sx kx x l r) = Bin sx kx (f kx x) (mapWithKey f l) (mapWithKey f r)
3175+
#endif
3176+
3177+
-- | A version of 'mapWithKey' that takes a function producing a unary
3178+
-- unboxed tuple.
3179+
mapWithKeyU :: (k -> a -> SoloU b) -> Map k a -> Map k b
3180+
mapWithKeyU f = go where
3181+
go Tip = Tip
3182+
go (Bin sx kx x l r)
3183+
| SoloU y <- f kx x
3184+
= Bin sx kx y (go l) (go r)
3185+
#if defined (__GLASGOW_HASKELL__) && (__GLASGOW_HASKELL__ >= 806) && (__GLASGOW_HASKELL__ < 810)
3186+
-- Something goes wrong checking SoloU completeness
3187+
-- in these versions
3188+
go _ = error "impossible"
3189+
#endif
30713190

30723191
#ifdef __GLASGOW_HASKELL__
3073-
{-# NOINLINE [1] mapWithKey #-}
3192+
{-# NOINLINE [1] mapWithKeyU #-}
30743193
{-# RULES
3075-
"mapWithKey/mapWithKey" forall f g xs . mapWithKey f (mapWithKey g xs) =
3076-
mapWithKey (\k a -> f k (g k a)) xs
3077-
"mapWithKey/map" forall f g xs . mapWithKey f (map g xs) =
3078-
mapWithKey (\k a -> f k (g a)) xs
3079-
"map/mapWithKey" forall f g xs . map f (mapWithKey g xs) =
3080-
mapWithKey (\k a -> f (g k a)) xs
3194+
"mapWK#/mapWK#" forall f g xs. mapWithKeyU f (mapWithKeyU g xs) = mapWithKeyU (\k x -> case g k x of SoloU y -> f k y) xs
3195+
"mapWK#/mapU" forall f g xs. mapWithKeyU f (mapU g xs) = mapWithKeyU (\k x -> case g x of SoloU y -> f k y) xs
3196+
"mapU/mapWK#" forall f g xs. mapU f (mapWithKeyU g xs) = mapWithKeyU (\k x -> case g k x of SoloU y -> f y) xs
30813197
#-}
30823198
#endif
30833199

3200+
30843201
-- | \(O(n)\).
30853202
-- @'traverseWithKey' f m == 'fromList' <$> 'traverse' (\(k, v) -> (,) k <$> f k v) ('toList' m)@
30863203
-- That is, behaves exactly like a regular 'traverse' except that the traversing
@@ -4195,10 +4312,12 @@ instance (Ord k, Read k) => Read1 (Map k) where
41954312
--------------------------------------------------------------------}
41964313
instance Functor (Map k) where
41974314
fmap f m = map f m
4198-
#ifdef __GLASGOW_HASKELL__
4199-
_ <$ Tip = Tip
4200-
a <$ (Bin sx kx _ l r) = Bin sx kx a (a <$ l) (a <$ r)
4201-
#endif
4315+
{-# INLINABLE fmap #-}
4316+
a <$ m = map (const a) m
4317+
-- For some reason, we need an explicit INLINE or INLINABLE pragma to
4318+
-- get the unfolding to use map rather than expanding into a recursive
4319+
-- function that RULES will never match. Hmm....
4320+
{-# INLINABLE (<$) #-}
42024321

42034322
-- | Traverses in order of increasing key.
42044323
instance Traversable (Map k) where

containers/src/Data/Map/Strict/Internal.hs

+22-24
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
{-# LANGUAGE CPP #-}
22
{-# LANGUAGE BangPatterns #-}
33
#if defined(__GLASGOW_HASKELL__)
4+
{-# LANGUAGE MagicHash #-}
5+
{-# LANGUAGE PatternSynonyms #-}
46
{-# LANGUAGE Trustworthy #-}
7+
{-# LANGUAGE UnboxedTuples #-}
58
#endif
69
{-# OPTIONS_HADDOCK not-home #-}
710

@@ -420,6 +423,8 @@ import Data.Semigroup (Arg (..))
420423
import qualified Data.Set.Internal as Set
421424
import qualified Data.Map.Internal as L
422425
import Utils.Containers.Internal.StrictPair
426+
import Utils.Containers.Internal.UnboxedMaybe (pattern NothingU, pattern JustU)
427+
import Utils.Containers.Internal.UnboxedSolo (pattern SoloU)
423428

424429
import Data.Bits (shiftL, shiftR)
425430
#ifdef __GLASGOW_HASKELL__
@@ -1271,17 +1276,26 @@ mergeWithKey f g1 g2 = go
12711276

12721277
mapMaybe :: (a -> Maybe b) -> Map k a -> Map k b
12731278
mapMaybe f = mapMaybeWithKey (\_ x -> f x)
1279+
{-# INLINABLE mapMaybe #-}
12741280

12751281
-- | \(O(n)\). Map keys\/values and collect the 'Just' results.
12761282
--
12771283
-- > let f k _ = if k < 5 then Just ("key : " ++ (show k)) else Nothing
12781284
-- > mapMaybeWithKey f (fromList [(5,"a"), (3,"b")]) == singleton 3 "key : 3"
12791285

12801286
mapMaybeWithKey :: (k -> a -> Maybe b) -> Map k a -> Map k b
1287+
{-
1288+
-
12811289
mapMaybeWithKey _ Tip = Tip
12821290
mapMaybeWithKey f (Bin _ kx x l r) = case f kx x of
12831291
Just y -> y `seq` link kx y (mapMaybeWithKey f l) (mapMaybeWithKey f r)
12841292
Nothing -> link2 (mapMaybeWithKey f l) (mapMaybeWithKey f r)
1293+
-}
1294+
mapMaybeWithKey f = \m ->
1295+
L.mapMaybeWithKeyU (\k x -> case f k x of
1296+
Nothing -> NothingU
1297+
Just !a -> JustU a) m
1298+
{-# INLINABLE mapMaybeWithKey #-}
12851299

12861300
-- | \(O(n)\). Traverse keys\/values and collect the 'Just' results.
12871301
--
@@ -1340,19 +1354,16 @@ mapEitherWithKey f0 t0 = toPair $ go f0 t0
13401354
-- > map (++ "x") (fromList [(5,"a"), (3,"b")]) == fromList [(3, "bx"), (5, "ax")]
13411355

13421356
map :: (a -> b) -> Map k a -> Map k b
1357+
#ifdef __GLASGOW_HASKELL__
1358+
map f = L.mapU (\x -> let !y = f x in SoloU y)
1359+
{-# INLINABLE map #-}
1360+
#else
13431361
map f = go
13441362
where
13451363
go Tip = Tip
13461364
go (Bin sx kx x l r) = let !x' = f x in Bin sx kx x' (go l) (go r)
13471365
-- We use `go` to let `map` inline. This is important if `f` is a constant
13481366
-- function.
1349-
1350-
#ifdef __GLASGOW_HASKELL__
1351-
{-# NOINLINE [1] map #-}
1352-
{-# RULES
1353-
"map/map" forall f g xs . map f (map g xs) = map (\x -> f $! g x) xs
1354-
"map/mapL" forall f g xs . map f (L.map g xs) = map (\x -> f (g x)) xs
1355-
#-}
13561367
#endif
13571368

13581369
-- | \(O(n)\). Map a function over all values in the map.
@@ -1361,27 +1372,14 @@ map f = go
13611372
-- > mapWithKey f (fromList [(5,"a"), (3,"b")]) == fromList [(3, "3:b"), (5, "5:a")]
13621373

13631374
mapWithKey :: (k -> a -> b) -> Map k a -> Map k b
1375+
#ifdef __GLASGOW_HASKELL__
1376+
mapWithKey f = L.mapWithKeyU (\k x -> let !y = f k x in SoloU y)
1377+
{-# INLINABLE mapWithKey #-}
1378+
#else
13641379
mapWithKey _ Tip = Tip
13651380
mapWithKey f (Bin sx kx x l r) =
13661381
let x' = f kx x
13671382
in x' `seq` Bin sx kx x' (mapWithKey f l) (mapWithKey f r)
1368-
1369-
#ifdef __GLASGOW_HASKELL__
1370-
{-# NOINLINE [1] mapWithKey #-}
1371-
{-# RULES
1372-
"mapWithKey/mapWithKey" forall f g xs . mapWithKey f (mapWithKey g xs) =
1373-
mapWithKey (\k a -> f k $! g k a) xs
1374-
"mapWithKey/mapWithKeyL" forall f g xs . mapWithKey f (L.mapWithKey g xs) =
1375-
mapWithKey (\k a -> f k (g k a)) xs
1376-
"mapWithKey/map" forall f g xs . mapWithKey f (map g xs) =
1377-
mapWithKey (\k a -> f k $! g a) xs
1378-
"mapWithKey/mapL" forall f g xs . mapWithKey f (L.map g xs) =
1379-
mapWithKey (\k a -> f k (g a)) xs
1380-
"map/mapWithKey" forall f g xs . map f (mapWithKey g xs) =
1381-
mapWithKey (\k a -> f $! g k a) xs
1382-
"map/mapWithKeyL" forall f g xs . map f (L.mapWithKey g xs) =
1383-
mapWithKey (\k a -> f (g k a)) xs
1384-
#-}
13851383
#endif
13861384

13871385
-- | \(O(n)\).

0 commit comments

Comments
 (0)