packages feed

binary-typed 0.1.0.1 → 0.2.0.0

raw patch · 10 files changed

+416/−360 lines, 10 filesdep ~basedep ~bytestringPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: base, bytestring

API changes (from Hackage documentation)

- Data.Binary.Typed: Hashed :: TypeFormat
- Data.Binary.Typed: precache :: Typed a -> Typed a
- Data.Binary.Typed.Internal: Hash :: Word64 -> Hash
- Data.Binary.Typed.Internal: Hashed :: TypeFormat
- Data.Binary.Typed.Internal: Hashed' :: Hash -> TypeInformation
- Data.Binary.Typed.Internal: hashType :: TypeRep -> Hash
- Data.Binary.Typed.Internal: instance Binary Hash
- Data.Binary.Typed.Internal: instance Constructor C1_0Hash
- Data.Binary.Typed.Internal: instance Datatype D1Hash
- Data.Binary.Typed.Internal: instance Eq Hash
- Data.Binary.Typed.Internal: instance Generic Hash
- Data.Binary.Typed.Internal: instance Ord Hash
- Data.Binary.Typed.Internal: instance Show Hash
- Data.Binary.Typed.Internal: newtype Hash
- Data.Binary.Typed.Internal: precache :: Typed a -> Typed a
+ Data.Binary.Typed: Hashed32 :: TypeFormat
+ Data.Binary.Typed: Hashed64 :: TypeFormat
+ Data.Binary.Typed: preserialize :: TypeInformation -> TypeInformation
+ Data.Binary.Typed.Internal: Hash32 :: Word32 -> Hash32
+ Data.Binary.Typed.Internal: Hash64 :: Word64 -> Hash64
+ Data.Binary.Typed.Internal: Hashed32 :: TypeFormat
+ Data.Binary.Typed.Internal: Hashed32' :: Hash32 -> TypeInformation
+ Data.Binary.Typed.Internal: Hashed64 :: TypeFormat
+ Data.Binary.Typed.Internal: Hashed64' :: Hash64 -> TypeInformation
+ Data.Binary.Typed.Internal: hashType32 :: TypeRep -> Hash32
+ Data.Binary.Typed.Internal: hashType64 :: TypeRep -> Hash64
+ Data.Binary.Typed.Internal: instance Binary Hash32
+ Data.Binary.Typed.Internal: instance Binary Hash64
+ Data.Binary.Typed.Internal: instance Constructor C1_0Hash32
+ Data.Binary.Typed.Internal: instance Constructor C1_0Hash64
+ Data.Binary.Typed.Internal: instance Constructor C1_5TypeInformation
+ Data.Binary.Typed.Internal: instance Datatype D1Hash32
+ Data.Binary.Typed.Internal: instance Datatype D1Hash64
+ Data.Binary.Typed.Internal: instance Eq Hash32
+ Data.Binary.Typed.Internal: instance Eq Hash64
+ Data.Binary.Typed.Internal: instance Generic Hash32
+ Data.Binary.Typed.Internal: instance Generic Hash64
+ Data.Binary.Typed.Internal: instance Hashable32 TyCon
+ Data.Binary.Typed.Internal: instance Hashable32 TypeRep
+ Data.Binary.Typed.Internal: instance Ord Hash32
+ Data.Binary.Typed.Internal: instance Ord Hash64
+ Data.Binary.Typed.Internal: instance Show Hash32
+ Data.Binary.Typed.Internal: instance Show Hash64
+ Data.Binary.Typed.Internal: makeTypeInformation :: TypeFormat -> TypeRep -> TypeInformation
+ Data.Binary.Typed.Internal: newtype Hash32
+ Data.Binary.Typed.Internal: newtype Hash64
+ Data.Binary.Typed.Internal: preserialize :: TypeInformation -> TypeInformation
- Data.Binary.Typed.Internal: Shown' :: Hash -> String -> TypeInformation
+ Data.Binary.Typed.Internal: Shown' :: Hash32 -> String -> TypeInformation

Files

