packages feed

vary 0.1.0.5 → 0.1.1.0

raw patch · 9 files changed

+218/−4 lines, 9 filesdep +binarydep +bytestringdep +cerealdep ~QuickCheckPVP ok

version bump matches the API change (PVP)

Dependencies added: binary, bytestring, cereal

Dependency ranges changed: QuickCheck

API changes (from Hackage documentation)

+ Vary.VEither: instance Data.Binary.Class.Binary (Vary.Core.Vary (a : errs)) => Data.Binary.Class.Binary (Vary.VEither.VEither errs a)
+ Vary.VEither: instance Data.Serialize.Serialize (Vary.Core.Vary (a : errs)) => Data.Serialize.Serialize (Vary.VEither.VEither errs a)

Files

CHANGELOG.md view
@@ -8,6 +8,11 @@  ## Unreleased +## 0.1.1.0 - 2025-02-05++- Add serialization support of `Vary` for `binary`'s `Data.Binary` and `cereal`'s `Data.Serialize`.+- Document `Data.Aeson` round-tripping behaviour (namely: only round-trippable if encodings do not overlap, the `UntaggedValue` sum encoding).+ ## 0.1.0.5 - 2025-02-05  - Relax max allowed versions of DeepSeq (<1.6), Hashable (<1.6), QuickCheck (<2.16)
README.md view
@@ -313,6 +313,7 @@ but we don't want it to be visible to human readers:  ```haskell top:1+{-# OPTIONS_GHC -Wno-missing-export-lists #-} {-# OPTIONS_GHC -fdefer-type-errors #-} -- We want to show some incorrect examples! module Main where 
src/Vary.hs view
@@ -27,6 +27,7 @@     -- $setup     -- $motivating_example     -- $vary_and_exceptions+    -- $vary_and_serialization      -- * Core type definition     Vary,@@ -149,6 +150,19 @@ -- >>> catcher (throw AllocationLimitExceeded) -- *** Exception: allocation limit exceeded ++-- $vary_and_serialization+--+-- == (De)Serializing Vary values+--+-- `Vary` has optional dependencies to enable `aeson`'s `Data.Aeson`, `binary`'s `Data.Binary` and `cereal`'s `Data.Serealize` serialization.+--+-- Specifically for Aeson serialization, Vary datatypes are encoded+-- as their ['UntaggedValue'](https://hackage.haskell.org/package/aeson-2.0.3.0/docs/Data-Aeson-Types.html#t:SumEncoding) encoding.+-- This means that serialization to JSON only round-trips when the encodings are disjoint;+-- on decoding, the first variant to succeed is used.+--+-- The Binary and Serialize instances always round-trip, as their encoding contains the variant's tag index.  -- | Builds a Vary from the given value. --
src/Vary/Core.hs view
@@ -30,6 +30,14 @@ import Test.QuickCheck.Arbitrary (GSubterms, RecursivelyShrink) # endif +# ifdef FLAG_BINARY+import Data.Binary qualified as Binary+# endif++# ifdef FLAG_CEREAL+import Data.Serialize qualified as Cereal+# endif+ -- $setup -- >>> :set -XGHC2021 -- >>> :set -XDataKinds@@ -231,6 +239,10 @@  deriving instance (Aeson.FromJSON a) => Aeson.FromJSON (Vary '[a]) +-- | This instance round-trips iff there is no overlap between the encodings of the element types.+--+-- For example, a `Vary '[Int, String] is round-trippable+-- but a `Vary '[String, Char]` is not. instance (Aeson.FromJSON a, Aeson.FromJSON (Vary (b : bs))) => Aeson.FromJSON (Vary (a : b : bs)) where   {-# INLINE parseJSON #-}   parseJSON val = (pushHead <$> Aeson.parseJSON val) <|> (pushTail <$> Aeson.parseJSON val)@@ -239,6 +251,10 @@  deriving instance (Aeson.ToJSON a) => Aeson.ToJSON (Vary '[a]) +-- | This instance round-trips iff there is no overlap between the encodings of the element types.+--+-- For example, a `Vary '[Int, String] is round-trippable+-- but a `Vary '[String, Char]` is not. instance (Aeson.ToJSON a, Aeson.ToJSON (Vary (b : bs))) => Aeson.ToJSON (Vary (a : b : bs)) where   {-# INLINE toJSON #-}   toJSON vary =@@ -291,4 +307,79 @@   where   hashWithSalt salt vary@(Vary tag _inner) = fromIntegral tag `hashWithSalt` badHashWithSalt salt vary   hash vary@(Vary tag _inner) = badHashWithSalt (fromIntegral tag) vary+#endif++#ifdef FLAG_BINARY+class BinaryHelper a where+  binaryPutVariant :: a -> Binary.Put+  binaryGetVariant :: Word -> Binary.Get a++instance BinaryHelper (Vary '[]) where+  {-# INLINE binaryPutVariant #-}+  binaryPutVariant emptyVary = case from emptyVary of {}++  {-# INLINE binaryGetVariant #-}+  binaryGetVariant = emptyVaryError "binaryGetVariant" undefined++instance+  ( Binary.Binary a+  , BinaryHelper (Vary as)+  ) =>+  BinaryHelper (Vary (a : as))+  where+  {-# INLINE binaryPutVariant #-}+  binaryPutVariant vary = case pop vary of+    Right val -> Binary.put val+    Left val -> binaryPutVariant val++  {-# INLINE binaryGetVariant #-}+  binaryGetVariant 0 = pushHead <$> Binary.get @a+  binaryGetVariant n = pushTail <$> binaryGetVariant @(Vary as) (n - 1)++instance (BinaryHelper (Vary as)) => Binary.Binary (Vary as) where+  {-# INLINE put #-}+  put vary@(Vary n _) = do+    Binary.put n+    binaryPutVariant vary+  {-# INLINE get #-}+  get = do+    tag <- Binary.get+    binaryGetVariant tag+#endif++#ifdef FLAG_CEREAL+class SerializeHelper a where+  cerealPutVariant :: a -> Cereal.Put+  cerealGetVariant :: Word -> Cereal.Get a++instance SerializeHelper (Vary '[]) where+  {-# INLINE cerealPutVariant #-}+  cerealPutVariant emptyVary = case from emptyVary of {} +  {-# INLINE cerealGetVariant #-}+  cerealGetVariant = emptyVaryError "cerealGetVariant" undefined++instance+  ( Cereal.Serialize a+  , SerializeHelper (Vary as)+  ) =>+  SerializeHelper (Vary (a : as))+  where+  {-# INLINE cerealPutVariant #-}+  cerealPutVariant vary = case pop vary of+    Right val -> Cereal.put val+    Left val -> cerealPutVariant val++  {-# INLINE cerealGetVariant #-}+  cerealGetVariant 0 = pushHead <$> Cereal.get @a+  cerealGetVariant n = pushTail <$> cerealGetVariant @(Vary as) (n - 1)++instance (SerializeHelper (Vary as)) => Cereal.Serialize (Vary as) where+  {-# INLINE put #-}+  put vary@(Vary n _) = do+    Cereal.putWord64le (fromIntegral n)+    cerealPutVariant vary+  {-# INLINE get #-}+  get = do+    tag <- Cereal.getWord64le+    cerealGetVariant (fromIntegral tag) #endif
src/Vary/VEither.hs view
@@ -62,6 +62,14 @@ import Test.QuickCheck # endif +# ifdef FLAG_CEREAL+import Data.Serialize qualified as Cereal+# endif++# ifdef FLAG_BINARY+import Data.Binary qualified as Binary+# endif+ -- $setup -- -- This module is intended to be used qualified:@@ -353,4 +361,12 @@  #ifdef FLAG_QUICKCHECK deriving instance (Arbitrary (Vary (a : errs))) => Test.QuickCheck.Arbitrary (VEither errs a)+#endif++#ifdef FLAG_CEREAL+deriving instance (Cereal.Serialize (Vary (a : errs))) => Cereal.Serialize (VEither errs a)+#endif++#ifdef FLAG_BINARY+deriving instance (Binary.Binary (Vary (a : errs))) => Binary.Binary (VEither errs a) #endif
test/README.lhs view
@@ -313,6 +313,7 @@ but we don't want it to be visible to human readers:  ```haskell top:1+{-# OPTIONS_GHC -Wno-missing-export-lists #-} {-# OPTIONS_GHC -fdefer-type-errors #-} -- We want to show some incorrect examples! module Main where 
test/doctest/doctests.hs view
@@ -1,3 +1,4 @@+{-# OPTIONS_GHC -Wno-missing-export-lists #-} module Main where  import Test.DocTest (mainFromCabal)
test/spec/spec.hs view
@@ -1,3 +1,40 @@-module Main where+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Main (main) where++import Test.Hspec+import Test.Hspec.QuickCheck++import Data.Aeson qualified as Aeson+import Data.Binary qualified as Binary+import Data.ByteString qualified as BS+import Data.ByteString.Lazy qualified as LBS+import Data.Serialize qualified as Cereal++import Vary (Vary)++type TestVariant = Vary '[Int, String, Maybe [Bool]]+ main :: IO ()-main = putStrLn "(No separate unit tests yet)"+main = hspec $ do+  describe "Serialization with aeson" $ do+    prop "encodes properly" $ \(v :: TestVariant) -> do+      Aeson.encode v `shouldSatisfy` (not . LBS.null)+    prop "encode - decode roundtrips" $ \(v :: TestVariant) -> do+      -- NOTE: `TestVariant` is chosen so the JSON encodings do not overlap.+      -- Without that, this property would not hold+      Aeson.eitherDecode (Aeson.encode v) `shouldBe` Right v++  describe "Serialization with binary" $ do+    prop "encodes properly" $ \(v :: TestVariant) -> do+      Binary.encode v `shouldSatisfy` (not . LBS.null)+    prop "encode - decode roundtrips" $ \(v :: TestVariant) -> do+      Binary.decode (Binary.encode v) `shouldBe` v++  describe "Serialization with cereal" $ do+    prop "encodes properly" $ \(v :: TestVariant) -> do+      Cereal.encode v `shouldSatisfy` (not . BS.null)+    prop "encode - decode roundtrips" $ \(v :: TestVariant) -> do+      Cereal.decode (Cereal.encode v) `shouldBe` Right v
vary.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           vary-version:        0.1.0.5+version:        0.1.1.0 synopsis:       Vary: Friendly and fast polymorphic variants (open unions/coproducts/extensible sums) description:    Vary: Friendly and fast Variant types for Haskell                 .@@ -40,6 +40,16 @@   manual: True   default: True +flag binary+  description: When enabled implements the @Binary@ typeclass from the binary library. When disabled, does not depend on the binary library.+  manual: True+  default: True++flag cereal+  description: When enabled implements the @Serialize@ typeclass from the cereal library. When disabled, does not depend on the cereal library.+  manual: True+  default: True+ flag hashable   description: When enabled implements the @Hashable@ typeclass from the hashable library. When disabled, does not depend on the @hashable@ library.   manual: True@@ -72,6 +82,14 @@     cpp-options: -DFLAG_AESON     build-depends:         aeson >=2.0.0 && <2.3+  if flag(binary)+    cpp-options: -DFLAG_BINARY+    build-depends:+        binary >=0.8.0.0 && <0.9.0.0+  if flag(cereal)+    cpp-options: -DFLAG_CEREAL+    build-depends:+        cereal >=0.5.0.0 && <0.6.0.0   if flag(quickcheck)     cpp-options: -DFLAG_QUICKCHECK     build-depends:@@ -99,6 +117,14 @@     cpp-options: -DFLAG_AESON     build-depends:         aeson >=2.0.0 && <2.3+  if flag(binary)+    cpp-options: -DFLAG_BINARY+    build-depends:+        binary >=0.8.0.0 && <0.9.0.0+  if flag(cereal)+    cpp-options: -DFLAG_CEREAL+    build-depends:+        cereal >=0.5.0.0 && <0.6.0.0   if flag(quickcheck)     cpp-options: -DFLAG_QUICKCHECK     build-depends:@@ -128,6 +154,14 @@     cpp-options: -DFLAG_AESON     build-depends:         aeson >=2.0.0 && <2.3+  if flag(binary)+    cpp-options: -DFLAG_BINARY+    build-depends:+        binary >=0.8.0.0 && <0.9.0.0+  if flag(cereal)+    cpp-options: -DFLAG_CEREAL+    build-depends:+        cereal >=0.5.0.0 && <0.6.0.0   if flag(quickcheck)     cpp-options: -DFLAG_QUICKCHECK     build-depends:@@ -142,8 +176,14 @@       test/spec   ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N   build-depends:-      base >=4.7 && <5+      QuickCheck >=2.12 && <2.15+    , aeson >=2.0.0 && <2.3+    , base >=4.7 && <5+    , binary+    , bytestring+    , cereal     , deepseq >=1.4.0 && <1.6+    , hspec     , vary   default-language: Haskell2010   if flag(hashable)@@ -154,6 +194,14 @@     cpp-options: -DFLAG_AESON     build-depends:         aeson >=2.0.0 && <2.3+  if flag(binary)+    cpp-options: -DFLAG_BINARY+    build-depends:+        binary >=0.8.0.0 && <0.9.0.0+  if flag(cereal)+    cpp-options: -DFLAG_CEREAL+    build-depends:+        cereal >=0.5.0.0 && <0.6.0.0   if flag(quickcheck)     cpp-options: -DFLAG_QUICKCHECK     build-depends: