Skip to content

Commit ec32861

Browse files
committed
WIP: TextMap
1 parent fa036f7 commit ec32861

File tree

11 files changed

+156
-68
lines changed

11 files changed

+156
-68
lines changed

changelog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ For the latest version of this document, please see [https://github.com/haskell/
88

99
To get `fast` compilation effect cabal-install users may specify `optimization: False`.
1010

11+
* Make map type used by Object abstract so the underlying implementation can
12+
be modified.
13+
1114
### 1.5.6.0
1215
* Make `Show Value` instance print object keys in lexicographic order.
1316

src/Data/Aeson/Encoding/Builder.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import Prelude.Compat
4242

4343
import Data.Aeson.Internal.Time
4444
import Data.Aeson.Types.Internal (Value (..))
45+
import qualified Data.Aeson.TextMap as TM
4546
import Data.ByteString.Builder as B
4647
import Data.ByteString.Builder.Prim as BP
4748
import Data.ByteString.Builder.Scientific (scientificBuilder)
@@ -54,7 +55,6 @@ import Data.Time.Calendar.Month.Compat (Month, toYearMonth)
5455
import Data.Time.Calendar.Quarter.Compat (Quarter, toYearQuarter, QuarterOfYear (..))
5556
import Data.Time.LocalTime
5657
import Data.Word (Word8)
57-
import qualified Data.HashMap.Strict as HMS
5858
import qualified Data.Text as T
5959
import qualified Data.Vector as V
6060

@@ -90,8 +90,8 @@ array v
9090
withComma a z = B.char8 ',' <> encodeToBuilder a <> z
9191

9292
-- Encode a JSON object.
93-
object :: HMS.HashMap T.Text Value -> Builder
94-
object m = case HMS.toList m of
93+
object :: TM.TextMap Value -> Builder
94+
object m = case TM.toList m of
9595
(x:xs) -> B.char8 '{' <> one x <> foldr withComma (B.char8 '}') xs
9696
_ -> emptyObject_
9797
where

src/Data/Aeson/Internal/Functions.hs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,31 @@
1111
module Data.Aeson.Internal.Functions
1212
(
1313
mapHashKeyVal
14+
, mapTextKeyVal
1415
, mapKeyVal
1516
, mapKey
1617
) where
1718

1819
import Prelude.Compat
1920

2021
import Data.Hashable (Hashable)
22+
import qualified Data.Aeson.TextMap as TM
2123
import qualified Data.HashMap.Strict as H
2224
import qualified Data.Map as M
25+
import qualified Data.Text as T
2326

2427
-- | Transform a 'M.Map' into a 'H.HashMap' while transforming the keys.
2528
mapHashKeyVal :: (Eq k2, Hashable k2) => (k1 -> k2) -> (v1 -> v2)
2629
-> M.Map k1 v1 -> H.HashMap k2 v2
2730
mapHashKeyVal fk kv = M.foldrWithKey (\k v -> H.insert (fk k) (kv v)) H.empty
2831
{-# INLINE mapHashKeyVal #-}
2932

33+
-- | Transform a 'M.Map' into a 'H.HashMap' while transforming the keys.
34+
mapTextKeyVal :: (k -> T.Text) -> (v1 -> v2)
35+
-> M.Map k v1 -> TM.TextMap v2
36+
mapTextKeyVal fk kv = M.foldrWithKey (\k v -> TM.insert (fk k) (kv v)) TM.empty
37+
{-# INLINE mapTextKeyVal #-}
38+
3039
-- | Transform the keys and values of a 'H.HashMap'.
3140
mapKeyVal :: (Eq k2, Hashable k2) => (k1 -> k2) -> (v1 -> v2)
3241
-> H.HashMap k1 v1 -> H.HashMap k2 v2

src/Data/Aeson/Parser/Internal.hs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import Prelude.Compat
5252
import Control.Applicative ((<|>))
5353
import Control.Monad (void, when)
5454
import Data.Aeson.Types.Internal (IResult(..), JSONPath, Object, Result(..), Value(..))
55+
import qualified Data.Aeson.TextMap as TM
5556
import Data.Attoparsec.ByteString.Char8 (Parser, char, decimal, endOfInput, isDigit_w8, signed, string)
5657
import Data.Function (fix)
5758
import Data.Functor.Compat (($>))
@@ -68,7 +69,6 @@ import qualified Data.ByteString.Lazy as L
6869
import qualified Data.ByteString.Lazy as BSL
6970
import qualified Data.ByteString.Lazy.Char8 as C
7071
import qualified Data.ByteString.Builder as B
71-
import qualified Data.HashMap.Strict as H
7272
import qualified Data.Scientific as Sci
7373
import Data.Aeson.Parser.Unescape (unescapeText)
7474

@@ -146,16 +146,18 @@ object_' mkObject val' = {-# SCC "object_'" #-} do
146146
{-# INLINE object_' #-}
147147

148148
objectValues :: ([(Text, Value)] -> Either String Object)
149-
-> Parser Text -> Parser Value -> Parser (H.HashMap Text Value)
149+
-> Parser Text -> Parser Value -> Parser (TM.TextMap Value)
150150
objectValues mkObject str val = do
151151
skipSpace
152152
w <- A.peekWord8'
153153
if w == CLOSE_CURLY
154-
then A.anyWord8 >> return H.empty
154+
then A.anyWord8 >> return TM.empty
155155
else loop []
156156
where
157-
-- Why use acc pattern here, you may ask? because 'H.fromList' use 'unsafeInsert'
158-
-- and it's much faster because it's doing in place update to the 'HashMap'!
157+
-- Why use acc pattern here, you may ask? because then the underlying 'TM.fromList'
158+
-- implementation can make use of mutation when constructing a map. For example,
159+
-- 'HashMap` uses 'unsafeInsert' and it's much faster because it's doing in place
160+
-- update to the 'HashMap'!
159161
loop acc = do
160162
k <- (str A.<?> "object key") <* skipSpace <* (char ':' A.<?> "':'")
161163
v <- (val A.<?> "object value") <* skipSpace
@@ -196,7 +198,7 @@ arrayValues val = do
196198

197199
-- | Parse any JSON value. Synonym of 'json'.
198200
value :: Parser Value
199-
value = jsonWith (pure . H.fromList)
201+
value = jsonWith (pure . TM.fromList)
200202

201203
-- | Parse any JSON value.
202204
--
@@ -206,7 +208,7 @@ value = jsonWith (pure . H.fromList)
206208
--
207209
-- ==== __Examples__
208210
--
209-
-- 'json' keeps only the first occurence of each key, using 'HashMap.Lazy.fromList'.
211+
-- 'json' keeps only the first occurence of each key, using 'Data.Aeson.TextMap.fromList'.
210212
--
211213
-- @
212214
-- 'json' = 'jsonWith' ('Right' '.' 'H.fromList')
@@ -249,7 +251,7 @@ jsonWith mkObject = fix $ \value_ -> do
249251

250252
-- | Variant of 'json' which keeps only the last occurence of every key.
251253
jsonLast :: Parser Value
252-
jsonLast = jsonWith (Right . H.fromListWith (const id))
254+
jsonLast = jsonWith (Right . TM.fromListWith (const id))
253255

254256
-- | Variant of 'json' wrapping all object mappings in 'Array' to preserve
255257
-- key-value pairs with the same keys.
@@ -267,19 +269,20 @@ jsonNoDup = jsonWith parseListNoDup
267269
-- fromList [("apple",Array [Bool False,Bool True]),("orange",Array [Bool False])]
268270
fromListAccum :: [(Text, Value)] -> Object
269271
fromListAccum =
270-
fmap (Array . Vector.fromList . ($ [])) . H.fromListWith (.) . (fmap . fmap) (:)
272+
fmap (Array . Vector.fromList . ($ [])) . TM.fromListWith (.) . (fmap . fmap) (:)
271273

272274
-- | @'fromListNoDup' kvs@ fails if @kvs@ contains duplicate keys.
273275
parseListNoDup :: [(Text, Value)] -> Either String Object
274276
parseListNoDup =
275-
H.traverseWithKey unwrap . H.fromListWith (\_ _ -> Nothing) . (fmap . fmap) Just
277+
TM.traverseWithKey unwrap . TM.fromListWith (\_ _ -> Nothing) . (fmap . fmap) Just
276278
where
279+
277280
unwrap k Nothing = Left $ "found duplicate key: " ++ show k
278281
unwrap _ (Just v) = Right v
279282

280283
-- | Strict version of 'value'. Synonym of 'json''.
281284
value' :: Parser Value
282-
value' = jsonWith' (pure . H.fromList)
285+
value' = jsonWith' (pure . TM.fromList)
283286

284287
-- | Strict version of 'jsonWith'.
285288
jsonWith' :: ([(Text, Value)] -> Either String Object) -> Parser Value
@@ -304,7 +307,7 @@ jsonWith' mkObject = fix $ \value_ -> do
304307

305308
-- | Variant of 'json'' which keeps only the last occurence of every key.
306309
jsonLast' :: Parser Value
307-
jsonLast' = jsonWith' (pure . H.fromListWith (const id))
310+
jsonLast' = jsonWith' (pure . TM.fromListWith (const id))
308311

309312
-- | Variant of 'json'' wrapping all object mappings in 'Array' to preserve
310313
-- key-value pairs with the same keys.

src/Data/Aeson/TH.hs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ import Data.Aeson.Types (Options(..), Parser, SumEncoding(..), Value(..), defaul
128128
import Data.Aeson.Types.Internal ((<?>), JSONPathElement(Key))
129129
import Data.Aeson.Types.FromJSON (parseOptionalFieldWith)
130130
import Data.Aeson.Types.ToJSON (fromPairs, pair)
131+
import qualified Data.Aeson.TextMap as TM
131132
import Control.Monad (liftM2, unless, when)
132133
import Data.Foldable (foldr')
133134
#if MIN_VERSION_template_haskell(2,8,0) && !MIN_VERSION_template_haskell(2,10,0)
@@ -147,7 +148,7 @@ import Language.Haskell.TH.Syntax (mkNameG_tc)
147148
import Text.Printf (printf)
148149
import qualified Data.Aeson.Encoding.Internal as E
149150
import qualified Data.Foldable as F (all)
150-
import qualified Data.HashMap.Strict as H (difference, fromList, keys, lookup, toList)
151+
import qualified Data.HashMap.Strict as H (difference, fromList, keys, toList)
151152
import qualified Data.List.NonEmpty as NE (length, reverse)
152153
import qualified Data.Map as M (fromList, keys, lookup , singleton, size)
153154
#if !MIN_VERSION_base(4,16,0)
@@ -1149,7 +1150,7 @@ instance INCOHERENT_ LookupField (Semigroup.Option a) where
11491150
lookupFieldWith :: (Value -> Parser a) -> String -> String
11501151
-> Object -> T.Text -> Parser a
11511152
lookupFieldWith pj tName rec obj key =
1152-
case H.lookup key obj of
1153+
case TM.lookup key obj of
11531154
Nothing -> unknownFieldFail tName rec (T.unpack key)
11541155
Just v -> pj v <?> Key key
11551156

src/Data/Aeson/Text.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ import Prelude.Compat
2626

2727
import Data.Aeson.Types (Value(..), ToJSON(..))
2828
import Data.Aeson.Encoding (encodingToLazyByteString)
29+
import qualified Data.Aeson.TextMap as TM
2930
import Data.Scientific (FPFormat(..), Scientific, base10Exponent)
3031
import Data.Text.Lazy.Builder
3132
import Data.Text.Lazy.Builder.Scientific (formatScientificBuilder)
3233
import Numeric (showHex)
33-
import qualified Data.HashMap.Strict as H
3434
import qualified Data.Text as T
3535
import qualified Data.Text.Lazy as LT
3636
import qualified Data.Text.Lazy.Encoding as LT
@@ -66,7 +66,7 @@ encodeToTextBuilder =
6666
V.foldr f (singleton ']') (V.unsafeTail v)
6767
where f a z = singleton ',' <> go a <> z
6868
go (Object m) = {-# SCC "go/Object" #-}
69-
case H.toList m of
69+
case TM.toList m of
7070
(x:xs) -> singleton '{' <> one x <> foldr f (singleton '}') xs
7171
_ -> "{}"
7272
where f a z = singleton ',' <> one a <> z

src/Data/Aeson/TextMap.hs

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,26 @@
11
{-# LANGUAGE CPP #-}
22
{-# LANGUAGE DeriveDataTypeable #-}
3+
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4+
{-# LANGUAGE DerivingStrategies #-}
5+
{-# LANGUAGE TemplateHaskell #-}
36
module Data.Aeson.TextMap (
47
TextMap,
8+
difference,
59
empty,
10+
foldrWithKey,
11+
fromHashMap,
612
fromList,
13+
fromListWith,
14+
insert,
15+
keys,
16+
lookup,
17+
mapKeyVal,
18+
singleton,
19+
size,
720
toList,
821
toAscList,
22+
toHashMap,
23+
traverseWithKey
924
) where
1025

1126
#if 1
@@ -15,41 +30,73 @@ import Data.Hashable (Hashable(..))
1530
import Data.HashMap.Strict (HashMap)
1631
import Data.List (sortBy)
1732
import Data.Ord (comparing)
18-
import Data.Text (Text)
33+
import Data.Text (Text, unpack)
1934
import Data.Typeable (Typeable)
35+
import Prelude hiding (lookup)
36+
import Data.Bifunctor (first)
37+
2038

2139
import qualified Data.HashMap.Strict as H
2240
import qualified Language.Haskell.TH.Syntax as TH
2341

2442
newtype TextMap v = TextMap { unTextMap :: HashMap Text v }
2543
deriving (Eq, Ord, Typeable, Data)
44+
deriving newtype (Read, Show, Functor, Hashable, NFData)
2645

27-
instance Read v => Read (TextMap v)
28-
-- TODO
46+
instance TH.Lift v => TH.Lift (TextMap v) where
47+
lift (TextMap m) = [| TextMap (H.fromList . map (first pack) $ m') |]
48+
where m' = map (first unpack) . H.toList $ m
2949

30-
instance Show v => Show (TextMap v) where
31-
-- TODO
50+
empty :: TextMap v
51+
empty = TextMap H.empty
3252

33-
instance TH.Lift v => TH.Lift (TextMap v) where
34-
lift = undefined
35-
-- TODO
53+
size :: TextMap v -> Int
54+
size = H.size . unTextMap
3655

37-
instance NFData v => NFData (TextMap v) where
38-
rnf = rnf . unTextMap
56+
keys :: TextMap v -> [Text]
57+
keys = H.keys . unTextMap
3958

40-
instance Hashable v => Hashable (TextMap v) where
41-
hashWithSalt = error "TODO"
59+
singleton :: Text -> v -> TextMap v
60+
singleton k v = TextMap (H.singleton k v)
4261

43-
empty :: TextMap v
44-
empty = TextMap H.empty
62+
insert :: Text -> v -> TextMap v -> TextMap v
63+
insert k v tm = TextMap (H.insert k v (unTextMap tm))
4564

4665
fromList :: [(Text, v)] -> TextMap v
4766
fromList = TextMap . H.fromList
4867

68+
fromListWith :: (v -> v -> v) -> [(Text, v)] -> TextMap v
69+
fromListWith op = TextMap . H.fromListWith op
70+
71+
traverseWithKey :: Applicative f => (Text -> v1 -> f v2) -> TextMap v1 -> f (TextMap v2)
72+
traverseWithKey f = fmap TextMap . H.traverseWithKey f . unTextMap
73+
74+
foldrWithKey :: (Text -> v -> a -> a) -> a -> TextMap v -> a
75+
foldrWithKey f a = H.foldrWithKey f a . unTextMap
76+
77+
difference :: TextMap v -> TextMap v' -> TextMap v
78+
difference tm1 tm2 = TextMap (H.difference (unTextMap tm1) (unTextMap tm2))
79+
4980
toList :: TextMap v -> [(Text, v)]
5081
toList = H.toList . unTextMap
5182

5283
toAscList :: TextMap v -> [(Text, v)]
5384
toAscList = sortBy (comparing fst) . toList
5485

86+
lookup :: Text -> TextMap v -> Maybe v
87+
lookup t tm = H.lookup t (unTextMap tm)
88+
89+
toHashMap :: TextMap v -> HashMap Text v
90+
toHashMap = unTextMap
91+
92+
fromHashMap :: HashMap Text v -> TextMap v
93+
fromHashMap = TextMap
94+
95+
96+
-- | Transform the keys and values of a 'TextMap'.
97+
mapKeyVal :: (Text -> Text) -> (v1 -> v2)
98+
-> TextMap v1 -> TextMap v2
99+
mapKeyVal fk kv = foldrWithKey (\k v -> insert (fk k) (kv v)) empty
100+
{-# INLINE mapKeyVal #-}
101+
55102
#endif

0 commit comments

Comments
 (0)