generic-data-functions 0.1.0 → 0.1.1
raw patch · 8 files changed
+179/−46 lines, 8 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Generic.Data.Function.Example: D1 :: a -> D a
+ Generic.Data.Function.Example: D2 :: a -> a -> D a
+ Generic.Data.Function.Example: Showly :: a -> Showly a
+ Generic.Data.Function.Example: [unShowly] :: Showly a -> a
+ Generic.Data.Function.Example: data D a
+ Generic.Data.Function.Example: instance GHC.Base.Monoid a => GHC.Base.Monoid (Generic.Data.Function.Example.Showly a)
+ Generic.Data.Function.Example: instance GHC.Base.Semigroup a => GHC.Base.Semigroup (Generic.Data.Function.Example.Showly a)
+ Generic.Data.Function.Example: instance GHC.Generics.Generic (Generic.Data.Function.Example.D a)
+ Generic.Data.Function.Example: instance GHC.Show.Show a => GHC.Show.Show (Generic.Data.Function.Example.D a)
+ Generic.Data.Function.Example: instance GHC.Show.Show a => GHC.Show.Show (Generic.Data.Function.Example.Showly a)
+ Generic.Data.Function.Example: instance Generic.Data.Function.FoldMap.Constructor.GenericFoldMap (Generic.Data.Function.Example.Showly GHC.Base.String)
+ Generic.Data.Function.Example: newtype Showly a
+ Generic.Data.Function.FoldMap: class GFoldMapSumConsByte m f
+ Generic.Data.Function.FoldMap: genericFoldMapSumConsByte :: forall m a. (Generic a, GFoldMapSumConsByte m (Rep a)) => (Word8 -> m) -> a -> m
+ Generic.Data.Function.FoldMap.SumConsByte: class GFoldMapCSumCtr m f
+ Generic.Data.Function.FoldMap.SumConsByte: class GFoldMapCSumCtrArityByte m (arity :: Natural) f
+ Generic.Data.Function.FoldMap.SumConsByte: class GFoldMapSumConsByte m f
+ Generic.Data.Function.FoldMap.SumConsByte: gFoldMapCSumCtr :: GFoldMapCSumCtr m f => f p -> m
+ Generic.Data.Function.FoldMap.SumConsByte: gFoldMapCSumCtrArityByte :: GFoldMapCSumCtrArityByte m arity f => (Word8 -> m) -> f p -> m
+ Generic.Data.Function.FoldMap.SumConsByte: gFoldMapSumConsByte :: GFoldMapSumConsByte m f => (Word8 -> m) -> f p -> m
+ Generic.Data.Function.FoldMap.SumConsByte: instance (Generic.Data.Function.FoldMap.SumConsByte.FitsInByte (Generic.Data.Function.FoldMap.SumConsByte.SumArity (l GHC.Generics.:+: r)), Generic.Data.Function.FoldMap.SumConsByte.GFoldMapCSumCtrArityByte m 0 (l GHC.Generics.:+: r), Generic.Data.Function.FoldMap.SumConsByte.GFoldMapCSumCtr m (l GHC.Generics.:+: r), GHC.Base.Semigroup m) => Generic.Data.Function.FoldMap.SumConsByte.GFoldMapSumConsByte m (l GHC.Generics.:+: r)
+ Generic.Data.Function.FoldMap.SumConsByte: instance (Generic.Data.Function.FoldMap.SumConsByte.GFoldMapCSumCtrArityByte m arity l, Generic.Data.Function.FoldMap.SumConsByte.GFoldMapCSumCtrArityByte m (arity GHC.TypeNats.+ Generic.Data.Function.FoldMap.SumConsByte.SumArity l) r) => Generic.Data.Function.FoldMap.SumConsByte.GFoldMapCSumCtrArityByte m arity (l GHC.Generics.:+: r)
+ Generic.Data.Function.FoldMap.SumConsByte: instance (TypeError ...) => Generic.Data.Function.FoldMap.SumConsByte.GFoldMapSumConsByte m GHC.Generics.V1
+ Generic.Data.Function.FoldMap.SumConsByte: instance forall k (arity :: GHC.TypeNats.Nat) m (c :: GHC.Generics.Meta) (f :: k -> Type). GHC.TypeNats.KnownNat arity => Generic.Data.Function.FoldMap.SumConsByte.GFoldMapCSumCtrArityByte m arity (GHC.Generics.C1 c f)
+ Generic.Data.Function.FoldMap.SumConsByte: instance forall k m (c :: GHC.Generics.Meta) (f :: k -> Type). (TypeError ...) => Generic.Data.Function.FoldMap.SumConsByte.GFoldMapSumConsByte m (GHC.Generics.C1 c f)
+ Generic.Data.Function.FoldMap.SumConsByte: instance forall k m (f :: k -> Type) (c :: GHC.Generics.Meta). Generic.Data.Function.FoldMap.Constructor.GFoldMapC m f => Generic.Data.Function.FoldMap.SumConsByte.GFoldMapCSumCtr m (GHC.Generics.C1 c f)
+ Generic.Data.Function.FoldMap.SumConsByte: instance forall k m (f :: k -> Type) (c :: GHC.Generics.Meta). Generic.Data.Function.FoldMap.SumConsByte.GFoldMapSumConsByte m f => Generic.Data.Function.FoldMap.SumConsByte.GFoldMapSumConsByte m (GHC.Generics.D1 c f)
+ Generic.Data.Function.FoldMap.SumConsByte: instance forall k m (l :: k -> Type) (r :: k -> Type). (Generic.Data.Function.FoldMap.SumConsByte.GFoldMapCSumCtr m l, Generic.Data.Function.FoldMap.SumConsByte.GFoldMapCSumCtr m r) => Generic.Data.Function.FoldMap.SumConsByte.GFoldMapCSumCtr m (l GHC.Generics.:+: r)
+ Generic.Data.Function.FoldMap.SumConsByte: type FitsInByte n = FitsInByteResult (n <=? 255)
+ Generic.Data.Function.FoldMap.SumConsByte: type family TypeErrorMessage (a :: Symbol) :: Constraint
Files
- CHANGELOG.md +4/−0
- README.md +30/−44
- generic-data-functions.cabal +3/−1
- src/Generic/Data/Function/Example.hs +14/−0
- src/Generic/Data/Function/FoldMap.hs +22/−0
- src/Generic/Data/Function/FoldMap/Sum.hs +12/−0
- src/Generic/Data/Function/FoldMap/SumConsByte.hs +93/−0
- src/Generic/Data/Function/Traverse/Sum.hs +1/−1
CHANGELOG.md view
@@ -1,3 +1,7 @@+## 0.1.1 (2023-07-20)+ * add work-in-progress store-style generic `foldMap`, encoding constructors by+ their index, at `Generic.Data.Function.FoldMap.SumConsByte`+ ## 0.1.0 (2023-06-23) Initial release.
README.md view
@@ -7,6 +7,36 @@ to pass a handful of definitions. Obtain simple, type-safe generic serializers/reducers and parsers for almost zero effort. +Well, OK, so really this is a generic binary serialization library. It just so+happens that the generics look like `foldMap` and `traverse`.++## Really?+Kind of. We only handle *sequential concatenation*, being cleanly represented by+builtin type classes. Weirder cases like JSON parsing/serialization have more+going on, and aren't sensibly discussed generically. So you are probably only+going to use this with bytestrings and simple binary formats.++## Why?+It is 2023. There are a number of competing parsing and serialization Haskell+libraries, recently some notable high-performance ones. These are often fairly+experimental. Maybe you want some generics to benchmark some real-world use case+against popular libraries like binary and cereal. But maybe generics aren't+provided. Shucks.++That's a shame, because a binary/cereal-esque generic binary parser or+serializer doesn't have much work to do:++ * traverse the generic sum-of-products tree of the given type left to right+ * defer to the appropriate type class for base cases++Sum types necessitate a little more work. Otherwise, most such parsers and+serializers look fairly comparable to each other. Why are we rewriting this+stuff over and over again?++generic-data-functions provides *reusable generics* which have holes in for your+favorite parsers and serializers. Fill out a few definitions to receive a fresh+new generic instance for your own library, without all the boilerplate.+ ## Functions ### `foldMap` (L->R) ```haskell@@ -45,47 +75,3 @@ ## License Provided under the MIT license. See `LICENSE` for license text.-------senserial is a small library providing reusable generics for (binary) parsers-and serializers. No need to muddle through boilerplate generics that look the-same as everyone else's; just provide a few definitions and senserial can give-you powerful generic instances.--Currently an unofficial library, distributed as part of binrep. Reader, please-let the author know if you'd like it released separately.--## Why?-It is 2023. There are a number of competing parsing and serialization Haskell-libraries, and some notable high-performance binary serialization libraries.-These are often fairly experimental. Maybe you want some generics to benchmark-some real-world use case against popular libraries like binary and cereal. But-maybe generics aren't provided. Shucks.--That's a shame, because a pure generic binary parser or serializer doesn't have-much work to do:-- * traverse the generic sum-of-products tree of the given type left to right- * defer to the appropriate type class for base cases--Sum types necessitate a little more work. Otherwise, most generic binary parsers-and serializers look fairly comparable to each other. Why are we rewriting this-stuff over and over again?--senserial provides *reusable generics* which have holes in for your favourite-parsers and serializers. Fill out a few definitions to receive a fresh new-generic instance for your own library, without all the boilerplate.--## Really?-Kind of. In reality, this library can only handle cases where no configuration-is needed other than what is provided in the data type itself. senserial-provides the generic traversal, and you can't alter that. Plus, the only-often rewritten and straightforward traversal I can think of is sequential field-concatenation. So though the code isn't limited to bytestrings and binary-serialization formats, you will have trouble using it for anything else, because-anything else will probably require a very different traversal (e.g. JSON-serialization).--In short, the primary use of this library is to pull out the common generics-patterns from binary serialization libraries for easy reuse.
generic-data-functions.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: generic-data-functions-version: 0.1.0+version: 0.1.1 synopsis: Familiar functions lifted to generic data types description: Please see README.md. category: Data, Serialization@@ -28,10 +28,12 @@ exposed-modules: Generic.Data.Function Generic.Data.Function.Error+ Generic.Data.Function.Example Generic.Data.Function.FoldMap Generic.Data.Function.FoldMap.Constructor Generic.Data.Function.FoldMap.NonSum Generic.Data.Function.FoldMap.Sum+ Generic.Data.Function.FoldMap.SumConsByte Generic.Data.Function.Traverse Generic.Data.Function.Traverse.Constructor Generic.Data.Function.Traverse.NonSum
+ src/Generic/Data/Function/Example.hs view
@@ -0,0 +1,14 @@+module Generic.Data.Function.Example where++import GHC.Generics ( Generic )+import Generic.Data.Function.FoldMap.Constructor ( GenericFoldMap(..) )++data D a = D1 a | D2 a a deriving stock (Generic, Show)++newtype Showly a = Showly { unShowly :: a }+ deriving Show via a+ deriving (Semigroup, Monoid) via a++instance GenericFoldMap (Showly String) where+ type GenericFoldMapC (Showly String) a = Show a+ genericFoldMapF = Showly . show
src/Generic/Data/Function/FoldMap.hs view
@@ -29,6 +29,7 @@ ( GenericFoldMap(..) , genericFoldMapNonSum, GFoldMapNonSum , genericFoldMapSum, GFoldMapSum+ , genericFoldMapSumConsByte, GFoldMapSumConsByte ) where import GHC.Generics@@ -36,6 +37,8 @@ import Generic.Data.Function.FoldMap.NonSum import Generic.Data.Function.FoldMap.Sum import Generic.Data.Function.FoldMap.Constructor+import Generic.Data.Function.FoldMap.SumConsByte+import Data.Word ( Word8 ) -- | Generic 'foldMap' over a term of non-sum data type @a@. --@@ -51,9 +54,28 @@ -- @a@ must have at least two constructors. -- -- You must provide a function for mapping constructor names to monoidal values.+--+-- This is the most generic option, but depending on your string manipulation+-- may be slower. genericFoldMapSum :: forall m a . (Generic a, GFoldMapSum m (Rep a)) => (String -> m) -> a -> m genericFoldMapSum f = gFoldMapSum f . from++-- | Generic 'foldMap' over a term of sum data type @a@ where constructors are+-- mapped to their index (distance from first/leftmost constructor)+--+-- @a@ must have at least two constructors.+--+-- You must provide a function for mapping bytes to monoidal values.+--+-- This should be fairly fast, but sadly I think it's slower than the generics+-- in store and binary/cereal libraries.+genericFoldMapSumConsByte+ :: forall m a+ . (Generic a, GFoldMapSumConsByte m (Rep a))+ => (Word8 -> m)+ -> a -> m+genericFoldMapSumConsByte f = gFoldMapSumConsByte f . from
src/Generic/Data/Function/FoldMap/Sum.hs view
@@ -1,5 +1,13 @@ {-# LANGUAGE UndecidableInstances #-} -- required below GHC 9.6 +{- | 'foldMap' for sum types where constructors are encoded by mapping the+ constructor name.++Note that constructor names are unique per type. So as long as your mapping+function similarly outputs unique values of your monoid for each constructor,+you should be able to "reverse" the process (e.g. for generic 'traverse').+-}+ module Generic.Data.Function.FoldMap.Sum where import GHC.Generics@@ -23,6 +31,10 @@ instance TypeError ENoEmpty => GFoldMapSum m V1 where gFoldMapSum = undefined +-- | Sum type handler prefixing constructor contents with their mapped+-- constructor name via a provided @String -> m@.+--+-- TODO rename class GFoldMapCSum m f where gFoldMapCSum :: (String -> m) -> f p -> m instance (GFoldMapCSum m l, GFoldMapCSum m r) => GFoldMapCSum m (l :+: r) where
+ src/Generic/Data/Function/FoldMap/SumConsByte.hs view
@@ -0,0 +1,93 @@+{-# LANGUAGE UndecidableInstances #-} -- required below GHC 9.6+{-# LANGUAGE AllowAmbiguousTypes #-} -- due to type class design+{-# LANGUAGE CPP #-} -- due to TypeErrorMessage hack++{- | 'foldMap' for sum types, where constructors are encoded by index+ (distance from first/leftmost constructor) in a single byte, which is+ prepended to their contents.++TODO. Clumsy and limited. And yet, still handy enough I think.+-}++module Generic.Data.Function.FoldMap.SumConsByte where++import GHC.Generics+import GHC.TypeLits+import Data.Kind ( Type, Constraint )+import Generic.Data.Function.Util.TypeNats ( natVal'' )+import Generic.Data.Function.Error ( type ENoEmpty, type EUnexpectedNonSum )+import Generic.Data.Function.FoldMap.Constructor ( GFoldMapC(gFoldMapC) )++import Data.Word ( Word8 )++class GFoldMapSumConsByte m f where+ gFoldMapSumConsByte :: (Word8 -> m) -> f p -> m++instance GFoldMapSumConsByte m f => GFoldMapSumConsByte m (D1 c f) where+ gFoldMapSumConsByte f (M1 a) = gFoldMapSumConsByte f a++instance+ ( FitsInByte (SumArity (l :+: r))+ , GFoldMapCSumCtrArityByte m 0 (l :+: r)+ , GFoldMapCSumCtr m (l :+: r)+ , Semigroup m+ ) => GFoldMapSumConsByte m (l :+: r) where+ gFoldMapSumConsByte f lr =+ gFoldMapCSumCtrArityByte @m @0 f lr <> gFoldMapCSumCtr lr++instance TypeError EUnexpectedNonSum => GFoldMapSumConsByte m (C1 c f) where+ gFoldMapSumConsByte _ = undefined++instance TypeError ENoEmpty => GFoldMapSumConsByte m V1 where+ gFoldMapSumConsByte _ = undefined++---++-- | Sum type handler handling constructors only. Useful if you handle+-- constructor prefixes elsewhere.+class GFoldMapCSumCtr m f where gFoldMapCSumCtr :: f p -> m++instance (GFoldMapCSumCtr m l, GFoldMapCSumCtr m r)+ => GFoldMapCSumCtr m (l :+: r) where+ gFoldMapCSumCtr = \case L1 l -> gFoldMapCSumCtr l+ R1 r -> gFoldMapCSumCtr r++instance GFoldMapC m f => GFoldMapCSumCtr m (C1 c f) where+ gFoldMapCSumCtr (M1 a) = gFoldMapC a++---++class GFoldMapCSumCtrArityByte m (arity :: Natural) f where+ gFoldMapCSumCtrArityByte :: (Word8 -> m) -> f p -> m++instance+ ( GFoldMapCSumCtrArityByte m arity l+ , GFoldMapCSumCtrArityByte m (arity + SumArity l) r+ ) => GFoldMapCSumCtrArityByte m arity (l :+: r) where+ gFoldMapCSumCtrArityByte f = \case+ L1 l -> gFoldMapCSumCtrArityByte @m @arity f l+ R1 r -> gFoldMapCSumCtrArityByte @m @(arity + SumArity l) f r++instance KnownNat arity => GFoldMapCSumCtrArityByte m arity (C1 c f) where+ gFoldMapCSumCtrArityByte f _ = f (fromIntegral (natVal'' @arity))++---++type family SumArity (a :: Type -> Type) :: Natural where+ SumArity (C1 c a) = 1+ SumArity (x :+: y) = SumArity x + SumArity y++type FitsInByte n = FitsInByteResult (n <=? 255)++type family FitsInByteResult (b :: Bool) :: Constraint where+ FitsInByteResult 'True = ()+ FitsInByteResult 'False = TypeErrorMessage+ "TODO ya type had more than 255 constructors"++type family TypeErrorMessage (a :: Symbol) :: Constraint where+#if MIN_VERSION_base(4,9,0)+ TypeErrorMessage a = TypeError ('Text a)+-- GHC < 8.0 does not support empty closed type families+#elif __GLASGOW_HASKELL__ < 800+ TypeErrorMessage a = a ~ ""+#endif
src/Generic/Data/Function/Traverse/Sum.hs view
@@ -13,7 +13,7 @@ import Control.Applicative qualified as Applicative import Control.Applicative ( Alternative((<|>)) ) -{- | Sum-type monads that can be generically 'traverse'd.+{- | Sum type monads that can be generically 'traverse'd. For sum types, we require a monad with choice to differentiate constructors. -}