packages feed

red-black-record 1.0.0.1 → 1.0.0.2

raw patch · 5 files changed

+132/−49 lines, 5 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Data.RBR.Internal: instance (GHC.TypeLits.CmpSymbol k' k Data.Type.Equality.~ ordering, Data.RBR.Internal.KeyHelper ordering k ('Data.RBR.Internal.N color left k' v' right)) => Data.RBR.Internal.Key k ('Data.RBR.Internal.N color left k' v' right)
- Data.RBR.Internal: instance Data.RBR.Internal.Key k left => Data.RBR.Internal.KeyHelper 'GHC.Types.GT k ('Data.RBR.Internal.N color left k' v' right)
- Data.RBR.Internal: instance Data.RBR.Internal.Key k right => Data.RBR.Internal.KeyHelper 'GHC.Types.LT k ('Data.RBR.Internal.N color left k' v' right)
- Data.RBR.Internal: instance Data.RBR.Internal.KeyHelper 'GHC.Types.EQ k ('Data.RBR.Internal.N color left k v right)
+ Data.RBR.Internal: instance (GHC.TypeLits.CmpSymbol k' k Data.Type.Equality.~ ordering, Data.RBR.Internal.KeyHelper ordering k left v' right) => Data.RBR.Internal.Key k ('Data.RBR.Internal.N color left k' v' right)
+ Data.RBR.Internal: instance (GHC.TypeLits.CmpSymbol k2 k Data.Type.Equality.~ ordering, Data.RBR.Internal.KeyHelper ordering k left2 v2 right2) => Data.RBR.Internal.KeyHelper 'GHC.Types.GT k ('Data.RBR.Internal.N color2 left2 k2 v2 right2) v' right
+ Data.RBR.Internal: instance (GHC.TypeLits.CmpSymbol k2 k Data.Type.Equality.~ ordering, Data.RBR.Internal.KeyHelper ordering k left2 v2 right2) => Data.RBR.Internal.KeyHelper 'GHC.Types.LT k left v ('Data.RBR.Internal.N color2 left2 k2 v2 right2)
+ Data.RBR.Internal: instance Data.RBR.Internal.KeyHelper 'GHC.Types.EQ k left v right
- Data.RBR.Internal: branch' :: KeyHelper ordering k t => (Variant f t -> Maybe (f (Value' ordering k t)), f (Value' ordering k t) -> Variant f t)
+ Data.RBR.Internal: branch' :: KeyHelper ordering k left v right => (Variant f (N colorx left kx v right) -> Maybe (f (Value' ordering k left v right)), f (Value' ordering k left v right) -> Variant f (N colorx left kx v right))
- Data.RBR.Internal: class KeyHelper (ordering :: Ordering) (k :: Symbol) (t :: RBT Symbol Type) where {
+ Data.RBR.Internal: class KeyHelper (ordering :: Ordering) (k :: Symbol) (left :: RBT Symbol Type) (v :: Type) (right :: RBT Symbol Type) where {
- Data.RBR.Internal: field' :: KeyHelper ordering k t => Record f t -> (f (Value' ordering k t) -> Record f t, f (Value' ordering k t))
+ Data.RBR.Internal: field' :: KeyHelper ordering k left v right => Record f (N colorx left kx v right) -> (f (Value' ordering k left v right) -> Record f (N colorx left kx v right), f (Value' ordering k left v right))

Files

CHANGELOG.md view
@@ -1,5 +1,9 @@ # Revision history for red-black-record
 
-## 1.0.0.0 -- YYYY-mm-dd
+## 1.0.0.2
+
+* Improved compilation times for getters by refactoring `KeyHelper`.
+
+## 1.0.0.0
 
 * First version. Released on an unsuspecting world.
README.md view
@@ -1,6 +1,8 @@-# red-black-record
+red-black-record
+================
 
-## What's this?
+What's this?
+------------
 
 A library that provides extensible records and variants, both indexed by a
 type-level [red-black](https://en.wikipedia.org/wiki/Red%E2%80%93black_tree)
@@ -36,25 +38,22 @@ For examples on how to use the library, check the haddocks for the
 `Data.RBR.Examples` module.
 
-## FAQ
+FAQ
+---
 
 ### What extensions do I need to use this library?
 
-- `DataKinds`
-
-- `TypeApplications`
-
-  To be able to specify field and branch names.
+- `DataKinds`.
 
-- `FlexibleContexts`
+- `TypeApplications` to be able to specify field and branch names.
 
-- `TypeFamilies`
+- `TypeFamilies`.
 
-- `DeriveGeneric `
+- `FlexibleContexts`.
 
-  For interfacing with normal records.
+- `DeriveGeneric` for interfacing with normal records.
 
-- `PartialTypeSignatures`
+- `PartialTypeSignatures` for hiding complex tree types.
 
 ### My type signatures are getting big and scary because of those type-level trees. What to do?
 
@@ -129,8 +128,25 @@ That isn't implemented (yet). It would require key deletion on the type-level
 tree.
 
-## Inspirations
+### What about compilation times?
 
+Sadly, compilation times balloon for large records. In the tests folder there's
+an example (not run by default in the tests) of the construction of a 50-field
+record whose fields are afterwards accessed one by one. It takes on the order
+of one minute to compile in my machine. 
+
+Surprisingly perhaps, most of the compilation time is *not* spent in the
+construction of the record itself, but in checking the applicability of the
+getters. If one removes the getter accesses, the program compiles more than
+twice faster. 
+
+The default generics-based implementations of `FromRecord` and `FromVariant`
+use the same type-level machinery as the getters and its use will likely slow
+down compilation as well :(
+
+Inspirations
+------------
+
 - The code for the red-black tree has been lifted from ["Persistent Red Black
   Trees in Haskell"](https://abhiroop.github.io/Haskell-Red-Black-Tree/).
 
@@ -142,7 +158,8 @@   types](https://blog.poisson.chat/posts/2018-11-26-type-surgery.html).
   [reddit](https://www.reddit.com/r/haskell/comments/a0gi4z/surgery_for_data_types/).
 
-## Alternatives
+Alternatives
+------------
 
 - [generics-sop](http://hackage.haskell.org/package/generics-sop) and
   [records-sop](http://hackage.haskell.org/package/records-sop). Like
@@ -179,7 +196,10 @@   extension](https://github.com/ghc-proposals/ghc-proposals/pull/180) for
   Haskell.
 
-- [generic-data-surgery](https://hackage.haskell.org/package/generic-data-surgery)
+- [generic-data-surgery](https://hackage.haskell.org/package/generic-data-surgery).
+  Lots of useful machinery for manipulating generic representations of
+  dataytpes, without requiring intrusive changes to the original
+  representation.
 
-- [Coxswain](https://ghc.haskell.org/trac/ghc/wiki/Plugins/TypeChecker/RowTypes/Coxswain)
+- [Coxswain](https://ghc.haskell.org/trac/ghc/wiki/Plugins/TypeChecker/RowTypes/Coxswain).
 
lib/Data/RBR/Examples.hs view
@@ -22,6 +22,9 @@    
     -- * Parsing a subset of a record's fields from JSON and inserting them in an existing record value
     -- $json3
+    
+    -- * Ensuring all branches of a sum type are parsed from JSON
+    -- $json4sum
     ) where
 
 import Data.RBR
@@ -36,9 +39,10 @@ >>> :set -Wno-partial-type-signatures  
 >>> import Data.RBR
 >>> import Data.SOP
->>> import Data.SOP.NP (cpure_NP,sequence_NP,liftA2_NP)
+>>> import Data.SOP.NP (cpure_NP,sequence_NP,liftA2_NP,collapse_NP)
 >>> import Data.String
 >>> import Data.Proxy
+>>> import Data.Foldable
 >>> import Data.Profunctor (Star(..))
 >>> import GHC.Generics
 >>> import qualified Data.Text
@@ -149,8 +153,8 @@               -> Data.Aeson.Value 
               -> Parser r
         parseSpecial transform = 
-            let pr = transform $ fromNP @c (cpure_NP (Proxy @FromJSON) (Star parseJSON))
-                mapKSS (K name) (Star pf) = Star (\o -> explicitParseField pf o (Data.Text.pack name))
+            let mapKSS (K name) (Star pf) = Star (\o -> explicitParseField pf o (Data.Text.pack name))
+                pr = transform $ fromNP @c (cpure_NP (Proxy @FromJSON) (Star parseJSON))
                 Star parser = fromNP <$> sequence_NP (liftA2_NP mapKSS (toNP @c demoteKeys) (toNP pr))
              in withObject "someobj" $ \o -> fromRecord <$> parser o
     :}
@@ -171,9 +175,9 @@ 
 {- $json2
  
-   We have to use 'getFieldSubset' because the aliases are listed in a
-   different order than the record fields, and that might result in different
-   type-level trees. If the orders were the same, we wouldn't need it. 
+    The aliases are passed as a 'Record' with values wrapped in the 'K'
+    functor. This means that there aren't really any values of the type that
+    corresponds to each field, only the `String` annotations.
 
 >>> :{
     let parseWithAliases
@@ -186,12 +190,16 @@               -> Data.Aeson.Value 
               -> Parser r
         parseWithAliases aliases = 
-            let pr = fromNP @c (cpure_NP (Proxy @FromJSON) (Star parseJSON))
-                mapKSS (K name) (Star pf) = Star (\o -> explicitParseField pf o (Data.Text.pack name))
+            let mapKSS (K name) (Star pf) = Star (\o -> explicitParseField pf o (Data.Text.pack name))
+                pr = fromNP @c (cpure_NP (Proxy @FromJSON) (Star parseJSON))
                 Star parser = fromNP <$> sequence_NP (liftA2_NP mapKSS (toNP @c aliases) (toNP pr))
              in withObject "someobj" $ \o -> fromRecord <$> parser o
     :}
 
+   We have to use 'getFieldSubset' because the aliases are listed in a
+   different order than the record fields, and that might result in different
+   type-level trees. If the orders were the same, we wouldn't need it. 
+
 >>> data Person = Person { name :: String, age :: Int } deriving (Generic, Show)
 >>> instance ToRecord Person 
 >>> instance FromRecord Person 
@@ -221,8 +229,8 @@               => Data.Aeson.Value 
               -> Parser (Record I subset)
         parseFieldSubset = 
-            let pNP = cpure_NP (Proxy @FromJSON) (Star parseJSON)
-                mapKSS (K name) (Star pf) = Star (\o -> explicitParseField pf o (Data.Text.pack name))
+            let mapKSS (K name) (Star pf) = Star (\o -> explicitParseField pf o (Data.Text.pack name))
+                pNP = cpure_NP (Proxy @FromJSON) (Star parseJSON)
                 objpNP = liftA2_NP mapKSS (toNP @whole demoteKeys) pNP
                 subNP = toNP @subset $ getFieldSubset @subset $ fromNP @whole objpNP
                 Star subparser = fromNP @subset <$> sequence_NP subNP
@@ -242,3 +250,50 @@ Person {name = "Mark", age = 70, whatever = True}
 
 -}
+
+
+{- $json3
+ 
+    To ensure that we don't forget any branch when parsing a sum type from JSON, 
+    we can create a n-ary product of parsers, one for each branch.
+
+    Then we create a n-ary product of injections. Each component of the
+    product creates a n-ary sum out of the value of the corresponding branch.
+
+    We combine the n-ary product of parsers with the n-ary product of
+    injections, and collapse all the resulting parsers with
+    'Control.Applicative.asum'.
+
+    Then we convert the n-ary sum value that "wins" into a 'Variant' and
+    finally back into the original type.
+
+>>> :{
+    let parseAll
+              :: forall r c flat. (Generic r, 
+                                   FromVariant r, 
+                                   VariantCode r ~ c, 
+                                   KeysValuesAll KnownKey c, 
+                                   Productlike '[] c flat, 
+                                   Sumlike '[] c flat, 
+                                   All FromJSON flat) 
+              => Data.Aeson.Value 
+              -> Parser r
+        parseAll = 
+            let mapKSS (K name) (Star pf) = Star (\o -> explicitParseField pf o (Data.Text.pack name))
+                pnp = liftA2_NP mapKSS (toNP @c demoteKeys) (cpure_NP (Proxy @FromJSON) (Star parseJSON))
+                injected = liftA2_NP (\f star -> K (unK . apFn f . I <$> star)) (injections @flat) pnp 
+                Star parser = asum $ collapse_NP injected
+             in withObject "someobj" (\o -> fromVariant @r . fromNS <$> parser o)
+    :}
+
+>>> data ThisOrThat = This String | That Int deriving (Generic, Show)
+>>> instance FromVariant ThisOrThat
+>>> :{ 
+    let Just v = Data.Aeson.decode @Data.Aeson.Value (fromString "{ \"That\" : 70 }")
+        Just s = parseMaybe (parseAll @ThisOrThat) v
+     in s
+    :}
+That 70
+
+-}
+
lib/Data/RBR/Internal.hs view
@@ -180,7 +180,8 @@ 
 {- | Like 'prettyShowVariant' but specialized to pure variants.
 -}
-prettyShowVariantI :: forall t flat. (KeysValuesAll KnownKey t,Productlike '[] t flat, Sumlike '[] t flat, All Show flat, SListI flat) => Variant I t -> String
+prettyShowVariantI :: forall t flat. (KeysValuesAll KnownKey t,Productlike '[] t flat, Sumlike '[] t flat, All Show flat, SListI flat) 
+                   => Variant I t -> String
 prettyShowVariantI v = prettyShowVariant (show . unI) v 
 
 --
@@ -457,41 +458,44 @@     field :: Record f t -> (f (Value k t) -> Record f t, f (Value k t))
     branch :: (Variant f t -> Maybe (f (Value k t)), f (Value k t) -> Variant f t)
 
-class KeyHelper (ordering :: Ordering) (k :: Symbol) (t :: RBT Symbol Type) where 
-    type Value' ordering k t :: Type
-    field' :: Record f t -> (f (Value' ordering k t) -> Record f t, f (Value' ordering k t))
-    branch' :: (Variant f t -> Maybe (f (Value' ordering k t)), f (Value' ordering k t) -> Variant f t)
+class KeyHelper (ordering :: Ordering) (k :: Symbol) (left :: RBT Symbol Type) (v :: Type) (right :: RBT Symbol Type) where 
+    type Value' ordering k left v right :: Type
+    field' :: Record f (N colorx left kx v right) -> (f (Value' ordering k left v right) -> Record f (N colorx left kx v right), 
+                                                      f (Value' ordering k left v right))
+    branch' :: (Variant f (N colorx left kx v right) -> Maybe (f (Value' ordering k left v right)), 
+                f (Value' ordering k left v right) -> Variant f (N colorx left kx v right))
 
-instance (CmpSymbol k' k ~ ordering, KeyHelper ordering k (N color left k' v' right)) 
-         => Key k (N color left k' v' right) where
-    type Value k (N color left k' v' right) = Value' (CmpSymbol k' k) k (N color left k' v' right)
-    field = field' @ordering @k
-    branch = branch' @ordering @k
+instance (CmpSymbol k' k ~ ordering, KeyHelper ordering k left v' right) => Key k (N color left k' v' right) where
+    type Value k (N color left k' v' right) = Value' (CmpSymbol k' k) k left v' right
+    field = field' @ordering @k @left @v' @right
+    branch = branch' @ordering @k @left @v' @right
 
-instance Key k right => KeyHelper LT k (N color left k' v' right) where
-    type Value' LT k (N color left k' v' right) = Value k right
+instance (CmpSymbol k2 k ~ ordering, KeyHelper ordering k left2 v2 right2) 
+      => KeyHelper LT k left v (N color2 left2 k2 v2 right2) where
+    type Value' LT k left v (N color2 left2 k2 v2 right2) = Value' (CmpSymbol k2 k) k left2 v2 right2
     field' (Node left fv right) = 
-        let (setter,x) = field @k right
+        let (setter,x) = field' @ordering @k @left2 @v2 @right2 right
          in (\z -> Node left fv (setter z),x)
     branch' = 
-        let (match,inj) = branch @k @right
+        let (match,inj) = branch' @ordering @k @left2 @v2 @right2 
          in (\case LookRight x -> match x
                    _ -> Nothing,
              \fv -> LookRight (inj fv))
 
-instance Key k left => KeyHelper GT k (N color left k' v' right) where
-    type Value' GT k (N color left k' v' right) = Value k left
+instance (CmpSymbol k2 k ~ ordering, KeyHelper ordering k left2 v2 right2) 
+      => KeyHelper GT k (N color2 left2 k2 v2 right2) v' right where
+    type Value' GT k (N color2 left2 k2 v2 right2) v' right = Value' (CmpSymbol k2 k) k left2 v2 right2
     field' (Node left fv right) = 
-        let (setter,x) = field @k left
+        let (setter,x) = field' @ordering @k @left2 @v2 @right2 left
          in (\z -> Node (setter z) fv right,x)
     branch' =
-        let (match,inj) = branch @k @left
+        let (match,inj) = branch' @ordering @k @left2 @v2 @right2 
          in (\case LookLeft x -> match x
                    _ -> Nothing,
              \fv -> LookLeft (inj fv))
 
-instance KeyHelper EQ k (N color left k v right) where
-    type Value' EQ k (N color left k v right) = v
+instance KeyHelper EQ k left v right where
+    type Value' EQ k left v right = v
     field' (Node left fv right) = (\x -> Node left x right, fv)
     branch' = (\case Here x -> Just x
                      _ -> Nothing,
red-black-record.cabal view
@@ -1,6 +1,6 @@ cabal-version:       2.0
 name:                red-black-record
-version:             1.0.0.1
+version:             1.0.0.2
 synopsis:            Extensible records and variants indexed by a type-level Red-Black tree.
 
 description:         A library that provides extensible records and variants,