red-black-record 2.1.0.1 → 2.1.0.2
raw patch · 6 files changed
+321/−204 lines, 6 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- README.md +194/−194
- lib/Data/RBR.hs +26/−6
- lib/Data/RBR/Examples.hs +12/−0
- lib/Data/RBR/Internal.hs +86/−2
- red-black-record.cabal +2/−2
- tests/doctests.hs +1/−0
README.md view
@@ -1,194 +1,194 @@-# red-black-record - -## 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) -tree that maps `Symbol` keys to value types of any kind. The keys correspond to -field names in records, and to branch names in variants. Many record functions -have their variant mirror-images and viceversa. - -At the term level, value types come wrapped in a type constructor of kind `q -> -Type`, where `q` is the kind of value types. Typically, the type constructor -will be an [identity -functor](http://hackage.haskell.org/package/sop-core-0.4.0.0/docs/Data-SOP.html#t:I), -but it can also be `Maybe` or some other `Applicative` for parsing, validation -and so on. - -If we forget about the keys and only keep the values, records are isomorphic to -[n-ary unlabeled -products](http://hackage.haskell.org/package/sop-core-0.4.0.0/docs/Data-SOP.html#t:NP), -and variants are isomorphic to [n-ary unlabeled -sums](http://hackage.haskell.org/package/sop-core-0.4.0.0/docs/Data-SOP.html#t:NS). -The [sop-core](http://hackage.haskell.org/package/sop-core) library provides -such unlabeled types, along with a rich API for manipulating them. Instead of -reinventing the wheel, red-black-record defines conversion functions to -facilitate working in the "unlabeled" world and then coming back to records and -variants. - -There is another world towards which bridges must be built: the everyday -Haskell world of conventional records and sums. In fact, one of the motivations -of extensible records and variants is to serve as "generalized" versions of -vanilla data types. Advanced use cases can rely on these generalized versions, -thereby avoiding intrusive changes to the original types. red-black-record -provides conversion typeclasses with default implementations by way of -[`GHC.Generic`](http://hackage.haskell.org/package/base-4.12.0.0/docs/GHC-Generics.html). - -For examples on how to use the library, check the haddocks for the -`Data.RBR.Examples` module. - -## FAQ - -### What extensions do I need to use this library? - -* `DataKinds`. - -* `TypeApplications` to be able to specify field and branch names. - -* `TypeFamilies`. - -* `FlexibleContexts`. - -* `DeriveGeneric` for interfacing with normal records. - -* `PartialTypeSignatures` for hiding complex tree types. - -### My type signatures are getting big and scary because of those type-level trees. What to do? - -The -[`-XPartialTypeSignatures`](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html?#extension-PartialTypeSignatures) -extension can help with that, in combination with the -[-Wno-partial-type-signatures](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/using-warnings.html#ghc-flag--Wpartial-type-signatures) -GHC flag that disables the warning message emitted when the underscore is -encountered in a signature. - -The flag can be set globally in the -[ghc-options](https://www.haskell.org/cabal/users-guide/developing-packages.html?#pkg-field-ghc-options) -section of the .cabal file, and also for particular modules with the -[OPTIONS_GHC](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html?highlight=options_ghc#options-ghc-pragma) -file-header pragma. - -### The `Show` instance for record doesn't show any field names. - -The field names exist only at the type level. Also, the `Show` instance uses -n-ary products and sums from -[sop-core](http://hackage.haskell.org/package/sop-core), which do not have -field labels. - -For fancier output, use the "pretty-show" functions instead. - -### Working with two records, I'm getting errors about incompatible types even as both records have the exact same fields. - -Alas, the order of insertion in the type-level tree matters :( Different -insertion orders can produce structurally different trees, even as they encode -the same symbol-to-type map. - -As a workaround, one can use the `-Subset` functions to convert between -equivalent structures. - -### I can't insert into a record when a field with the same name but different type already exists. Why not simply overwrite it? - -That limitation was intentional, because allowing it would make impossible to -implement of `widen` for `Variant`. One solution is to explicitly delete the -field and then insert it again. - -### The library doesn't use Proxy and relies on type application instead. But what’s the order of the type parameters? - -For standalone functions, it’s the order in which the type variables appear in -the `forall`. - -### What's the deal with all those -I suffixed versions of functions? - -This library aims to provide -[HKD](http://reasonablypolymorphic.com/blog/higher-kinded-data/)-like -functionality by wrapping all the fields of a record in a type constructor. - -But sometimes we are working with "pure" records without effects, and we just -want to get and set a field's value. In that case, the type constructor that -wraps each field will be an identity functor `I` (from -[sop-core](http://hackage.haskell.org/package/sop-core)). The -I suffixed -functions wrap and unwrap the field's value on behalf of the user. - -### What's the deal with all those -Subset suffixed versions of functions? - -These functions target multiple fields or branches at the same time. They can -be used to build lawful lenses and prisms over fragmenst of a structure. - -They can also be used to convert between type-level trees that have the same -entries but different structure. - -### What about compilation times? - -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 about 22 -seconds to compile in my machine. - -Code involving deletion of fields and branches (like using the `winnow` -function for `Variant`s) is currently poorly optimized and will compile -[slower](https://github.com/danidiaz/red-black-record/issues/12) than that. - -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 Stefan Kahrs's code - [available - here](https://www.cs.kent.ac.uk/people/staff/smk/redblack/rb.html). See also - [this post](https://www.cs.kent.ac.uk/people/staff/smk/redblack/rb.html). - -* Besides depending on sop-core, I have copied and adapted code from it. In - particular the `KeysValuessAll` typeclass is a version of the `All` typeclass - from sop-core. - -* [Surgery for data - 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 - -* [generics-sop](http://hackage.haskell.org/package/generics-sop) and - [records-sop](http://hackage.haskell.org/package/records-sop). Like - red-black-record, both of these libraries build upon sop-core. They are in - fact written by the same author of sop-core. generics-sop can provide - sum-of-products representations of any datatype with a Generic instance - (red-black-record is more limited, it only converts types that fit the named - record or variant mold—so no types with anonymous fields for example). - - If you don't need to explicitly target *individual* fields in the generic - representation, you'll be better off using generics-sop instead of - red-black-record. - - On top of generics-sop, records-sop provides named field accessors and record - subtyping based on a type-level list of fields (unlike the type-level tree - used by red-black-record). It doesn't seem to provide variants. - -* [superrecord](http://hackage.haskell.org/package/superrecord). This library - provides very efficient field access at runtime because the fields are backed - internally by an array. Uses a *sorted* type-level list of fields, to avoid - the problems of multiple orderings of the same fields. - -* [vinyl](http://hackage.haskell.org/package/vinyl). One of the oldest and more - fully-featured extensible records libraries. Uses a type level list of - fields. The fields' values are wrapped in a type constructor, like in - sop-core. The records seem to use an auxiliary sum type that serves as a - "code" for the fields. See also - [vinyl-genercics](https://hackage.haskell.org/package/vinyl-generics). - -* [HTree](https://github.com/i-am-tom/learn-me-a-haskell#htree). Another - implementation of extensible records using type-level red-black trees. - -* [megarecord](https://github.com/jvanbruegge/Megarecord). Seems to be a - proof-of-concept for a future [row polymorphism - extension](https://github.com/ghc-proposals/ghc-proposals/pull/180) for - Haskell. - -* [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). - +# red-black-record++## 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)+tree that maps `Symbol` keys to value types of any kind. The keys correspond to+field names in records, and to branch names in variants. Many record functions+have their variant mirror-images and viceversa.++At the term level, value types come wrapped in a type constructor of kind `q ->+Type`, where `q` is the kind of value types. Typically, the type constructor+will be an [identity+functor](http://hackage.haskell.org/package/sop-core-0.4.0.0/docs/Data-SOP.html#t:I),+but it can also be `Maybe` or some other `Applicative` for parsing, validation+and so on.++If we forget about the keys and only keep the values, records are isomorphic to+[n-ary unlabeled+products](http://hackage.haskell.org/package/sop-core-0.4.0.0/docs/Data-SOP.html#t:NP),+and variants are isomorphic to [n-ary unlabeled+sums](http://hackage.haskell.org/package/sop-core-0.4.0.0/docs/Data-SOP.html#t:NS).+The [sop-core](http://hackage.haskell.org/package/sop-core) library provides+such unlabeled types, along with a rich API for manipulating them. Instead of+reinventing the wheel, red-black-record defines conversion functions to+facilitate working in the "unlabeled" world and then coming back to records and+variants.++There is another world towards which bridges must be built: the everyday+Haskell world of conventional records and sums. In fact, one of the motivations+of extensible records and variants is to serve as "generalized" versions of+vanilla data types. Advanced use cases can rely on these generalized versions,+thereby avoiding intrusive changes to the original types. red-black-record+provides conversion typeclasses with default implementations by way of+[`GHC.Generic`](http://hackage.haskell.org/package/base-4.12.0.0/docs/GHC-Generics.html).++For examples on how to use the library, check the haddocks for the+`Data.RBR.Examples` module.++## FAQ++### What extensions do I need to use this library?++* `DataKinds`.++* `TypeApplications` to be able to specify field and branch names.++* `TypeFamilies`.++* `FlexibleContexts`.++* `DeriveGeneric` for interfacing with normal records.++* `PartialTypeSignatures` for hiding complex tree types.++### My type signatures are getting big and scary because of those type-level trees. What to do?++The+[`-XPartialTypeSignatures`](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html?#extension-PartialTypeSignatures)+extension can help with that, in combination with the+[-Wno-partial-type-signatures](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/using-warnings.html#ghc-flag--Wpartial-type-signatures)+GHC flag that disables the warning message emitted when the underscore is+encountered in a signature.++The flag can be set globally in the+[ghc-options](https://www.haskell.org/cabal/users-guide/developing-packages.html?#pkg-field-ghc-options)+section of the .cabal file, and also for particular modules with the+[OPTIONS_GHC](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html?highlight=options_ghc#options-ghc-pragma)+file-header pragma.++### The `Show` instance for record doesn't show any field names.++The field names exist only at the type level. Also, the `Show` instance uses+n-ary products and sums from+[sop-core](http://hackage.haskell.org/package/sop-core), which do not have+field labels.++For fancier output, use the "pretty-show" functions instead.++### Working with two records, I'm getting errors about incompatible types even as both records have the exact same fields.++Alas, the order of insertion in the type-level tree matters :( Different+insertion orders can produce structurally different trees, even as they encode+the same symbol-to-type map.++As a workaround, one can use the `-Subset` functions to convert between+equivalent structures.++### I can't insert into a record when a field with the same name but different type already exists. Why not simply overwrite it?++That limitation was intentional, because allowing it would make impossible to+implement of `widen` for `Variant`. One solution is to explicitly delete the+field and then insert it again.++### The library doesn't use Proxy and relies on type application instead. But what’s the order of the type parameters?++For standalone functions, it’s the order in which the type variables appear in+the `forall`.++### What's the deal with all those -I suffixed versions of functions?++This library aims to provide+[HKD](http://reasonablypolymorphic.com/blog/higher-kinded-data/)-like+functionality by wrapping all the fields of a record in a type constructor.++But sometimes we are working with "pure" records without effects, and we just+want to get and set a field's value. In that case, the type constructor that+wraps each field will be an identity functor `I` (from+[sop-core](http://hackage.haskell.org/package/sop-core)). The -I suffixed+functions wrap and unwrap the field's value on behalf of the user.++### What's the deal with all those -Subset suffixed versions of functions?++These functions target multiple fields or branches at the same time. They can+be used to build lawful lenses and prisms over fragmenst of a structure.++They can also be used to convert between type-level trees that have the same+entries but different structure.++### What about compilation times?++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 about 22+seconds to compile in my machine. ++Code involving deletion of fields and branches (like using the `winnow`+function for `Variant`s) is currently poorly optimized and will compile+[slower](https://github.com/danidiaz/red-black-record/issues/12) than that.++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 Stefan Kahrs's code+ [available+ here](https://www.cs.kent.ac.uk/people/staff/smk/redblack/rb.html). See also+ [this post](https://www.cs.kent.ac.uk/people/staff/smk/redblack/rb.html).++* Besides depending on sop-core, I have copied and adapted code from it. In+ particular the `KeysValuessAll` typeclass is a version of the `All` typeclass+ from sop-core. ++* [Surgery for data+ 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++* [generics-sop](http://hackage.haskell.org/package/generics-sop) and+ [records-sop](http://hackage.haskell.org/package/records-sop). Like+ red-black-record, both of these libraries build upon sop-core. They are in+ fact written by the same author of sop-core. generics-sop can provide+ sum-of-products representations of any datatype with a Generic instance+ (red-black-record is more limited, it only converts types that fit the named+ record or variant mold—so no types with anonymous fields for example). + + If you don't need to explicitly target *individual* fields in the generic+ representation, you'll be better off using generics-sop instead of+ red-black-record. + + On top of generics-sop, records-sop provides named field accessors and record+ subtyping based on a type-level list of fields (unlike the type-level tree+ used by red-black-record). It doesn't seem to provide variants.++* [superrecord](http://hackage.haskell.org/package/superrecord). This library+ provides very efficient field access at runtime because the fields are backed+ internally by an array. Uses a *sorted* type-level list of fields, to avoid+ the problems of multiple orderings of the same fields.++* [vinyl](http://hackage.haskell.org/package/vinyl). One of the oldest and more+ fully-featured extensible records libraries. Uses a type level list of+ fields. The fields' values are wrapped in a type constructor, like in+ sop-core. The records seem to use an auxiliary sum type that serves as a+ "code" for the fields. See also+ [vinyl-genercics](https://hackage.haskell.org/package/vinyl-generics).++* [HTree](https://github.com/i-am-tom/learn-me-a-haskell#htree). Another+ implementation of extensible records using type-level red-black trees.++* [megarecord](https://github.com/jvanbruegge/Megarecord). Seems to be a+ proof-of-concept for a future [row polymorphism+ extension](https://github.com/ghc-proposals/ghc-proposals/pull/180) for+ Haskell.++* [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).+
lib/Data/RBR.hs view
@@ -1,5 +1,25 @@+{-| + This module provides extensible 'Record' and 'Variant' types, which are + indexed by a type-level 'Map'. + + Many functions in this module require the use of @TypeApplications@ to + avoid ambiguity. The order of the applications is the order of the type + variables in the function signature's @forall@. The first type variable is + usually the field/branch name and it's always required. The other type + variables can often be inferred. + + Meaning of commonly used type and kind variables: + + - @t@: A type-level 'Map', usually of kind @Map Symbol q@. + - @k@: A key of kind 'Symbol' in a type-level 'Map'. + - @v@: A type value of kind @q@ in a type-level 'Map'. + - @q@: The kind of the type value @v@. + - @f@: A type constructor of kind @q -> Type@ that wraps the type @v@. + - @flat@: A type-level list of kind @[q]@ whose elements correspond to values in a type-level 'Map'. + +-} module Data.RBR ( - -- * Type-level Red-Black tree + -- * Type-level map -- $typelevel Map, Empty, @@ -80,13 +100,13 @@ Productlike (..), prefixNP, breakNP, - fromNP, toNP, + fromNP, Sumlike (..), prefixNS, breakNS, - fromNS, toNS, + fromNS, -- * Data.SOP re-exports I(..), K(..), @@ -109,10 +129,10 @@ {- $typelevel - A Red-Black tree that is used at the type level, with @DataKinds@. The tree - keeps track of what keys are present and to what types they correspond. - + A type-level map that keeps track of which keys are present, and to which + types they correspond. + Implemented as a red-black tree, and used as a kind by means of @DataKinds@. -} {- $nominal
lib/Data/RBR/Examples.hs view
@@ -31,6 +31,9 @@ -- * Ensuring all branches of a sum type are parsed from JSON -- $json4sum + + -- * External examples + -- $externalexamples ) where import Data.RBR @@ -331,6 +334,15 @@ in s :} That 70 + +-} + +{- $externalexamples + + * [Is there a canonical way of comparing/changing one/two records in haskell? (SO)](https://stackoverflow.com/a/57574731/1364288) + * [Help with Generics. (Reddit)](https://www.reddit.com/r/haskell/comments/cteemj/help_with_generics/expyjfk) + * [Adventures assembling records of capabilities. (Discourse)](https://discourse.haskell.org/t/adventures-assembling-records-of-capabilities/623) + * [Resources on sop-core and generics-sop. (GitHub)](https://github.com/well-typed/generics-sop/issues/47) -}
lib/Data/RBR/Internal.hs view
@@ -45,6 +45,18 @@ import Data.SOP.NP (collapse_NP,liftA_NP,liftA2_NP,cliftA_NP,cliftA2_NP,pure_NP) import Data.SOP.NS (collapse_NS,ap_NS,injections,Injection) + +{- $setup + +>>> :set -XDataKinds -XTypeApplications -XPartialTypeSignatures -XFlexibleContexts -XTypeFamilies -XDeriveGeneric +>>> :set -Wno-partial-type-signatures +>>> import Data.RBR +>>> import Data.SOP +>>> import GHC.Generics + +-} + + -- | The color of a node. data Color = R | B @@ -111,6 +123,10 @@ The names are represented by a constant functor 'K' carrying an annotation of type 'String'. This means that there aren't actually any values of the type that corresponds to each field, only the 'String' annotations. + +>>> putStrLn $ prettyShowRecord show $ demoteKeys @(Insert "foo" Char (Insert "bar" Bool Empty)) +{bar = K "bar", foo = K "foo"} + -} demoteKeys :: forall t. KeysValuesAll KnownKey t => Record (K String) t demoteKeys = cpara_Map (Proxy @KnownKey) unit go @@ -133,6 +149,9 @@ Create a record containing the names of each field along with a term-level representation of each type. +>>> putStrLn $ prettyShowRecord show $ demoteEntries @(Insert "foo" Char (Insert "bar" Bool Empty)) +{bar = K ("bar",Bool), foo = K ("foo",Char)} + See also 'collapse_Record' for getting the entries as a list. -} demoteEntries :: forall t. KeysValuesAll KnownKeyTypeableValue t => Record (K (String,TypeRep)) t @@ -175,6 +194,12 @@ {- | Collapse a 'Record' composed of 'K' annotations. +>>> collapse_Record unit +[] + +>>> collapse_Record (insert @"bar" (K False) unit) +[False] + The naming scheme follows that of 'Data.SOP.NP.collapse_NP'. -} @@ -265,6 +290,13 @@ {- | Adds a new field to a 'Record'. + +>>> project @"foo" (insert @"foo" (I 'a') unit) +I 'a' + +>>> project @"foo" (insert @"foo" @Char Nothing unit) +Nothing + -} insert :: forall k v t f. Insertable k v t => f v -> Record f t -> Record f (Insert k v t) insert = _insert @_ @k @v @t @f @@ -282,6 +314,10 @@ addField = insert @k @v @t @f {- | Like 'insert' but specialized to pure 'Record's. + +>>> projectI @"foo" (insertI @"foo" 'a' unit) +'a' + -} insertI :: forall k v t . Insertable k v t => v -> Record I t -> Record I (Insert k v t) insertI = insert @k @v @t . I @@ -652,6 +688,8 @@ Here) {- | Get the value of a field for a 'Record'. + + -} project :: forall k t f . Key k t => Record f t -> f (Value k t) project = snd . field @k @t @@ -672,6 +710,10 @@ modifyField f r = uncurry ($) (fmap f (field @k @t @f r)) {- | Put a value into the branch of a 'Variant'. + +>>> match @"foo" (inject @"foo" (I 'a') :: Variant I (Insert "foo" Char Empty)) +Just (I 'a') + -} inject :: forall k t f. Key k t => f (Value k t) -> Variant f t inject = snd (branch @k @t) @@ -682,6 +724,10 @@ match = fst (branch @k @t) {- | Like 'project' but specialized to pure 'Record's. + +>>> projectI @"foo" (insertI @"foo" 'a' (insertI @"bar" False unit)) +'a' + -} projectI :: forall k t . Key k t => Record I t -> Value k t projectI = unI . snd . field @k @t @@ -702,6 +748,10 @@ modifyFieldI f = modifyField @k @t (I . f . unI) {- | Like 'inject' but specialized to pure 'Variant's. + +>>> matchI @"foo" (injectI @"foo" 'a' :: Variant I (Insert "foo" Char Empty)) +Just 'a' + -} injectI :: forall k t. Key k t => Value k t -> Variant I t injectI = snd (branch @k @t) . I @@ -713,6 +763,10 @@ {- | Process a 'Variant' using a eliminator 'Record' that carries handlers for each possible branch of the 'Variant'. + +>>> eliminate (addCaseI @"foo" @Int succ (addCaseI @"bar" pred unit)) (injectI @"bar" 33) +32 + -} eliminate :: (Productlike '[] t result, Sumlike '[] t result, SListI result) => Record (Case f r) t -> Variant f t -> r eliminate cases variant = @@ -754,6 +808,7 @@ SListI flat) {- | Like 'field', but targets multiple fields at the same time + -} fieldSubset :: forall subset whole flat f. (ProductlikeSubset subset whole flat) => Record f whole -> (Record f subset -> Record f whole, Record f subset) @@ -777,7 +832,12 @@ in cpara_Map (Proxy @(PresentIn whole)) unit goget) {- | Like 'project', but extracts multiple fields at the same time. + + The types in the subset tree can often be inferred and left as wildcards in type signature. +>>> prettyShowRecordI $ projectSubset @(Insert "foo" _ (Insert "bar" _ Empty)) (insertI @"foo" 'a' (insertI @"bar" True (insertI @"baz" (Just ()) unit))) +"{bar = True, foo = 'a'}" + Can also be used to convert between 'Record's with structurally dissimilar type-level maps that nevertheless hold the same entries. -} @@ -916,12 +976,21 @@ breakNP :: forall start t result f. Productlike start t result => NP f result -> (Record f t, NP f start) breakNP = _breakNP @_ @start @t @result -{- | Convert a 'Record' into a n-ary product. +{- | Convert a 'Record' into a n-ary product. The order of the elements in the + product is not the order of insertion in the record. + +>>> toNP (insertI @"foo" 'a' (insertI @"bar" True unit)) +I True :* I 'a' :* Nil + -} toNP :: forall t result f. Productlike '[] t result => Record f t -> NP f result toNP r = prefixNP r Nil -{- | Convert a n-ary product into a compatible 'Record'. +{- | Convert a n-ary product into a compatible 'Record'. Usually follows an invocation of 'toNP'. + +>>> prettyShowRecordI . fromNP @(Insert "foo" _ (Insert "bar" _ Empty)) . toNP $ insertI @"foo" 'a' (insertI @"bar" True unit) +"{bar = True, foo = 'a'}" + -} fromNP :: forall t result f. Productlike '[] t result => NP f result -> Record f t fromNP np = let (r,Nil) = breakNP np in r @@ -1011,11 +1080,19 @@ breakNS = _breakNS @_ @start @t @result {- | Convert a 'Variant' into a n-ary sum. + +>>> toNS (injectI @"foo" 'a' :: Variant I (Insert "foo" Char (Insert "bar" Bool Empty))) +S (Z (I 'a')) + -} toNS :: forall t result f. Sumlike '[] t result => Variant f t -> NS f result toNS = prefixNS . Right {- | Convert a n-ary sum into a compatible 'Variant'. + +>>> prettyShowVariantI $ fromNS @(Insert "foo" _ (Insert "bar" _ Empty)) . toNS $ (injectI @"foo" 'a' :: Variant I (Insert "foo" Char (Insert "bar" Bool Empty))) +"foo ('a')" + -} fromNS :: forall t result f. Sumlike '[] t result => NS f result -> Variant f t fromNS ns = case breakNS ns of @@ -1722,6 +1799,13 @@ winnow = _winnow @_ @k @v @t {- | Like 'winnow' but specialized to pure 'Variant's. + +>>> winnow @"bar" @Bool (injectI @"bar" False :: Variant I (Insert "foo" Char (Insert "bar" Bool Empty))) +Right (I False) + +>>> prettyShowVariantI `first` winnow @"foo" @Char (injectI @"bar" False :: Variant I (Insert "foo" Char (Insert "bar" Bool Empty))) +Left "bar (False)" + -} winnowI :: forall k v t . Deletable k v t => Variant I t -> Either (Variant I (Delete k v t)) v winnowI = fmap unI . winnow @k @v @t
red-black-record.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.0 name: red-black-record -version: 2.1.0.1 +version: 2.1.0.2 synopsis: Extensible records and variants indexed by a type-level Red-Black tree. description: A library that provides extensible records and variants, @@ -11,7 +11,7 @@ names in records, and to branch names in variants. . At the term level, value types come wrapped in a type - constructor of kind `q -> Type`, where `q` is the kind of + constructor of kind @q -> Type@, where @q@ is the kind of value types. . The records and variants can be converted to and from
tests/doctests.hs view
@@ -2,5 +2,6 @@ main = doctest ["-ilib", "lib/Data/RBR.hs", + "lib/Data/RBR/Internal.hs", "lib/Data/RBR/Examples.hs" ]