Skip to content

Commit 366a181

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 366a181

File tree

4 files changed

+183
-45
lines changed

4 files changed

+183
-45
lines changed

containers/containers.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ Library
6969
Utils.Containers.Internal.BitUtil
7070
Utils.Containers.Internal.BitQueue
7171
Utils.Containers.Internal.StrictPair
72+
Utils.Containers.Internal.UnboxedMaybe
7273

7374
other-modules:
7475
Prelude

containers/src/Data/Map/Internal.hs

Lines changed: 123 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,15 @@
33
{-# LANGUAGE PatternGuards #-}
44
#if defined(__GLASGOW_HASKELL__)
55
{-# LANGUAGE DeriveLift #-}
6+
{-# LANGUAGE MagicHash #-}
7+
{-# LANGUAGE UnboxedTuples #-}
68
{-# LANGUAGE RoleAnnotations #-}
79
{-# LANGUAGE StandaloneDeriving #-}
810
{-# LANGUAGE Trustworthy #-}
911
{-# LANGUAGE TypeFamilies #-}
1012
#endif
1113
#define USE_MAGIC_PROXY 1
1214

13-
#ifdef USE_MAGIC_PROXY
14-
{-# LANGUAGE MagicHash #-}
15-
#endif
16-
1715
{-# OPTIONS_HADDOCK not-home #-}
1816

1917
#include "containers.h"
@@ -236,7 +234,9 @@ module Data.Map.Internal (
236234
-- * Traversal
237235
-- ** Map
238236
, map
237+
, map#
239238
, mapWithKey
239+
, mapWithKey#
240240
, traverseWithKey
241241
, traverseMaybeWithKey
242242
, mapAccum
@@ -301,6 +301,7 @@ module Data.Map.Internal (
301301

302302
, mapMaybe
303303
, mapMaybeWithKey
304+
, mapMaybeWithKey#
304305
, mapEither
305306
, mapEitherWithKey
306307

@@ -407,6 +408,7 @@ import Data.Data
407408
import qualified Control.Category as Category
408409
import Data.Coerce
409410
#endif
411+
import Utils.Containers.Internal.UnboxedMaybe
410412

411413

412414
{--------------------------------------------------------------------
@@ -2849,6 +2851,7 @@ isProperSubmapOfBy f t1 t2
28492851
filter :: (a -> Bool) -> Map k a -> Map k a
28502852
filter p m
28512853
= filterWithKey (\_ x -> p x) m
2854+
{-# INLINE filter #-}
28522855

28532856
-- | \(O(n)\). Filter all keys\/values that satisfy the predicate.
28542857
--
@@ -2863,6 +2866,32 @@ filterWithKey p t@(Bin _ kx x l r)
28632866
| otherwise = link2 pl pr
28642867
where !pl = filterWithKey p l
28652868
!pr = filterWithKey p r
2869+
{-# NOINLINE [1] filterWithKey #-}
2870+
2871+
{-# RULES
2872+
"filterWK/filterWK" forall p q m. filterWithKey p (filterWithKey q m) =
2873+
filterWithKey (\k x -> q k x && p k x) m
2874+
"filterWK/map#" forall p f m. filterWithKey p (map# f m) =
2875+
mapMaybeWithKey# (\k x -> case f x of
2876+
(# y #)
2877+
| p k y -> Just# y
2878+
| otherwise -> Nothing#) m
2879+
"filterWK/mapWK#" forall p f m. filterWithKey p (mapWithKey# f m) =
2880+
mapMaybeWithKey# (\k x -> case f k x of
2881+
(# y #)
2882+
| p k y -> Just# y
2883+
| otherwise -> Nothing#) m
2884+
"map#/filterWK" forall f p m. map# f (filterWithKey p m) =
2885+
mapMaybeWithKey# (\k x ->
2886+
if p k x
2887+
then case f x of (# y #) -> Just# y
2888+
else Nothing#) m
2889+
"mapWK#/filterWK" forall f p m. mapWithKey# f (filterWithKey p m) =
2890+
mapMaybeWithKey# (\k x ->
2891+
if p k x
2892+
then case f k x of (# y #) -> Just# y
2893+
else Nothing#) m
2894+
#-}
28662895

28672896
-- | \(O(n)\). Filter keys and values using an 'Applicative'
28682897
-- predicate.
@@ -2977,17 +3006,60 @@ partitionWithKey p0 t0 = toPair $ go p0 t0
29773006

29783007
mapMaybe :: (a -> Maybe b) -> Map k a -> Map k b
29793008
mapMaybe f = mapMaybeWithKey (\_ x -> f x)
3009+
{-# INLINE mapMaybe #-}
29803010

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

29863016
mapMaybeWithKey :: (k -> a -> Maybe b) -> Map k a -> Map k b
3017+
{-
29873018
mapMaybeWithKey _ Tip = Tip
29883019
mapMaybeWithKey f (Bin _ kx x l r) = case f kx x of
29893020
Just y -> link kx y (mapMaybeWithKey f l) (mapMaybeWithKey f r)
29903021
Nothing -> link2 (mapMaybeWithKey f l) (mapMaybeWithKey f r)
3022+
-}
3023+
mapMaybeWithKey f = \m ->
3024+
mapMaybeWithKey# (\k x -> toMaybe# (f k x)) m
3025+
{-# INLINE mapMaybeWithKey #-}
3026+
3027+
mapMaybeWithKey# :: (k -> a -> Maybe# b) -> Map k a -> Map k b
3028+
mapMaybeWithKey# _ Tip = Tip
3029+
mapMaybeWithKey# f (Bin _ kx x l r) = case f kx x of
3030+
Just# y -> link kx y (mapMaybeWithKey# f l) (mapMaybeWithKey# f r)
3031+
Nothing# -> link2 (mapMaybeWithKey# f l) (mapMaybeWithKey# f r)
3032+
{-# NOINLINE [1] mapMaybeWithKey# #-}
3033+
3034+
{-# RULES
3035+
"mapMaybeWK#/map#" forall f g m. mapMaybeWithKey# f (map# g m) =
3036+
mapMaybeWithKey# (\k x -> case g x of (# y #) -> f k y) m
3037+
"map#/mapMaybeWK#" forall f g m. map# f (mapMaybeWithKey# g m) =
3038+
mapMaybeWithKey#
3039+
(\k x -> case g k x of
3040+
Nothing# -> Nothing#
3041+
Just# y -> case f y of (# z #) -> Just# z) m
3042+
"mapMaybeWK#/mapWK#" forall f g m. mapMaybeWithKey# f (mapWithKey# g m) =
3043+
mapMaybeWithKey# (\k x -> case g k x of (# y #) -> f k y) m
3044+
"mapWK#/mapMaybeWK#" forall f g m. mapWithKey# f (mapMaybeWithKey# g m) =
3045+
mapMaybeWithKey#
3046+
(\k x -> case g k x of
3047+
Nothing# -> Nothing#
3048+
Just# y -> case f k y of (# z #) -> Just# z) m
3049+
"mapMaybeWK#/mapMaybeWK#" forall f g m. mapMaybeWithKey# f (mapMaybeWithKey# g m) =
3050+
mapMaybeWithKey#
3051+
(\k x -> case g k x of
3052+
Nothing# -> Nothing#
3053+
Just# y -> f k y) m
3054+
"mapMaybeWK#/filterWK" forall f p m. mapMaybeWithKey# f (filterWithKey p m) =
3055+
mapMaybeWithKey# (\k x -> if p k x then f k x else Nothing#) m
3056+
"filterWK/mapMaybeWK#" forall p f m. filterWithKey p (mapMaybeWithKey# f m) =
3057+
mapMaybeWithKey# (\k x -> case f k x of
3058+
Nothing# -> Nothing#
3059+
Just# y
3060+
| p k y -> Just# y
3061+
| otherwise -> Nothing#) m
3062+
#-}
29913063

29923064
-- | \(O(n)\). Traverse keys\/values and collect the 'Just' results.
29933065
--
@@ -3045,18 +3117,34 @@ mapEitherWithKey f0 t0 = toPair $ go f0 t0
30453117
-- > map (++ "x") (fromList [(5,"a"), (3,"b")]) == fromList [(3, "bx"), (5, "ax")]
30463118

30473119
map :: (a -> b) -> Map k a -> Map k b
3120+
#ifdef __GLASGOW_HASKELL__
3121+
-- We define map using map# solely to reduce the number of rewrite
3122+
-- rules we need.
3123+
map f = map# (\x -> (# f x #))
3124+
{-# INLINABLE map #-}
3125+
#else
30483126
map f = go where
30493127
go Tip = Tip
30503128
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.
3129+
#endif
30543130

30553131
#ifdef __GLASGOW_HASKELL__
3056-
{-# NOINLINE [1] map #-}
3132+
map# :: (a -> (# b #)) -> Map k a -> Map k b
3133+
map# f = go where
3134+
go Tip = Tip
3135+
go (Bin sx kx x l r)
3136+
| (# y #) <- f x
3137+
= Bin sx kx y (go l) (go r)
3138+
-- We use a `go` function to allow `map#` to inline. Without this,
3139+
-- we'd slow down both strict and lazy map, which wouldn't be great.
3140+
-- This also lets us avoid a custom implementation of <$
3141+
3142+
{-# NOINLINE [1] map# #-}
3143+
-- Perhaps surprisingly, this map#/coerce rule seems to work. Hopefully,
3144+
-- it will continue to do so.
30573145
{-# RULES
3058-
"map/map" forall f g xs . map f (map g xs) = map (f . g) xs
3059-
"map/coerce" map coerce = coerce
3146+
"map#/map#" forall f g xs . map# f (map# g xs) = map# (\x -> case g x of (# y #) -> f y) xs
3147+
"map#/coerce" map# (\x -> (# coerce x #)) = coerce
30603148
#-}
30613149
#endif
30623150

@@ -3066,21 +3154,33 @@ map f = go where
30663154
-- > mapWithKey f (fromList [(5,"a"), (3,"b")]) == fromList [(3, "3:b"), (5, "5:a")]
30673155

30683156
mapWithKey :: (k -> a -> b) -> Map k a -> Map k b
3157+
#ifdef __GLASGOW_HASKELL__
3158+
mapWithKey f = mapWithKey# (\k a -> (# f k a #))
3159+
{-# INLINABLE mapWithKey #-}
3160+
#else
30693161
mapWithKey _ Tip = Tip
30703162
mapWithKey f (Bin sx kx x l r) = Bin sx kx (f kx x) (mapWithKey f l) (mapWithKey f r)
3163+
#endif
3164+
3165+
-- | A version of 'mapWithKey' that takes a function producing a unary
3166+
-- unboxed tuple.
3167+
mapWithKey# :: (k -> a -> (# b #)) -> Map k a -> Map k b
3168+
mapWithKey# f = go where
3169+
go Tip = Tip
3170+
go (Bin sx kx x l r)
3171+
| (# y #) <- f kx x
3172+
= Bin sx kx y (go l) (go r)
30713173

30723174
#ifdef __GLASGOW_HASKELL__
3073-
{-# NOINLINE [1] mapWithKey #-}
3175+
{-# NOINLINE [1] mapWithKey# #-}
30743176
{-# 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
3177+
"mapWK#/mapWK#" forall f g xs. mapWithKey# f (mapWithKey# g xs) = mapWithKey# (\k x -> case g k x of (# y #) -> f k y) xs
3178+
"mapWK#/map#" forall f g xs. mapWithKey# f (map# g xs) = mapWithKey# (\k x -> case g x of (# y #) -> f k y) xs
3179+
"map#/mapWK#" forall f g xs. map# f (mapWithKey# g xs) = mapWithKey# (\k x -> case g k x of (# y #) -> f y) xs
30813180
#-}
30823181
#endif
30833182

3183+
30843184
-- | \(O(n)\).
30853185
-- @'traverseWithKey' f m == 'fromList' <$> 'traverse' (\(k, v) -> (,) k <$> f k v) ('toList' m)@
30863186
-- That is, behaves exactly like a regular 'traverse' except that the traversing
@@ -4195,10 +4295,12 @@ instance (Ord k, Read k) => Read1 (Map k) where
41954295
--------------------------------------------------------------------}
41964296
instance Functor (Map k) where
41974297
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
4298+
{-# INLINABLE fmap #-}
4299+
a <$ m = map (const a) m
4300+
-- For some reason, we need an explicit INLINE or INLINABLE pragma to
4301+
-- get the unfolding to use map rather than expanding into a recursive
4302+
-- function that RULES will never match. Hmm....
4303+
{-# INLINABLE (<$) #-}
42024304

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

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

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
{-# LANGUAGE CPP #-}
22
{-# LANGUAGE BangPatterns #-}
33
#if defined(__GLASGOW_HASKELL__)
4+
{-# LANGUAGE MagicHash #-}
45
{-# LANGUAGE Trustworthy #-}
6+
{-# LANGUAGE UnboxedTuples #-}
57
#endif
68
{-# OPTIONS_HADDOCK not-home #-}
79

@@ -420,6 +422,7 @@ import Data.Semigroup (Arg (..))
420422
import qualified Data.Set.Internal as Set
421423
import qualified Data.Map.Internal as L
422424
import Utils.Containers.Internal.StrictPair
425+
import Utils.Containers.Internal.UnboxedMaybe (Maybe# (..))
423426

424427
import Data.Bits (shiftL, shiftR)
425428
#ifdef __GLASGOW_HASKELL__
@@ -1271,17 +1274,26 @@ mergeWithKey f g1 g2 = go
12711274

12721275
mapMaybe :: (a -> Maybe b) -> Map k a -> Map k b
12731276
mapMaybe f = mapMaybeWithKey (\_ x -> f x)
1277+
{-# INLINABLE mapMaybe #-}
12741278

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

12801284
mapMaybeWithKey :: (k -> a -> Maybe b) -> Map k a -> Map k b
1285+
{-
1286+
-
12811287
mapMaybeWithKey _ Tip = Tip
12821288
mapMaybeWithKey f (Bin _ kx x l r) = case f kx x of
12831289
Just y -> y `seq` link kx y (mapMaybeWithKey f l) (mapMaybeWithKey f r)
12841290
Nothing -> link2 (mapMaybeWithKey f l) (mapMaybeWithKey f r)
1291+
-}
1292+
mapMaybeWithKey f = \m ->
1293+
L.mapMaybeWithKey# (\k x -> case f k x of
1294+
Nothing -> Nothing#
1295+
Just !a -> Just# a) m
1296+
{-# INLINABLE mapMaybeWithKey #-}
12851297

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

13421354
map :: (a -> b) -> Map k a -> Map k b
1355+
#ifdef __GLASGOW_HASKELL__
1356+
map f = L.map# (\x -> let !y = f x in (# y #))
1357+
{-# INLINABLE map #-}
1358+
#else
13431359
map f = go
13441360
where
13451361
go Tip = Tip
13461362
go (Bin sx kx x l r) = let !x' = f x in Bin sx kx x' (go l) (go r)
13471363
-- We use `go` to let `map` inline. This is important if `f` is a constant
13481364
-- 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-
#-}
13561365
#endif
13571366

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

13631372
mapWithKey :: (k -> a -> b) -> Map k a -> Map k b
1373+
#ifdef __GLASGOW_HASKELL__
1374+
mapWithKey f = L.mapWithKey# (\k x -> let !y = f k x in (# y #))
1375+
{-# INLINABLE mapWithKey #-}
1376+
#else
13641377
mapWithKey _ Tip = Tip
13651378
mapWithKey f (Bin sx kx x l r) =
13661379
let x' = f kx x
13671380
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-
#-}
13851381
#endif
13861382

13871383
-- | \(O(n)\).
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{-# language LambdaCase #-}
2+
{-# language MagicHash #-}
3+
{-# language PatternSynonyms #-}
4+
{-# language UnboxedSums #-}
5+
{-# language UnboxedTuples #-}
6+
{-# language UnliftedNewtypes #-}
7+
module Utils.Containers.Internal.UnboxedMaybe
8+
( Maybe# (Just#, Nothing#)
9+
, maybe#
10+
, toMaybe
11+
, toMaybe#
12+
) where
13+
14+
newtype Maybe# a = Maybe# (# (##) | a #)
15+
16+
maybe# :: r -> (a -> r) -> Maybe# a -> r
17+
maybe# n j = \case
18+
Nothing# -> n
19+
Just# a -> j a
20+
{-# INLINE maybe# #-}
21+
22+
pattern Nothing# :: Maybe# a
23+
pattern Nothing# = Maybe# (# (##)| #)
24+
{-# INLINE Nothing# #-}
25+
26+
pattern Just# :: a -> Maybe# a
27+
pattern Just# a = Maybe# (#|a #)
28+
{-# INLINE Just# #-}
29+
30+
{-# COMPLETE Just#, Nothing# #-}
31+
32+
toMaybe :: Maybe# a -> Maybe a
33+
toMaybe = maybe# Nothing Just
34+
{-# INLINE toMaybe #-}
35+
36+
toMaybe# :: Maybe a -> Maybe# a
37+
toMaybe# Nothing = Nothing#
38+
toMaybe# (Just a) = Just# a
39+
{-# INLINE toMaybe# #-}

0 commit comments

Comments
 (0)