on master help sign in
Add Config setter functions for Serialize derivation
commite7f6e7e9eb4864b8261ddc6d87617d3134cb5cc1 authorHarendra Kumar <harendra@composewell.com> dateTue 21 Nov 2023, 23:14 (3 years ago) parentdb06710
10 files changed+169−82
benchmark/Streamly/Benchmark/Data/Serialize/RecCompatible.hs+1−1 core/src/Streamly/Data/Serialize.hs+9−3 core/src/Streamly/Internal/Data/IORef/Unboxed.hs+3−3 core/src/Streamly/Internal/Data/Serialize.hs+2−8 core/src/Streamly/Internal/Data/Serialize/TH.hs+25−17 core/src/Streamly/Internal/Data/Serialize/TH/Bottom.hs+115−36 core/src/Streamly/Internal/Data/Serialize/TH/RecHeader.hs+9−9 test/Streamly/Test/Data/Serialize.hs+1−1 test/Streamly/Test/Data/Serialize/CompatV0.hs+2−2 test/Streamly/Test/Data/Serialize/CompatV1.hs+2−2
benchmark/Streamly/Benchmark/Data/Serialize/RecCompatible.hs+1−1
@@ -20,5 +20,5 @@ import qualified Streamly.Internal.Data.Serialize as Serialize
2020
2121$(genLargeRecord "RecCompatible" 50)
2222$(Serialize.deriveSerializeWith
23− (Serialize.defaultConfig {Serialize.recordSyntaxWithHeader = True})
23+ (Serialize.encodeRecordFields True Serialize.defaultConfig)
2424 [d|instance Serialize RecCompatible|])
core/src/Streamly/Data/Serialize.hs+9−3
@@ -51,14 +51,20 @@ module Streamly.Data.Serialize
5151
5252 -- * Serialize
5353 , Serialize(..)
54− -- XXX Need to export the config setters as well.
55− , defaultConfig
54+
55+ -- Deriving instances
56+ , Config -- XXX rename to SerializeConfig
57+ , defaultConfig -- XXX rename to defaultSerializeConfig
58+ , inlineSize
59+ , inlineSerialize
60+ , inlineDeserialize
61+
5662 , deriveSerialize
5763 , deriveSerializeWith
5864
65+ -- Encoding and Decoding
5966 -- , encode
6067 , pinnedEncode
61−
6268 , decode
6369 ) where
6470
core/src/Streamly/Internal/Data/IORef/Unboxed.hs+3−3
@@ -23,14 +23,14 @@ module Streamly.Internal.Data.IORef.Unboxed
2323 (
2424 IORef
2525
26− -- * Construction
26+ -- Construction
2727 , newIORef
2828
29− -- * Write
29+ -- Write
3030 , writeIORef
3131 , modifyIORef'
3232
33− -- * Read
33+ -- Read
3434 , readIORef
3535 , toStreamD
3636 )
core/src/Streamly/Internal/Data/Serialize.hs+2−8
@@ -22,9 +22,6 @@ module Streamly.Internal.Data.Serialize
2222 , module Streamly.Internal.Data.Serialize.Type
2323 -- * Serialize TH
2424 , module Streamly.Internal.Data.Serialize.TH
25− , module Streamly.Internal.Data.Serialize.TH.RecHeader
26− , module Streamly.Internal.Data.Serialize.TH.Common
27− , module Streamly.Internal.Data.Serialize.TH.Bottom
2825 ) where
2926
3027--------------------------------------------------------------------------------
@@ -42,14 +39,11 @@ import GHC.Num.Integer (Integer(..))
4239import GHC.Integer.GMP.Internals (Integer(..), BigNat(..))
4340#endif
4441
42+import Streamly.Internal.Data.IORef.Unboxed
43+import Streamly.Internal.Data.Serialize.TH
4544import Streamly.Internal.Data.Serialize.Type
4645import Streamly.Internal.Data.Unbox
4746import Streamly.Internal.Data.Unbox.TH
48−import Streamly.Internal.Data.Serialize.TH
49−import Streamly.Internal.Data.Serialize.TH.RecHeader
50−import Streamly.Internal.Data.Serialize.TH.Common
51−import Streamly.Internal.Data.Serialize.TH.Bottom
52−import Streamly.Internal.Data.IORef.Unboxed
5347
5448--------------------------------------------------------------------------------
5549-- Common instances
core/src/Streamly/Internal/Data/Serialize/TH.hs+25−17
@@ -9,10 +9,17 @@
99-- Portability : GHC
1010--
1111module Streamly.Internal.Data.Serialize.TH
12− ( deriveSerialize
13− , Config(..)
14− , defaultConfig
12+ (
13+ -- Deriving
14+ deriveSerialize
1515 , deriveSerializeWith
16+
17+ -- Utilities
18+ , module Streamly.Internal.Data.Serialize.TH.Bottom
19+ -- ** Common
20+ , module Streamly.Internal.Data.Serialize.TH.Common
21+ -- ** RecHeader
22+ , module Streamly.Internal.Data.Serialize.TH.RecHeader
1623 ) where
1724
1825--------------------------------------------------------------------------------
@@ -37,6 +44,7 @@ import qualified Streamly.Internal.Data.Serialize.TH.RecHeader as RecHeader
3744
3845import Streamly.Internal.Data.Serialize.TH.Bottom
3946import Streamly.Internal.Data.Serialize.TH.Common
47+import Streamly.Internal.Data.Serialize.TH.RecHeader
4048
4149--------------------------------------------------------------------------------
4250-- Domain specific helpers
@@ -148,7 +156,7 @@ mkSizeOfExpr False False tyOfTy =
148156 [varP _acc, varP _x]
149157 (caseE (varE _x) (fmap (matchCons acc) cons))
150158
151−mkSizeOfExpr False True (TheType con) = RecHeader.mkSizeOfExpr con
159+mkSizeOfExpr False True (TheType con) = RecHeader.mkRecSizeOfExpr con
152160
153161mkSizeOfExpr _ _ _ = errorUnimplemented
154162
@@ -157,11 +165,11 @@ mkSizeDec (Config {..}) headTy cons = do
157165 -- INLINE on sizeOf actually worsens some benchmarks, and improves none
158166 sizeOfMethod <-
159167 mkSizeOfExpr
160− constructorTagAsString
161− recordSyntaxWithHeader
168+ cfgConstructorTagAsString
169+ cfgRecordSyntaxWithHeader
162170 (typeOfType headTy cons)
163171 pure
164− [ PragmaD (InlineP 'size inlineSize FunLike AllPhases)
172+ [ PragmaD (InlineP 'size cfgInlineSize FunLike AllPhases)
165173 , FunD 'size [Clause [] (NormalB sizeOfMethod) []]
166174 ]
167175
@@ -250,7 +258,7 @@ mkDeserializeExpr False True _ (TheType con@(SimpleDataCon _ fields)) = do
250258 RecHeader.mkDeserializeKeysDec deserializeWithKeys updateFunc con
251259 letE
252260 (pure <$> (deserializeWithKeysDec ++ updateFuncDec))
253− (RecHeader.mkDeserializeExpr
261+ (RecHeader.mkRecDeserializeExpr
254262 _initialOffset
255263 _endOffset
256264 deserializeWithKeys
@@ -262,16 +270,16 @@ mkDeserializeDec :: Config -> Type -> [DataCon] -> Q [Dec]
262270mkDeserializeDec (Config {..}) headTy cons = do
263271 peekMethod <-
264272 mkDeserializeExpr
265− constructorTagAsString
266− recordSyntaxWithHeader
273+ cfgConstructorTagAsString
274+ cfgRecordSyntaxWithHeader
267275 headTy
268276 (typeOfType headTy cons)
269277 pure
270− [ PragmaD (InlineP 'deserialize inlineDeserialize FunLike AllPhases)
278+ [ PragmaD (InlineP 'deserialize cfgInlineDeserialize FunLike AllPhases)
271279 , FunD
272280 'deserialize
273281 [ Clause
274− (if isUnitType cons && not constructorTagAsString
282+ (if isUnitType cons && not cfgConstructorTagAsString
275283 then [VarP _initialOffset, WildP, WildP]
276284 else [VarP _initialOffset, VarP _arr, VarP _endOffset])
277285 (NormalB peekMethod)
@@ -359,7 +367,7 @@ mkSerializeExpr False False tyOfTy =
359367 (zip [0 ..] cons))
360368
361369mkSerializeExpr False True (TheType con) =
362− RecHeader.mkSerializeExpr _initialOffset con
370+ RecHeader.mkRecSerializeExpr _initialOffset con
363371
364372mkSerializeExpr _ _ _ = errorUnimplemented
365373
@@ -367,15 +375,15 @@ mkSerializeDec :: Config -> Type -> [DataCon] -> Q [Dec]
367375mkSerializeDec (Config {..}) headTy cons = do
368376 pokeMethod <-
369377 mkSerializeExpr
370− constructorTagAsString
371− recordSyntaxWithHeader
378+ cfgConstructorTagAsString
379+ cfgRecordSyntaxWithHeader
372380 (typeOfType headTy cons)
373381 pure
374− [ PragmaD (InlineP 'serialize inlineSerialize FunLike AllPhases)
382+ [ PragmaD (InlineP 'serialize cfgInlineSerialize FunLike AllPhases)
375383 , FunD
376384 'serialize
377385 [ Clause
378− (if isUnitType cons && not constructorTagAsString
386+ (if isUnitType cons && not cfgConstructorTagAsString
379387 then [VarP _initialOffset, WildP, WildP]
380388 else [VarP _initialOffset, VarP _arr, VarP _val])
381389 (NormalB pokeMethod)
core/src/Streamly/Internal/Data/Serialize/TH/Bottom.hs+115−36
@@ -9,8 +9,17 @@
99-- Portability : GHC
1010--
1111module Streamly.Internal.Data.Serialize.TH.Bottom
12− ( Config(..)
12+ (
13+ -- ** Config
14+ Config(..)
1315 , defaultConfig
16+ , inlineSize
17+ , inlineSerialize
18+ , inlineDeserialize
19+ , encodeConstrNames
20+ , encodeRecordFields
21+
22+ -- ** Other Utilities
1423 , TypeOfType(..)
1524 , typeOfType
1625 , SimpleDataCon(..)
@@ -65,48 +74,118 @@ import Streamly.Internal.Data.Unbox.TH (DataCon(..))
6574-- Config
6675--------------------------------------------------------------------------------
6776
68−-- | Config to control how the 'Serialize' instance is generated.
77+-- XXX Need to make it (Maybe Inline) so that we can use Nothing as default.
78+
79+-- | Configuration to control how the 'Serialize' instance is generated. Use
80+-- 'defaultConfig' and config setter functions to generate desired Config. For
81+-- example:
82+--
83+-- >>> (inlineSize Inline) . (inlineSerialize Inlinable) defaultConfig
84+--
6985data Config =
7086 Config
71− { inlineSize :: Inline
72− -- ^ Inline value for 'size'. Default is Inline.
73− , inlineSerialize :: Inline
74− -- ^ Inline value for 'serialize'. Default is Inline.
75− , inlineDeserialize :: Inline
76− -- ^ Inline value for 'deserialize'. Default is Inline.
77− , constructorTagAsString :: Bool
78− -- ^ __Experimental__
79− --
80− -- If True, encode constructors using the constructor names as Latin-1
81− -- byte sequence.
82− , recordSyntaxWithHeader :: Bool
83− -- ^ __Experimental__
84− --
85− -- If True, encode the keys of the record as a header and then
86− -- serialize the data. Multiple constructors are not supported with
87− -- @recordSyntaxWithHeader@ enabled.
88− --
89− -- __Performance Notes:__
90− --
91− -- There is a constant regression proportional to
92− -- @sum (map length keyList) + length keyList@ where @keyList@ is the
93− -- list of keys of that record as strings.
94− --
95− -- As an example, @keyList@ of,
96− -- @
97− -- data RecordType = RecordType { field0 :: Int, field2 :: Char }
98− -- @
99− -- is @["field0", "field1"]@
87+ { cfgInlineSize :: Inline
88+ , cfgInlineSerialize :: Inline
89+ , cfgInlineDeserialize :: Inline
90+ , cfgConstructorTagAsString :: Bool
91+ , cfgRecordSyntaxWithHeader :: Bool
10092 }
10193
94+-- | How should we inline the 'size' function? The default in 'defaultConfig'
95+-- is 'Inline'. However, aggressive inlining can bloat the code and increase
96+-- compilation times in when there are big functions and too many nesting
97+-- levels so you can change it accordingly.
98+inlineSize :: Inline -> Config -> Config
99+inlineSize v cfg = cfg {cfgInlineSize = v}
100+
101+-- | How should we inline the 'serialize' function? See guidelines in
102+-- 'inlineSize'.
103+inlineSerialize :: Inline -> Config -> Config
104+inlineSerialize v cfg = cfg {cfgInlineSerialize = v}
105+
106+-- | How should we inline the 'deserialize' function? See guidelines in
107+-- 'inlineSize'.
108+inlineDeserialize :: Inline -> Config -> Config
109+inlineDeserialize v cfg = cfg {cfgInlineDeserialize = v}
110+
111+-- | __Experimental__
112+--
113+-- In sum types, use Latin-1 encoded original constructor names rather than
114+-- binary values to identify constructors. This option is not useful on a
115+-- product type.
116+--
117+-- This option enables the following backward compatible behavior:
118+--
119+-- * __Reordering__: Order of the fields can be changed without affecting
120+-- serialization.
121+-- * __Addition__: If a field is added in the new version, the old version of
122+-- the data type can still be deserialized by the new version. The new value
123+-- would never occur in the old one.
124+-- * __Deletion__: If a field is deleted in the new version and deserialization
125+-- of the old version will result in an error. TBD: We can possibly designate a
126+-- catch-all field to handle this case.
127+--
128+-- Note that if you change a type, change the semantics of a type, or delete a
129+-- field and add a new field with the same name, deserialization of old data
130+-- may result in silent unexpected behavior.
131+--
132+-- This option has to be the same on both encoding and decoding side.
133+-- The default is 'False'.
134+--
135+encodeConstrNames :: Bool -> Config -> Config
136+encodeConstrNames v cfg = cfg {cfgConstructorTagAsString = v}
137+
138+-- XXX We can deserialize each field to Either, so if there is a
139+-- deserialization error in any field it can handled independently. Also, a
140+-- unique type/version identifier of the field (based on the versions of the
141+-- packages, full module name space + type identifier) can be serialized along
142+-- with the value for stricter compatibility, semantics checking. Or we can
143+-- store a type hash.
144+
145+-- | __Experimental__
146+--
147+-- In explicit record types, use Latin-1 encoded record field names rather than
148+-- binary values to identify the record fields. Note that this option cannot be
149+-- used on a sum type. Also, it does not work on a product type which is not a
150+-- record, because there are no field names to begin with.
151+--
152+-- This option enables the following backward compatible behavior:
153+--
154+-- * __Reordering__: Order of the fields can be changed without affecting
155+-- serialization.
156+-- * __Addition__: If a 'Maybe' type field is added in the new version, the old
157+-- version of the data type can still be deserialized by the new version, the
158+-- field value in the older version is assumed to be 'Nothing'. If any other
159+-- type of field is added, deserialization of the older version results in an
160+-- error but only when that field is accessed in the deserialized record.
161+-- * __Deletion__: If a field is deleted in the new version and it is
162+-- encountered in a previously serialized version then the field is discarded.
163+-- TBD: We can possibly designate a catch-all field to handle this case.
164+--
165+-- This option has to be the same on both encoding and decoding side.
166+--
167+-- There is a constant performance overhead proportional to the total length of
168+-- the record field names and the number of record fields.
169+--
170+-- The default is 'False'.
171+--
172+encodeRecordFields :: Bool -> Config -> Config
173+encodeRecordFields v cfg = cfg {cfgRecordSyntaxWithHeader = v}
174+
175+-- | The default configuration settings are:
176+--
177+-- * 'inlineSize' 'Inline'
178+-- * 'inlineSerialize' 'Inline'
179+-- * 'inlineDeserialize' 'Inline'
180+--
102181defaultConfig :: Config
103182defaultConfig =
104183 Config
105− { inlineSize = Inline
106− , inlineSerialize = Inline
107− , inlineDeserialize = Inline
108− , constructorTagAsString = False
109− , recordSyntaxWithHeader = False
184+ { cfgInlineSize = Inline
185+ , cfgInlineSerialize = Inline
186+ , cfgInlineDeserialize = Inline
187+ , cfgConstructorTagAsString = False
188+ , cfgRecordSyntaxWithHeader = False
110189 }
111190
112191--------------------------------------------------------------------------------
core/src/Streamly/Internal/Data/Serialize/TH/RecHeader.hs+9−9
@@ -9,9 +9,9 @@
99-- Portability : GHC
1010--
1111module Streamly.Internal.Data.Serialize.TH.RecHeader
12− ( mkSerializeExpr
13− , mkDeserializeExpr
14− , mkSizeOfExpr
12+ ( mkRecSerializeExpr
13+ , mkRecDeserializeExpr
14+ , mkRecSizeOfExpr
1515 , conUpdateFuncDec
1616 , mkDeserializeKeysDec
1717 ) where
@@ -171,8 +171,8 @@ sizeOfHeader (SimpleDataCon _ fields) =
171171 sizeForNumFields = 1 -- At max 255 fields in the record constructor
172172 sizeForFieldLen = 1 -- At max 255 letters in the key
173173
174−mkSizeOfExpr :: SimpleDataCon -> Q Exp
175−mkSizeOfExpr con = do
174+mkRecSizeOfExpr :: SimpleDataCon -> Q Exp
175+mkRecSizeOfExpr con = do
176176 n_acc <- newName "acc"
177177 n_x <- newName "x"
178178 (lamE
@@ -231,8 +231,8 @@ serializeWithSize off arr val = do
231231 Unbox.pokeByteIndex off arr (int_w32 (off1 - off - 4) :: Word32)
232232 pure off1
233233
234−mkSerializeExpr :: Name -> SimpleDataCon -> Q Exp
235−mkSerializeExpr initialOffset (con@(SimpleDataCon cname fields)) = do
234+mkRecSerializeExpr :: Name -> SimpleDataCon -> Q Exp
235+mkRecSerializeExpr initialOffset (con@(SimpleDataCon cname fields)) = do
236236 afterHLen <- newName "afterHLen"
237237 -- Encoding the header length is required.
238238 -- We first compare the header length encoded and the current header
@@ -383,8 +383,8 @@ mkDeserializeKeysDec funcName updateFunc (SimpleDataCon cname fields) = do
383383 else [|error $(litE (StringL (nameBase k ++ " is not found.")))|]
384384
385385
386−mkDeserializeExpr :: Name -> Name -> Name -> SimpleDataCon -> Q Exp
387−mkDeserializeExpr initialOff endOff deserializeWithKeys con = do
386+mkRecDeserializeExpr :: Name -> Name -> Name -> SimpleDataCon -> Q Exp
387+mkRecDeserializeExpr initialOff endOff deserializeWithKeys con = do
388388 hOff <- newName "hOff"
389389 let sizeForFinalOff = 4 -- Word32
390390 sizeForHeaderLength = 4 -- Word32
test/Streamly/Test/Data/Serialize.hs+1−1
@@ -45,7 +45,7 @@ import Test.Hspec as H
4545
4646#ifdef ENABLE_constructorTagAsString
4747#define CONF_NAME "ENABLE_constructorTagAsString"
48−#define CONF (Serialize.defaultConfig {Serialize.constructorTagAsString = True})
48+#define CONF (Serialize.encodeConstrNames True Serialize.defaultConfig)
4949#else
5050#define CONF_NAME "DEFAULT"
5151#define CONF Serialize.defaultConfig
test/Streamly/Test/Data/Serialize/CompatV0.hs+2−2
@@ -29,7 +29,7 @@ instance Arbitrary a => Arbitrary (Rec a) where
2929 arbitrary = Rec <$> arbitrary <*> arbitrary <*> arbitrary
3030
3131$(Serialize.deriveSerializeWith
32− (Serialize.defaultConfig {Serialize.recordSyntaxWithHeader = True})
32+ (Serialize.encodeRecordFields True Serialize.defaultConfig)
3333 [d|instance Serialize a => Serialize (Rec a)|])
3434
3535data River
@@ -42,5 +42,5 @@ instance Arbitrary River where
4242 arbitrary = elements [Ganga, Yamuna, Godavari]
4343
4444$(Serialize.deriveSerializeWith
45− (Serialize.defaultConfig {Serialize.constructorTagAsString = True})
45+ (Serialize.encodeConstrNames True Serialize.defaultConfig)
4646 [d|instance Serialize River|])
test/Streamly/Test/Data/Serialize/CompatV1.hs+2−2
@@ -30,7 +30,7 @@ instance Arbitrary a => Arbitrary (Rec a) where
3030 arbitrary = Rec <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
3131
3232$(Serialize.deriveSerializeWith
33− (Serialize.defaultConfig {Serialize.recordSyntaxWithHeader = True})
33+ (Serialize.encodeRecordFields True Serialize.defaultConfig)
3434 [d|instance Serialize a => Serialize (Rec a)|])
3535
3636data River
@@ -44,5 +44,5 @@ instance Arbitrary River where
4444 arbitrary = elements [Ganga, Yamuna, Godavari]
4545
4646$(Serialize.deriveSerializeWith
47− (Serialize.defaultConfig {Serialize.constructorTagAsString = True})
47+ (Serialize.encodeConstrNames True Serialize.defaultConfig)
4848 [d|instance Serialize River|])