benchmark/Criterion.hs view
@@ -55,20 +55,6 @@       [ NF someInt       , NF someShortString       , NF someLongString-      , NF defaultInt-      , NF defaultString-      , NF (encode intValUntyped) -- Evaluate the encodings to NF to force a 'Typed'. Hacky but works :-)-      , NF (encode intValHashed)-      , NF (encode intValShown)-      , NF (encode intValFull)-      , NF (encode strSValUntyped)-      , NF (encode strSValHashed)-      , NF (encode strSValShown)-      , NF (encode strSValFull)-      , NF (encode strLValUntyped)-      , NF (encode strLValHashed)-      , NF (encode strLValShown)-      , NF (encode strLValFull)       ]  forceCafs :: IO ()@@ -79,79 +65,24 @@ bench_binaryVsTyped =       [ bgroup "Int"             [ bench_int_untyped-            , bgroup "recalculate" bench_int-            , bgroup "precache"    (bench_encode_precached intValUntyped-                                                           intValHashed-                                                           intValShown-                                                           intValFull-                                                           someInt)+            , bgroup "Typed" bench_int             ]       , bgroup "\"hello\""             [ bench_short_string_untyped-            , bgroup "recalculate" bench_short_string-            , bgroup "precache"    (bench_encode_precached strSValUntyped-                                                           strSValHashed-                                                           strSValShown-                                                           strSValFull-                                                           someShortString)+            , bgroup "Typed" bench_short_string             ]       , bgroup "Lipsum (length 100)"             [ bench_long_string_untyped-            , bgroup "recalculate" bench_long_string-            , bgroup "precache"    (bench_encode_precached strLValUntyped-                                                           strLValHashed-                                                           strLValShown-                                                           strLValFull-                                                           someLongString)+            , bgroup "Typed" bench_long_string             ]       , bgroup "Complicated type"             [ bench_complicated_untyped-            , bgroup "recalculate" bench_complicated-            , bgroup "precache"    (bench_encode_precached compLValUntyped-                                                           compLValHashed-                                                           compLValShown-                                                           compLValFull-                                                           someComplicated)+            , bgroup "Typed" bench_complicated             ]       ]   -defaultInt :: Int-defaultInt = 0--defaultString :: String-defaultString = ""--defaultComplicated :: Complicated-defaultComplicated = Left (' ', 0)--intValUntyped, intValHashed, intValShown, intValFull :: Typed Int-intValUntyped = precache (typed  Untyped defaultInt)-intValHashed  = precache (typed  Hashed  defaultInt)-intValShown   = precache (typed  Shown   defaultInt)-intValFull    = precache (typed  Full    defaultInt)--strSValUntyped, strSValHashed, strSValShown, strSValFull :: Typed String-strSValUntyped = precache (typed Untyped defaultString)-strSValHashed  = precache (typed Hashed  defaultString)-strSValShown   = precache (typed Shown   defaultString)-strSValFull    = precache (typed Full    defaultString)--strLValUntyped, strLValHashed, strLValShown, strLValFull :: Typed String-strLValUntyped = precache (typed Untyped defaultString)-strLValHashed  = precache (typed Hashed  defaultString)-strLValShown   = precache (typed Shown   defaultString)-strLValFull    = precache (typed Full    defaultString)--compLValUntyped, compLValHashed, compLValShown, compLValFull :: Typed Complicated-compLValUntyped = precache (typed Untyped defaultComplicated)-compLValHashed  = precache (typed Hashed  defaultComplicated)-compLValShown   = precache (typed Shown   defaultComplicated)-compLValFull    = precache (typed Full    defaultComplicated)--- bench_int :: [Benchmark] bench_int = map (bench_encode someInt) formats @@ -178,7 +109,8 @@  formats :: [TypeFormat] formats = [ Untyped-          , Hashed+          , Hashed32+          , Hashed64           , Shown           , Full           ]@@ -190,23 +122,4 @@       -> TypeFormat       -> Benchmark bench_encode x format = bench description (nf (encodeTyped format) x)-      where description = "Typed: " ++ show format------ | Encode a value using a precached 'Typed' value.-bench_encode_precached-      :: (Binary a, Typeable a)-      => Typed a -- ^ Precached 'Untyped' dummy value-      -> Typed a -- ^ dito, with 'Hashed'-      -> Typed a -- ^ dito, with 'Shown'-      -> Typed a -- ^ dito, with 'Full'-      -> a       -- ^ Actual value to encode-      -> [Benchmark]-bench_encode_precached untyped hashed shown full x =-      [ bench (description Untyped) (nf (encodeTypedLike untyped) x)-      , bench (description Hashed)  (nf (encodeTypedLike hashed ) x)-      , bench (description Shown)   (nf (encodeTypedLike shown  ) x)-      , bench (description Full)    (nf (encodeTypedLike full   ) x)-      ]-      where description format = "Typed: " ++ show format+      where description = show format
benchmark/CriterionOverview.hs view
@@ -15,7 +15,7 @@  -- Encoding mode to be used mode :: TypeFormat-mode = Hashed+mode = Hashed64  type Complicated = Either (Char, Int) (Either String (Maybe Integer)) @@ -30,19 +30,11 @@   --- Cached typed encoder-encodeTyped' :: Complicated -> ByteString-encodeTyped' = encodeTypedLike (typed mode dummyValue)-      where dummyValue :: Complicated-            dummyValue = Left ('x', 0)--- main :: IO () main = do       evaluate (value `deepseq` ())-      defaultMain [ bgroup "encode only"   bench_encode-                  , bgroup "decode only"   bench_decode+      defaultMain [ bgroup "encode"        bench_encode+                  , bgroup "decode"        bench_decode                   , bgroup "encode+decode" bench_both                   ] @@ -55,29 +47,23 @@  bench_encode :: [Benchmark] bench_encode = [ bench_encode_binaryOnly-               , bench_encode_uncached-               , bench_encode_cached+               , bench_encode_typed                ]  bench_encode_binaryOnly :: Benchmark bench_encode_binaryOnly = bench d (nf f value)-      where d = "Binary only"+      where d = "Binary"             f = encode -bench_encode_uncached :: Benchmark-bench_encode_uncached = bench d (nf f value)-      where d = "Uncached"+bench_encode_typed :: Benchmark+bench_encode_typed = bench d (nf f value)+      where d = "Typed with " ++ show mode             f = encodeTyped mode -bench_encode_cached :: Benchmark-bench_encode_cached = bench d (nf f value)-      where d = "Cached"-            f = encodeTyped'    - -- ############################################################################# -- ###  Decode only  ########################################################### -- #############################################################################@@ -97,7 +83,7 @@  bench_decode_typed :: Benchmark bench_decode_typed = bench d (nf f value_encodedTyped)-      where d = "Typed"+      where d = "Typed with " ++ show mode             f :: ByteString -> Complicated             f = unsafeDecodeTyped @@ -111,8 +97,7 @@  bench_both :: [Benchmark] bench_both = [ bench_both_binaryOnly-             , bench_both_uncached-             , bench_both_cached+             , bench_both_typed              ]  @@ -122,14 +107,8 @@             f :: Complicated -> Complicated             f = decode . encode -bench_both_uncached :: Benchmark-bench_both_uncached = bench d (nf f value)-      where d = "Uncached"+bench_both_typed :: Benchmark+bench_both_typed = bench d (nf f value)+      where d = "Typed with " ++ show mode             f :: Complicated -> Complicated             f = unsafeDecodeTyped . encodeTyped mode--bench_both_cached :: Benchmark-bench_both_cached = bench d (nf f value)-      where d = "Cached"-            f :: Complicated -> Complicated-            f = unsafeDecodeTyped . encodeTyped'
benchmark/MessageLength.hs view
@@ -24,31 +24,36 @@ lipsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam\          \ vitae lacinia tellus. Maecenas posuere." -data EncodeLengths = EL { info         :: String-                        , binary       :: Int64-                        , typedUntyped :: Int64-                        , typedHashed  :: Int64-                        , typedShown   :: Int64-                        , typedFull    :: Int64+data EncodeLengths = EL { info           :: String+                        , binary         :: Int64+                        , typedUntyped   :: Int64+                        , typedHashed32  :: Int64+                        , typedHashed64  :: Int64+                        , typedShown     :: Int64+                        , typedFull      :: Int64                         }  -- | Prettyprint 'EncodeLengths' ppr :: EncodeLengths -> String ppr el = unlines [ info el-                 , printf "  Binary:        %d" (binary el)-                 , printf "  Typed/Untyped: %d (+%d, +%2.2f%%)"+                 , printf "  Binary:         %d" (binary el)+                 , printf "  Typed/Untyped:  %d (+%d, +%2.2f%%)"                           (typedUntyped el)                           (absolute (binary el) (typedUntyped el))                           (percent  (binary el) (typedUntyped el))-                 , printf "  Typed/Hashed:  %d (+%d, +%2.2f%%)"-                          (typedHashed el)-                          (absolute (binary el) (typedHashed el))-                          (percent  (binary el) (typedHashed el))-                 , printf "  Typed/Shown:   %d (+%d, +%2.2f%%)"+                 , printf "  Typed/Hashed32: %d (+%d, +%2.2f%%)"+                          (typedHashed32 el)+                          (absolute (binary el) (typedHashed32 el))+                          (percent  (binary el) (typedHashed32 el))+                 , printf "  Typed/Hashed64: %d (+%d, +%2.2f%%)"+                          (typedHashed64 el)+                          (absolute (binary el) (typedHashed64 el))+                          (percent  (binary el) (typedHashed64 el))+                 , printf "  Typed/Shown:    %d (+%d, +%2.2f%%)"                           (typedShown el)                           (absolute (binary el) (typedShown el))                           (percent  (binary el) (typedShown el))-                 , printf "  Typed/Full:    %d (+%d, +%2.2f%%)"+                 , printf "  Typed/Full:     %d (+%d, +%2.2f%%)"                           (typedFull el)                           (absolute (binary el) (typedFull el))                           (percent  (binary el) (typedFull el))@@ -68,17 +73,19 @@ -- | Measure the message lengths generated by different encodings. measure :: (Binary a, Typeable a) => String -> a -> EncodeLengths measure i x = EL { info = i-                 , binary       = BSL.length binary'-                 , typedUntyped = BSL.length typedUntyped'-                 , typedHashed  = BSL.length typedHashed'-                 , typedShown   = BSL.length typedShown'-                 , typedFull    = BSL.length typedFull'+                 , binary         = BSL.length binary'+                 , typedUntyped   = BSL.length typedUntyped'+                 , typedHashed32  = BSL.length typedHashed32'+                 , typedHashed64  = BSL.length typedHashed64'+                 , typedShown     = BSL.length typedShown'+                 , typedFull      = BSL.length typedFull'                  }        where -      binary'       = encode              x-      typedUntyped' = encodeTyped Untyped x-      typedHashed'  = encodeTyped Hashed  x-      typedShown'   = encodeTyped Shown   x-      typedFull'    = encodeTyped Full    x+      binary'        = encode                x+      typedUntyped'  = encodeTyped  Untyped  x+      typedHashed32' = encodeTyped  Hashed32 x+      typedHashed64' = encodeTyped  Hashed64 x+      typedShown'    = encodeTyped  Shown    x+      typedFull'     = encodeTyped  Full     x
binary-typed.cabal view
@@ -1,5 +1,5 @@ name:          binary-typed-version:       0.1.0.1+version:       0.2.0.0 synopsis:      Type-safe binary serialization Description:       `Binary` serialization tagged with type information, allowing for@@ -26,9 +26,9 @@       .       Performance-wise, here is a value @Right (Left \<100 chars lipsum\>)@ of       type @Either (Char, Int) (Either String (Maybe Integer))@ benchmarked-      using the @Hashed@ type representation:+      using the @Hashed64@ type representation:       .-      <<http://i.imgur.com/nY6hgMP.png>>+      <<http://i.imgur.com/JzhYqGZ.png>>       .       <doc/bench-overview.png (local copy)> homepage:           https://github.com/quchen/binary-typed@@ -56,7 +56,7 @@       ghc-options:         -Wall       hs-source-dirs:      src       other-extensions:    GADTs, DeriveGeneric-      build-depends:       base             >= 4.7 && < 5+      build-depends:       base             >= 4.6 && < 5                          , binary           >= 0.7                          , bytestring       >= 0.9                          , murmur-hash      >= 0.1@@ -70,9 +70,10 @@       other-modules:       HUnit, QuickCheck       other-extensions:    ScopedTypeVariables, NumDecimals       type:                exitcode-stdio-1.0-      build-depends:       base             >= 4.7 && < 5+      build-depends:       base                          , binary           >= 0.7                          , binary-typed+                         , bytestring                          , tasty            >= 0.8                          , tasty-hunit      >= 0.8                          , tasty-quickcheck >= 0.8@@ -81,6 +82,7 @@  test-suite message-length       default-language:    Haskell2010+      ghc-options:         -Wall       type:                exitcode-stdio-1.0       hs-source-dirs:      benchmark       main-is:             MessageLength.hs@@ -94,6 +96,7 @@  benchmark criterion       default-language:    Haskell2010+      ghc-options:         -Wall       type:                exitcode-stdio-1.0       hs-source-dirs:      benchmark       main-is:             Criterion.hs@@ -108,6 +111,7 @@  benchmark criterion-overview       default-language:    Haskell2010+      ghc-options:         -Wall       type:                exitcode-stdio-1.0       hs-source-dirs:      benchmark       main-is:             CriterionOverview.hs
doc/bench-overview.png view

binary file changed (22136 → 21408 bytes)

src/Data/Binary/Typed.hs view
@@ -16,7 +16,7 @@       , mapTyped       , reValue       , reType-      , precache+      , preserialize         -- * Typed serialization@@ -36,7 +36,7 @@  import qualified Data.ByteString.Lazy as BSL -import           Data.Typeable (Typeable)+import           Data.Typeable (Typeable, typeOf)  import           Data.Binary import           Data.Binary.Get (ByteOffset)@@ -81,44 +81,34 @@   -- | Encode a 'Typeable' value to 'BSL.ByteString' that includes type--- information. If at all possible, prefer the more efficient 'encodeTypedLike'--- though.+-- information. This function is useful to create specialized typed encoding+-- functions, because the type information is cached and does not need to be+-- recalculated on every serialization. ----- @--- 'encodeTyped' format value = 'encode' ('typed' format value)--- @+-- Observationally, @'encodeTyped' format value@ is equivalent to+-- @'encode' ('typed' format value)@. However, 'encodeTyped' does the type+-- information related calculations in advance and shares the results between+-- future invocations of it, making it much more efficient to serialize many+-- values of the same type. encodeTyped :: (Typeable a, Binary a)             => TypeFormat             -> a             -> BSL.ByteString-encodeTyped format value = encode (typed format value)+encodeTyped format = \x ->+      let ty = preserialize (makeTypeInformation format (typeOf x))+      in  encode (Typed ty x)   --- | Version of 'encodeTyped' that avoids recomputing the type representation---   of the input by using the one already contained in the first parameter.---   This is usually /much/ more efficient than using 'encode', having a---   computational cost similar to using 'Binary' directly.------ @--- 'encodeTypedLike' ty x--- -- is observationally identical to--- 'encode' ('reValue' ('const' x) ty)--- @------ This function is intended to generate new encoding functions like so:------ @--- encodeInt :: 'Int' -> 'Data.ByteString.Lazy.ByteString'--- encodeInt = 'encodeTypedLike' ('typed' 'Full' 0)--- @-encodeTypedLike-      :: (Typeable a, Binary a)+encodeTypedLike ::+         (Typeable a, Binary a)       => Typed a       -> a       -> BSL.ByteString-encodeTypedLike dummy = let (Typed ty _) = precache dummy-                        in  encode . Typed ty+encodeTypedLike (Typed ty _) = encodeTyped (getFormat ty)++{-# DEPRECATED encodeTypedLike+               "'encodeTyped' now caches automatically for all types" #-}   
src/Data/Binary/Typed/Internal.hs view
@@ -10,19 +10,22 @@       -- * 'Typed'         Typed(..)       , TypeInformation(..)-      , Hash(..)+      , Hash32(..)+      , Hash64(..)       , typed+      , makeTypeInformation       , TypeFormat(..)       , getFormat       , typecheck       , erase-      , precache+      , preserialize        -- * 'TypeRep'       , TypeRep(..)       , stripTypeRep       , unStripTypeRep-      , hashType+      , hashType32+      , hashType64        -- * 'TyCon'       , TyCon(..)@@ -44,7 +47,8 @@ import           Data.Binary  -- Crypto stuff for hashing-import           Data.Digest.Murmur64+import qualified Data.Digest.Murmur32 as H32+import qualified Data.Digest.Murmur64 as H64   @@ -52,10 +56,11 @@ --   recipient can do consistency checks. See 'TypeFormat' for more detailed --   information on the fields. data TypeInformation = Untyped'-                     | Hashed'  Hash-                     | Shown'   Hash String-                     | Full'    TypeRep-                     | Cached'  BSL.ByteString+                     | Hashed32'  Hash32+                     | Hashed64'  Hash64+                     | Shown'     Hash32 String+                     | Full'      TypeRep+                     | Cached'    BSL.ByteString                      deriving (Eq, Ord, Show, Generic)  instance Binary TypeInformation@@ -63,22 +68,33 @@   -- | Extract which 'TypeFormat' was used to create a certain 'TypeInformation'.+--+-- If the type is 'Cached'', then the contained information is assumed+-- well-formed. In the public API, this is safe to do, since only well-typed+-- 'Typed' values can be created in the first place. getFormat :: TypeInformation -> TypeFormat-getFormat (Untyped' {}) = Untyped-getFormat (Hashed'  {}) = Hashed-getFormat (Shown'   {}) = Shown-getFormat (Full'    {}) = Full-getFormat (Cached'  bs) = getFormat (decode bs)-                        -- decode is safe here since caching ensures-                        -- a well-formed input ByteString+getFormat (Untyped'   {}) = Untyped+getFormat (Hashed32'  {}) = Hashed32+getFormat (Hashed64'  {}) = Hashed64+getFormat (Shown'     {}) = Shown+getFormat (Full'      {}) = Full+getFormat (Cached'    bs) = getFormat (decode bs)   +-- | A hash value of a 'TypeRep'. Currently a 32-bit value created using+--   the MurmurHash2 algorithm.+newtype Hash32 = Hash32 Word32+      deriving (Eq, Ord, Show, Generic)+instance Binary Hash32+++ -- | A hash value of a 'TypeRep'. Currently a 64-bit value created using --   the MurmurHash2 algorithm.-newtype Hash = Hash Word64+newtype Hash64 = Hash64 Word64       deriving (Eq, Ord, Show, Generic)-instance Binary Hash+instance Binary Hash64   @@ -106,54 +122,86 @@   --- | Calculate the serialization of a 'TypeInformation' and store it in a---   'Typed' value so it does not have to be recalculated on every call to---   'encode'.+-- | Sometimes it can be beneficial to serialize the type information in+--   advance, so that the maybe costly serialization step does not have to be+--   repeated on every invocation of 'encode'. Preserialization comes at a price+--   though, as the directly contained 'BSL.ByteString'requires its length to+--   be included in the final serialization, yielding a 8-byte overhead for the+--   required 'Data.Int.Int64', and one for the tag of what was serialized+--   ("shown or full?"). -----   This is typically applied to a dummy value created using 'typed' and---   the desired 'TypeFormat'; the actual data is then inserted using---   'Data.Binary.Typed.reValue', which is how---   'Data.Binary.Typed.encodeTyped' works.-precache :: Typed a -> Typed a-precache t@(Typed (Cached' _) _) = t-precache   (Typed ty          x) = Typed (Cached' (encode ty)) x-                                   -- This is the only place that constructs a-                                   -- Cached' value.+--   This function calculates the serialized version of 'TypeInformation' in+--   cases where the required 8 bytes are negligible (determined by an+--   arbitrary threshold, currently 10*9 bytes).+--+--   Used to make 'Data.Binary.Typed.encodeTyped' more efficient; the source+--   there also makes a good usage example.+preserialize :: TypeInformation -> TypeInformation+preserialize x@(Cached'   _) = x+preserialize x@(Untyped'   ) = x+preserialize x@(Hashed32' _) = x+preserialize x@(Hashed64' _) = x+-- Explicit cases for Shown' and Full' so exhaustiveness can be checked when+-- new constructors are added. (The default pattern of just "x" would do right+-- now as well, but not provide that.)+preserialize x@(Shown'  _ _) = preserialize' x+preserialize x@(Full'     _) = preserialize' x   +-- | Preserializes type information if its encoded byte length is larger than+--   an arbitrary threshold. Less efficient than 'preserialize' since it+--   always preserializes and always calculates the encoded version no matter+--   what.+preserialize' :: TypeInformation -> TypeInformation+preserialize' x | BSL.length encoded > 10*9 = Cached' encoded+                | otherwise = x+                where encoded = encode x+++ -- | Different ways of including/verifying type information of serialized --   messages. data TypeFormat =          -- | Include no type information.         ---        --   * Requires one byte more than using 'Binary' directly (namely to-        --     tag the data as untyped, required for the decoding step).+        --   * Requires one byte more compared to using 'Binary' directly+        --     (to tag the data as untyped, required for the decoding step).         Untyped          -- | Compare types by their hash values (using the MurmurHash2         --   algorithm).         ---        --   * Requires only 8 additional bytes for the type information.+        --   * Requires five bytes more compared to using 'Binary' directly for+        --     the type information (one to tag as 'Hashed32', four for the+        --     hash value)         --   * Subject to false positive due to hash collisions, although in         --     practice this should almost never happen.         --   * Type errors cannot tell the provided type ("Expected X, received         --     type with hash H")+      | Hashed32 -      | Hashed+        -- | Like 'Hashed32', but uses a 64-bit hash value.+        --+        --   * Requires nine bytes more compared to using 'Binary'.+        --   * Hash collisions are even less likely to occur than with+        --     'Hashed32'.+      | Hashed64          -- | Compare 'String' representation of types, obtained by calling         --   'show' on the 'TypeRep', and also include a hash value-        --   (like 'Hashed'). The former is mostly for readable error messages,-        --   the latter provides collision resistance.+        --   (like 'Hashed32'). The former is mostly for readable error+        --   messages, the latter provides better collision resistance.         ---        --   * Data size larger than 'Hashed', but usually smaller than 'Full'.+        --   * Data size larger than 'Hashed32', but usually smaller than+        --     'Full'.         --   * Both the hash and the shown type must match to satisfy the         --     typechecker.         --   * Useful type errors ("expected X, received Y"). All types are         --     shown unqualified though, making @Foo.X@ and @Bar.X@ look-        --     identical in error messages.+        --     identical in error messages. Remember this when you get a+        --     seemingly silly error "expected Foo, but given Foo".       | Shown          -- | Compare the full representation of a data type.@@ -167,7 +215,8 @@         --     type.         --   * Useful type errors ("expected X, received Y"). All types are         --     shown unqualified though, making @Foo.X@ and @Bar.X@ look-        --     identical in error messages.+        --     identical in error messages. Remember this when you get a+        --     seemingly silly error "expected Foo, but given Foo".       | Full        deriving (Eq, Ord, Show)@@ -185,16 +234,23 @@ -- -- The decode site can now verify whether decoding happens with the right type. typed :: Typeable a => TypeFormat -> a -> Typed a-typed format x = Typed typeInformation x where-      ty = typeOf x-      typeInformation = case format of-            Untyped -> Untyped'-            Hashed  -> Hashed'  (hashType     ty)-            Shown   -> Shown'   (hashType     ty) (show ty)-            Full    -> Full'    (stripTypeRep ty)+typed format x = Typed (makeTypeInformation format (typeOf x)) x   +-- | Create the 'TypeInformation' to be stored inside a 'Typed' value from+--   a 'Ty.TypeRep'.+makeTypeInformation :: TypeFormat -> Ty.TypeRep -> TypeInformation+makeTypeInformation format ty = case format of+      Untyped   -> Untyped'+      Hashed32  -> Hashed32'  (hashType32     ty)+      Hashed64  -> Hashed64'  (hashType64     ty)+      Shown     -> Shown'     (hashType32     ty) (show ty)+      Full      -> Full'      (stripTypeRep   ty)++++ -- | Extract the value of a 'Typed', i.e. strip off the explicit type -- information. --@@ -215,25 +271,27 @@ typecheck :: Typeable a => Typed a -> Either String (Typed a) typecheck ty@(Typed typeInformation x) = case typeInformation of       Cached' cache -> decode' cache >>= \ty' -> typecheck (Typed ty' x)-      Full'   full     | exFull /= full -> Left (fullError full)-      Hashed' hash     | exHash /= hash -> Left (hashError hash)-      Shown'  hash str | (exHash, exShow) /= (hash, str)-                                        -> Left (shownError hash str)+      Full'   full      | exFull /= full     -> Left (fullError full)+      Hashed32' hash32  | exHash32 /= hash32 -> Left (hashError exHash32 hash32)+      Hashed64' hash64  | exHash64 /= hash64 -> Left (hashError exHash64 hash64)+      Shown'  hash32 str | (exHash32, exShow) /= (hash32, str)+                                             -> Left (shownError hash32 str)       _no_type_error -> Right ty         where        -- ex = expected-      exType = typeOf x-      exHash = hashType     exType-      exShow = show         exType-      exFull = stripTypeRep exType+      exType   = typeOf x+      exHash32 = hashType32   exType+      exHash64 = hashType64   exType+      exShow   = show         exType+      exFull   = stripTypeRep exType -      hashError hash = printf pat exShow (show exHash) (show hash)+      hashError eHash hash = printf pat exShow (show eHash) (show hash)             where pat = "Type error: expected type %s with hash %s,\                         \ but received data with hash %s"-      shownError hash str = printf pat exShow (show exHash) str (show hash)+      shownError hash str = printf pat exShow (show exHash32) str (show hash)             where pat = "Type error: expected type %s and hash %s,\                         \ but received data with type %s and hash %s"       fullError full = printf pat exShow (show full)@@ -246,12 +304,18 @@   --- | Hash a 'Ty.TypeRep'.-hashType :: Ty.TypeRep -> Hash-hashType = Hash . asWord64 . hash64 . stripTypeRep+-- | Hash a 'Ty.TypeRep' to a 32-bit digest.+hashType32 :: Ty.TypeRep -> Hash32+hashType32 = Hash32 . H32.asWord32 . H32.hash32 . stripTypeRep   +-- | Hash a 'Ty.TypeRep' to a 64-bit digest.+hashType64 :: Ty.TypeRep -> Hash64+hashType64 = Hash64 . H64.asWord64 . H64.hash64 . stripTypeRep+++ -- | 'Ty.TypeRep' without the (internal) fingerprint. data TypeRep = TypeRep TyCon [TypeRep]       deriving (Eq, Ord, Generic)@@ -260,12 +324,15 @@ instance Show TypeRep where       show = show . unStripTypeRep -instance Hashable64 TypeRep where-      hash64Add (TypeRep tycon args) = hash64Add (tycon, args)+instance H32.Hashable32 TypeRep where+      hash32Add (TypeRep tycon args) = H32.hash32Add (tycon, args) +instance H64.Hashable64 TypeRep where+      hash64Add (TypeRep tycon args) = H64.hash64Add (tycon, args)   + -- | 'Ty.TyCon' without the (internal) fingerprint. data TyCon = TyCon String String String -- ^ Package, module, constructor name       deriving (Eq, Ord, Generic)@@ -274,8 +341,11 @@ instance Show TyCon where       show = show . unStripTyCon -instance Hashable64 TyCon where-      hash64Add (TyCon p m c) = hash64Add (p, m, c)+instance H32.Hashable32 TyCon where+      hash32Add (TyCon p m c) = H32.hash32Add (p, m, c)++instance H64.Hashable64 TyCon where+      hash64Add (TyCon p m c) = H64.hash64Add (p, m, c)   
src/Data/Binary/Typed/Tutorial.hs view
@@ -68,7 +68,7 @@  -- $usage ----- This package is typically used for debugging purposes. 'Hashed' type+-- This package is typically used for debugging purposes. 'Hashed32' type -- information keeps the size overhead relatively low, but requires a certain -- amount of computational ressources. It is reliable at detecting errors, but -- not very good at telling specifics about it. If a problem is identified, the@@ -83,29 +83,29 @@ -- -- @ -- test3 = let val = 10 :: 'Int'---             enc = 'encodeTyped' val+--             enc = 'encodeTyped' 'Hashed32' val --             dec = 'decodeTyped' enc :: 'Either' 'String' 'Bool' --         in  'print' dec -- @ ----- However, using 'encodeTyped' is computationally inefficient when many--- messages of the same type are serialized, since it recomputes a serialized--- version of that type for every single serialized value from scratch.--- 'encodeTypedLike' exists to remedy that: it takes a separately constructed--- 'Typed' dummy value, and computes a new serialization function for that type--- out of it. This serialization function then re-uses the type representation--- of the dummy value, and simply replaces the contained value on each--- serialization so that no unnecessary overhead is introduced.+-- Using 'encodeTyped' in particular has a significant advantage: when used to+-- create new specialized encoding functions, the type information has to be+-- calculated only once, and can be shared among further invocations of the+-- function. In other words, using -- -- @--- -- Computes 'Int's type representation 100 times:--- manyIntsNaive = map 'encodeTyped' [1..100 :: 'Int']+-- encodeInt :: 'Int' -> 'Data.ByteString.Lazy.ByteString'+-- encodeInt = 'encodeTyped' 'Hashed32'+-- @ ----- -- Much more efficient: prepare dummy value to precache the--- -- type representation, computing it only once:--- 'encodeInt' = 'encodeTypedLike' ('typed' 'Full' (0 :: 'Int'))--- manyIntsCached = map 'encodeInt' [1..100]+-- is much more efficient than+-- -- @+-- encodeInt :: 'Int' -> 'Data.ByteString.Lazy.ByteString'+-- encodeInt = 'encode' . 'typed' 'Hashed32'+-- @+--+-- since the latter recalculates the hash of \"Int\" on every invocation.   @@ -127,13 +127,11 @@ --   * 'mapTyped' (change values contained in 'Typed's) --   * 'reValue' (change value, but don't recompute type representation) --   * 'reType' (change type representation, but keep value)---   * 'precache' (compute serialized type representation, useful as an optimization)+--   * 'preserialize' (compute serialized type representation and cache it, useful as an optimization) ----- Lastly, there are a number of encoding/decoding functions, mostly for--- convenience:+-- Lastly, there are a number of encoding/decoding functions: -----   * 'encodeTyped' (pack in 'Typed' and then 'encode')---   * 'encodeTypedLike' (usually much more efficient version of 'encodeTyped')+--   * 'encodeTyped' (pack in 'Typed' and then 'encode', but more efficient) --   * 'decodeTyped' (decode 'Typed' 'Data.ByteString.Lazy.ByteString' to @'Either' 'String' a@) --   * 'decodeTypedOrFail' (like 'decodeTyped', but with more meta information) --   * 'unsafeDecodeTyped' (which throws a runtime error on type mismatch)
tests/HUnit.hs view
@@ -26,7 +26,7 @@ -- | The entire HUnit test tree, to be imported qualified props :: TestTree props = tree tests where-      tree = testGroup "HUnit"+      tree x = testGroup "HUnit" [ testGroup "Should be type errors" x ]       tests = [ error_coercions_simple_large_to_small               , error_coercions_simple_small_to_large               , error_coercions_complicated@@ -56,22 +56,32 @@ --   memory (Bool). error_coercions_simple_large_to_small :: TestTree error_coercions_simple_large_to_small = tree tests where-      tree = testGroup "Encode Int, decode Bool => Type error"-      tests = [ error_int_bool_hashed+      tree = testGroup "Encode Int, decode Bool"+      tests = [ error_int_bool_hashed32+              , error_int_bool_hashed64               , error_int_bool_shown               , error_int_bool_full               ]  -- | See 'error_coercions_simple_large_to_small'-error_int_bool_hashed :: TestTree-error_int_bool_hashed =-      testCase "Hashed" $+error_int_bool_hashed32 :: TestTree+error_int_bool_hashed32 =+      testCase "Hashed32" $ -      isLeft (wackyCoercion Hashed (123 :: Int) :: Either String Bool)+      isLeft (wackyCoercion Hashed32 (123 :: Int) :: Either String Bool)       @?-      "No type error when coercing Int to Bool (with hashed type info)"+      "No type error when coercing Int to Bool (with 32 bit hashed type info)"  -- | See 'error_coercions_simple_large_to_small'+error_int_bool_hashed64 :: TestTree+error_int_bool_hashed64 =+      testCase "Hashed64" $++      isLeft (wackyCoercion Hashed64 (123 :: Int) :: Either String Bool)+      @?+      "No type error when coercing Int to Bool (with 64 bit hashed type info)"++-- | See 'error_coercions_simple_large_to_small' error_int_bool_shown :: TestTree error_int_bool_shown =       testCase "Shown" $@@ -97,22 +107,32 @@ --   memory (Bool). error_coercions_simple_small_to_large :: TestTree error_coercions_simple_small_to_large = tree tests where-      tree = testGroup "Encode Bool, decode Int => Type error"-      tests = [ error_bool_int_hashed+      tree = testGroup "Encode Bool, decode Int"+      tests = [ error_bool_int_hashed32+              , error_bool_int_hashed64               , error_bool_int_shown               , error_bool_int_full               ]  -- | See 'error_coercions_simple_small_to_large'-error_bool_int_hashed :: TestTree-error_bool_int_hashed =-      testCase "Hashed" $+error_bool_int_hashed32 :: TestTree+error_bool_int_hashed32 =+      testCase "Hashed32" $ -      isLeft (wackyCoercion Hashed True :: Either String Int)+      isLeft (wackyCoercion Hashed32 True :: Either String Int)       @?-      "No type error when coercing Bool to Int (with hashed type info)"+      "No type error when coercing Bool to Int (with 32 bit hashed type info)"  -- | See 'error_coercions_simple_small_to_large'+error_bool_int_hashed64 :: TestTree+error_bool_int_hashed64 =+      testCase "Hashed64" $++      isLeft (wackyCoercion Hashed64 True :: Either String Int)+      @?+      "No type error when coercing Bool to Int (with 64 bit hashed type info)"++-- | See 'error_coercions_simple_small_to_large' error_bool_int_shown :: TestTree error_bool_int_shown =       testCase "Shown" $@@ -145,7 +165,8 @@ error_coercions_complicated :: TestTree error_coercions_complicated = tree tests where       tree = testGroup "Complicated type coercion with small discrepancy"-      tests = [ error_long_type_hashed+      tests = [ error_long_type_hashed32+              , error_long_type_hashed64               , error_long_type_shown               , error_long_type_full               ]@@ -153,13 +174,24 @@   -- | See 'error_coercions_complicated'-error_long_type_hashed :: TestTree-error_long_type_hashed =-      testCase "Hashed" $+error_long_type_hashed32 :: TestTree+error_long_type_hashed32 =+      testCase "Hashed32" $ -      isLeft (wackyCoercion Hashed long_type_input `asTypeOf` long_type_output)+      isLeft (wackyCoercion Hashed32 long_type_input `asTypeOf` long_type_output)       @?-      "No type error doing a complicated coercion (with hashed type info)"+      "No type error doing a complicated coercion (with 32 bit hashed type info)"++++-- | See 'error_coercions_complicated'+error_long_type_hashed64 :: TestTree+error_long_type_hashed64 =+      testCase "Hashed64" $++      isLeft (wackyCoercion Hashed64 long_type_input `asTypeOf` long_type_output)+      @?+      "No type error doing a complicated coercion (with 64 bit hashed type info)"   
tests/QuickCheck.hs view
@@ -1,11 +1,14 @@ {-# LANGUAGE NumDecimals #-}+{-# LANGUAGE RankNTypes #-}  module QuickCheck (props) where   import Control.Applicative import Data.Typeable (Typeable)+import Data.Int +import Data.ByteString.Lazy as BSL import Data.Binary import Data.Binary.Typed import Data.Binary.Typed.Internal@@ -21,14 +24,22 @@ props :: TestTree props = tree tests where       tree = testGroup "QuickCheck"-      tests = [ prop_typerep-              , prop_inverses+      tests = [ prop_inverses               , prop_api               , prop_internal+              , prop_sizes               ]   +++-- #############################################################################+-- ###  Decode is left-inverse to encode  ######################################+-- #############################################################################+++ -- | Check whether typed encoding and decoding are inverses of each other prop_inverses :: TestTree prop_inverses = tree tests where@@ -44,80 +55,44 @@ prop_inverses_int = tree tests where        tree = localOption (QuickCheckMaxSize maxBound)-           . localOption (QuickCheckTests 1e3)            . testGroup "Int" -      tests = [ testProperty "Untyped" (prop Untyped)-              , testProperty "Hashed"  (prop Hashed)-              , testProperty "Shown"   (prop Shown)-              , testProperty "Full"    (prop Full)-              , testProperty "Cached"   prop_cached+      tests = [ testProperty "Untyped"  (prop Untyped)+              , testProperty "Hashed32" (prop Hashed32)+              , testProperty "Hashed64" (prop Hashed64)+              , testProperty "Shown"    (prop Shown)+              , testProperty "Full"     (prop Full)               ]        prop :: TypeFormat -> Int -> Bool       prop format i = unsafeDecodeTyped (encodeTyped format i) == i -      prop_cached :: Typed Int -> Int -> Bool-      prop_cached dummy i = unsafeDecodeTyped (encodeTypedLike dummy i) == i  - -- | Check whether encoding and decoding a String works properly prop_inverses_string :: TestTree prop_inverses_string = tree tests where        tree = localOption (QuickCheckMaxSize 100)-           . localOption (QuickCheckTests 1e3)            . testGroup "String" -      tests = [ testProperty "Untyped" (prop Untyped)-              , testProperty "Hashed"  (prop Hashed)-              , testProperty "Shown"   (prop Shown)-              , testProperty "Full"    (prop Full)-              , testProperty "Cached"   prop_cached+      tests = [ testProperty "Untyped"  (prop Untyped)+              , testProperty "Hashed32" (prop Hashed32)+              , testProperty "Hashed64" (prop Hashed64)+              , testProperty "Shown"    (prop Shown)+              , testProperty "Full"     (prop Full)               ]        prop :: TypeFormat -> String -> Bool       prop format i = unsafeDecodeTyped (encodeTyped format i) == i -      prop_cached :: Typed String -> String -> Bool-      prop_cached dummy i = unsafeDecodeTyped (encodeTypedLike dummy i) == i   --- | Test properties of 'TypeRep's and 'TyCon's.-prop_typerep :: TestTree-prop_typerep = tree tests where-      tree = localOption (QuickCheckTests 1e3)-           . localOption (QuickCheckMaxSize 10)-           . testGroup "TypeRep, TyCon" -      tests = [ prop_hash_total-              ]------ | Generate lots of hashes from random 'typeRep's and see whether one of them---   crashes.-prop_hash_total :: TestTree-prop_hash_total = testProperty "Hash function total" prop where-      prop = forAll arbitrary-                    (\tyCon -> hashType (unStripTypeRep tyCon) `seq` True)----instance Arbitrary TyCon where-      arbitrary = TyCon <$> arbitrary <*> arbitrary <*> arbitrary--instance Arbitrary TypeRep where-      arbitrary = TypeRep <$> arbitrary <*> args-            where args = listOf (modifySize (`div` 2) arbitrary)------ | Modify the size parameter of a 'Gen'.-modifySize :: (Int -> Int) -> Gen a -> Gen a-modifySize f gen = sized (\n -> resize (f n) gen)+-- #############################################################################+-- ###  API tests  #############################################################+-- #############################################################################   @@ -127,12 +102,11 @@        tree = testGroup "API" -      tests = [ testProperty "erase"            prop_erase-              , testProperty "mapTyped id law"  prop_mapTyped_id-              , testProperty "mapTyped f.g law" prop_mapTyped_compose-              , testProperty "reType"           prop_reType-              , testProperty "encodeTyped"      prop_encodeTyped-              , testProperty "encodeTypedLike"  prop_encodeTypedLike+      tests = [ testProperty "erase inverse of typed"                 prop_erase+              , testProperty "mapTyped id ~ id"                       prop_mapTyped_id+              , testProperty "mapTyped f.g ~ mapTyped f . mapTyped g" prop_mapTyped_compose+              , testProperty "reType equivalent to reconstruction"    prop_reType+              , testProperty "encodeTyped = encode.typed"             prop_encodeTyped               ]        prop_erase :: TypeFormat -> Int -> Bool@@ -154,13 +128,9 @@        prop_encodeTyped :: TypeFormat -> Int -> Bool       prop_encodeTyped format value =-            encodeTyped format value == encode (typed format value)--      prop_encodeTypedLike :: Typed Int -> Int -> Bool-      prop_encodeTypedLike ty value =-            (unsafeDecodeTyped (encodeTypedLike ty value) :: Int)+            (unsafeDecodeTyped (encodeTyped format value) :: Int)             ==-            unsafeDecodeTyped (encode (reValue (const value) ty))+            (unsafeDecodeTyped (encode (typed format value)) :: Int)   @@ -183,22 +153,115 @@ instance (Arbitrary a, Typeable a) => Arbitrary (Typed a) where       arbitrary = frequency [(10, plain), (5, cached), (3, cached2)]             where plain = typed <$> arbitrary <*> arbitrary-                  cached  = fmap precache plain-                  cached2 = fmap precache cached+                  cached  = fmap preserializeTyped plain+                  cached2 = fmap preserializeTyped cached+                  preserializeTyped (Typed ty x) = Typed (preserialize ty) x  instance Arbitrary TypeFormat where-      arbitrary = elements [Untyped, Hashed, Shown, Full]+      arbitrary = elements [Untyped, Hashed32, Hashed64, Shown, Full]   ++-- #############################################################################+-- ###  Internal functions  ####################################################+-- #############################################################################+++ prop_internal :: TestTree prop_internal = tree tests where        tree = testGroup "Internal" -      tests = [ testProperty "getFormat" prop_getFormat+      tests = [ localOption (QuickCheckMaxSize 10)+                            (testProperty "stripTypeRep . unStripTypeRep = id"+                                          prop_stripTypeRep_inverses)+              , testProperty "stripTyCon . unStripTyCon = id"+                             prop_stripTyCon_inverses+              , testProperty "getFormat extracts format correctly"+                             prop_getFormat               ] -      -- getFormat extracts the right format-      prop_getFormat :: Typed Double -> Bool-      prop_getFormat t@(Typed ty x) = t `isEqual` typed (getFormat ty) x++-- getFormat extracts the right format+prop_getFormat :: Typed Double -> Bool+prop_getFormat t@(Typed ty x) = t `isEqual` typed (getFormat ty) x+++prop_stripTypeRep_inverses :: TypeRep -> Bool+prop_stripTypeRep_inverses x = (stripTypeRep . unStripTypeRep) x == x++prop_stripTyCon_inverses :: TyCon -> Bool+prop_stripTyCon_inverses x = (stripTyCon . unStripTyCon) x == x+++++instance Arbitrary TyCon where+      arbitrary = TyCon <$> arbitrary <*> arbitrary <*> arbitrary++instance Arbitrary TypeRep where+      arbitrary = TypeRep <$> arbitrary <*> args+            where args = listOf (modifySize (`div` 2) arbitrary)++++-- | Modify the size parameter of a 'Gen'.+modifySize :: (Int -> Int) -> Gen a -> Gen a+modifySize f gen = sized (\n -> resize (f n) gen)++++++-- #############################################################################+-- ###  Encoding sizes  ########################################################+-- #############################################################################++++-- | Are the additional message sizes stated by the docs accurate?+--+-- Untyped:  +1 byte+-- Hashed32: +5 byte+-- Hashed64: +9 byte+prop_sizes :: TestTree+prop_sizes = tree tests where++      tree = testGroup "Data sizes"++      tests = [ testProperty "Untyped:  +1 byte"+                             (prop_size_added (encodeTyped Untyped)  1)+              , testProperty "Hashed32: +5 byte"+                             (prop_size_added (encodeTyped Hashed32) 5)+              , testProperty "Hashed64: +9 byte"+                             (prop_size_added (encodeTyped Hashed64) 9)+              ]+++type Complicated = Either (Char, Int) (Either String (Maybe Integer))++-- | Check whether data created with a certain format has a certain+--   overhead over the direct Binary serialization.+prop_size_added ::+         (forall a. (Typeable a, Binary a) => a -> BSL.ByteString)+      -> Int64+      -> Property+prop_size_added serializer n =+      conjoin [ forAll arbitrary (verify :: Integer     -> Bool)+              , forAll arbitrary (verify :: Double      -> Bool)+              , forAll arbitrary (verify :: [Double]    -> Bool)+              , forAll arbitrary (verify :: Complicated -> Bool)+              ]++      where++      binSize :: Binary a => a -> Int64+      binSize   = BSL.length . encode++      typedSize :: (Binary a, Typeable a) => a -> Int64+      typedSize = BSL.length . serializer++      verify :: (Binary a, Typeable a) => a -> Bool+      verify x = binSize x + n == typedSize x