packages feed

large-generics (empty) → 0.1.0.0

raw patch · 25 files changed

+2984/−0 lines, 25 filesdep +QuickCheckdep +aesondep +base

Dependencies added: QuickCheck, aeson, base, generic-deriving, generics-sop, large-generics, microlens, mtl, sop-core, tasty, tasty-hunit, tasty-quickcheck, vector

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for large-generics++## 0.1.0.0 -- 2022-03-23++* First public release
+ large-generics.cabal view
@@ -0,0 +1,87 @@+cabal-version:      2.4+name:               large-generics+version:            0.1.0.0+synopsis:           Generic programming API for large-records and large-anon+description:        The large-generics package offers a style of generic+                    programming inspired by generics-sop, but optimized for+                    compilation time. For more information, see the blog posts+                    "Avoiding quadratic core code size with large records"+                    <https://well-typed.com/blog/2021/08/large-records/>.+bug-reports:        https://github.com/well-typed/large-records/issues+license:            BSD-3-Clause+author:             Edsko de Vries+maintainer:         edsko@well-typed.com+category:           Generics+extra-source-files: CHANGELOG.md+tested-with:        GHC ==8.8.4 || ==8.10.7 || ==9.0.2 || ==9.2.2++library+  exposed-modules:+      -- General infrastructure+      Data.Record.Generic+      Data.Record.Generic.Rep+      Data.Record.Generic.Rep.Internal+      Data.Record.Generic.Transform++      -- Interop with other generics approaches+      Data.Record.Generic.GHC+      Data.Record.Generic.SOP++      -- Specific generic functions+      Data.Record.Generic.Eq+      Data.Record.Generic.JSON+      Data.Record.Generic.Lens.VL+      Data.Record.Generic.LowerBound+      Data.Record.Generic.Show+  default-language:+      Haskell2010+  ghc-options:+      -Wall+      -Wredundant-constraints+  hs-source-dirs:+      src+  build-depends:+      base         >= 4.13  && < 4.17+    , aeson        >= 1.4.4 && < 2.1+    , generics-sop >= 0.5   && < 0.6+    , sop-core     >= 0.5   && < 0.6+    , vector       >= 0.12  && < 0.13++test-suite test-large-generics+  type:+      exitcode-stdio-1.0+  main-is:+      TestLargeGenerics.hs+  other-modules:+      Test.Record.Generic.Infra.Examples+      Test.Record.Generic.Infra.Beam.Interpretation+      Test.Record.Generic.Infra.Beam.Mini+      Test.Record.Generic.Infra.Util+      Test.Record.Generic.Prop.Show+      Test.Record.Generic.Prop.ToFromJSON+      Test.Record.Generic.Sanity.GhcGenerics+      Test.Record.Generic.Sanity.Laziness+      Test.Record.Generic.Sanity.Lens.VL+      Test.Record.Generic.Sanity.Rep+      Test.Record.Generic.Sanity.Transform+  default-language:+      Haskell2010+  ghc-options:+      -Wall+      -Wredundant-constraints+  hs-source-dirs:+      test+  build-depends:+      base+    , large-generics++    , aeson+    , generic-deriving+    , generics-sop+    , microlens+    , mtl+    , QuickCheck+    , sop-core+    , tasty+    , tasty-hunit+    , tasty-quickcheck
+ src/Data/Record/Generic.hs view
@@ -0,0 +1,121 @@+{-# LANGUAGE DataKinds            #-}+{-# LANGUAGE ExplicitNamespaces   #-}+{-# LANGUAGE FlexibleContexts     #-}+{-# LANGUAGE FlexibleInstances    #-}+{-# LANGUAGE GADTs                #-}+{-# LANGUAGE KindSignatures       #-}+{-# LANGUAGE TypeFamilies         #-}+{-# LANGUAGE UndecidableInstances #-}++module Data.Record.Generic (+    -- * Types with a generic view+    Generic(..)+  , Rep(..) -- TODO: Make opaque?+    -- * Metadata+  , Metadata(..)+  , FieldStrictness(..)+  , recordFieldNames+  , FieldMetadata(..)+    -- * Working with type-level metadata+  , FieldName+  , FieldType+  , IsField+    -- * Re-exports+  , module SOP+  , Proxy(..)+  ) where++import Data.Kind+import Data.Proxy+import GHC.TypeLits++-- To reduce overlap between the two libraries and improve interoperability,+-- we import as much from sop-core as possible.+import Data.SOP.BasicFunctors as SOP+import Data.SOP.Classes       as SOP (type (-.->)(..))+import Data.SOP.Dict          as SOP (Dict(..))++import Data.Record.Generic.Rep.Internal (Rep(..))++import qualified Data.Record.Generic.Rep.Internal as Rep++{-------------------------------------------------------------------------------+  Generic type class+-------------------------------------------------------------------------------}++class Generic a where+  -- | @Constraints a c@ means "all fields of @a@ satisfy @c@"+  type Constraints a :: (Type -> Constraint) -> Constraint++  -- | Type-level metadata+  --+  -- NOTE: using type-level lists without resulting in quadratic core code is+  -- extremely difficult. Any use of this type-level metadata therefore needs+  -- delibrate consideration. Some examples:+  --+  -- o Within the @large-generics@ library, 'MetadataOf' is used in the+  --   definition of 'HasNormalForm'. This constraint is carefully defined to+  --   avoid quadratic code, as described in the presentation+  --   "Avoiding Quadratic Blow-up During Compilation"+  --   <https://skillsmatter.com/skillscasts/17262-avoiding-quadratic-blow-up-during-compilation>+  -- o The @large-records@ library uses it to provide a compatibility layer+  --   between it and @sop-core@; this is however only for testing purposes, and+  --   the quadratic code here is simply accepted.+  type MetadataOf a :: [(Symbol, Type)]++  -- | Translate to generic representation+  from :: a -> Rep I a++  -- | Translate from generic representation+  to :: Rep I a -> a++  -- | Construct vector of dictionaries, one for each field of the record+  dict :: Constraints a c => Proxy c -> Rep (Dict c) a++  -- | Metadata+  metadata :: proxy a -> Metadata a++{-------------------------------------------------------------------------------+  Metadata+-------------------------------------------------------------------------------}++data Metadata a = Metadata {+      recordName          :: String+    , recordConstructor   :: String+    , recordSize          :: Int+    , recordFieldMetadata :: Rep FieldMetadata a+    }++data FieldStrictness = FieldStrict | FieldLazy++data FieldMetadata x where+  FieldMetadata ::+       KnownSymbol name+    => Proxy name+    -> FieldStrictness+    -> FieldMetadata x++recordFieldNames :: Metadata a -> Rep (K String) a+recordFieldNames = Rep.map' aux . recordFieldMetadata+  where+    aux :: FieldMetadata x -> K String x+    aux (FieldMetadata p _) = K $ symbolVal p++{-------------------------------------------------------------------------------+  Working with the type-level metadata++  This is primarily designed for interop with SOP.+-------------------------------------------------------------------------------}++type family FieldName (field :: (Symbol, Type)) :: Symbol where+  FieldName '(name, _typ) = name++type family FieldType (field :: (Symbol, Type)) :: Type where+  FieldType '(_name, typ) = typ++class    ( field ~ '(FieldName field, FieldType field)+         , KnownSymbol (FieldName field)+         ) => IsField field+instance ( field ~ '(FieldName field, FieldType field)+         , KnownSymbol (FieldName field)+         ) => IsField field
+ src/Data/Record/Generic/Eq.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE TypeApplications #-}++{-# OPTIONS_GHC -Wwarn #-}++module Data.Record.Generic.Eq (+    geq+  , gcompare+  ) where++import Data.Record.Generic+import qualified Data.Record.Generic.Rep as Rep++-- | Generic equality function+--+-- Typical usage:+--+-- > instance Eq T where+-- >   (==) = geq+--+-- TODO: Should we worry about short-circuiting here?+geq :: (Generic a, Constraints a Eq) => a -> a -> Bool+geq = \x y ->+      and+    . Rep.collapse+    $ Rep.czipWith (Proxy @Eq) compareField (from x) (from y)+  where+    compareField :: Eq x => I x -> I x -> K Bool x+    compareField (I x) (I y) = K (x == y)++gcompare :: (Generic a, Constraints a Ord) => a -> a -> Ordering+gcompare = \x y ->+      mconcat+    . Rep.collapse+    $ Rep.czipWith (Proxy @Ord) compareField (from x) (from y)+  where+    compareField :: Ord x => I x -> I x -> K Ordering x+    compareField (I x) (I y) = K (compare x y)
+ src/Data/Record/Generic/GHC.hs view
@@ -0,0 +1,111 @@+{-# LANGUAGE DataKinds           #-}+{-# LANGUAGE GADTs               #-}+{-# LANGUAGE KindSignatures      #-}+{-# LANGUAGE RankNTypes          #-}+{-# LANGUAGE RecordWildCards     #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications    #-}+{-# LANGUAGE TypeOperators       #-}++-- | Interop with standard GHC generics+module Data.Record.Generic.GHC (+    -- * From GHC to LR generics+    ThroughLRGenerics(..)+    -- * GHC generics metadata+  , GhcMetadata(..)+  , GhcFieldMetadata(..)+  , ghcMetadata+  ) where++import Data.Kind+import Data.Proxy+import GHC.Generics hiding (Generic(..), Rep)+import GHC.TypeLits++import Data.Record.Generic++import qualified Data.Record.Generic.Rep as Rep++{-------------------------------------------------------------------------------+  From GHC to LR generics+-------------------------------------------------------------------------------}++-- | Route from GHC generics to LR generics+--+-- Suppose a function such as+--+-- > allEqualTo :: Eq a => a -> [a] -> Bool+-- > allEqualTo x = all (== x)+--+-- is instead written as+--+-- > allEqualTo :: (GHC.Generic a, GHC.GEq' (GHC.Rep a)) => a -> [a] -> Bool+-- > allEqualTo x = all (GHC.geqdefault x)+--+-- where instead of using an indirection through an auxiliary type class `Eq`,+-- it directly assumes @GHC.Generics@ and uses a concrete generic+-- implementation. Such design is arguably questionable, but for example+-- @beam-core@ contains many such deeply ingrained assumptions of the+-- availability of @GHC.Generics@.+--+-- In order to be able to call such a function on a large record @Foo@,+-- 'largeRecord' will generate an instance+--+-- > instance GHC.Generic Foo where+-- >   type Rep Foo = ThroughLRGenerics Foo+-- >+-- >   from = WrapThroughLRGenerics+-- >   to   = unwrapThroughLRGenerics+--+-- For our running example, this instance makes it possible to call 'allEqualTo'+-- provided we then provide an instance+--+-- > instance ( LR.Generic a+-- >          , LR.Constraints a Eq+-- >          ) => GHC.GEq' (ThroughLRGenerics a) where+-- >   geq' = LR.geq `on` unwrapThroughLRGenerics+--+-- Effectively, 'ThroughLRGenerics' can be used to redirect a function that uses+-- GHC generics to a function that uses LR generics.+newtype ThroughLRGenerics a p = WrapThroughLRGenerics {+      unwrapThroughLRGenerics :: a+    }++{-------------------------------------------------------------------------------+  GHC generics metadata+-------------------------------------------------------------------------------}++-- | GHC generics metadata+--+-- TODO: Currently we provide metadata only for the record fields, not the+-- constructor or type name+data GhcMetadata a = GhcMetadata {+      ghcMetadataFields :: Rep GhcFieldMetadata a+    }++data GhcFieldMetadata :: Type -> Type where+  GhcFieldMetadata :: forall (f :: Meta) (a :: Type).+       Selector f+    => Proxy f -> GhcFieldMetadata a++withFieldMetadata :: forall (s :: Symbol) (r :: Type).+     KnownSymbol s+  => Proxy s+  -> FieldStrictness+  -> (forall (f :: Meta). Selector f => Proxy f -> r)+  -> r+withFieldMetadata _ s k =+    case s of+      FieldLazy   -> k (Proxy @('MetaSel ('Just s) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy))+      FieldStrict -> k (Proxy @('MetaSel ('Just s) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedStrict))++ghcMetadata :: Generic a => proxy a -> GhcMetadata a+ghcMetadata pa = GhcMetadata {+      ghcMetadataFields = Rep.map ghcFieldMetadata recordFieldMetadata+    }+  where+    Metadata{..} = metadata pa++    ghcFieldMetadata :: FieldMetadata x -> GhcFieldMetadata x+    ghcFieldMetadata (FieldMetadata pName s) =+        withFieldMetadata pName s $ GhcFieldMetadata
+ src/Data/Record/Generic/JSON.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE RecordWildCards     #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications    #-}+{-# LANGUAGE TypeOperators       #-}++-- | Generic conversion to/from JSON+module Data.Record.Generic.JSON (+    gtoJSON+  , gparseJSON+  ) where++import Data.Aeson+import Data.Aeson.Types+import Data.Proxy+import Data.String++import Data.Record.Generic+import qualified Data.Record.Generic.Rep as Rep++gtoJSON :: forall a. (Generic a, Constraints a ToJSON) => a -> Value+gtoJSON =+      object+    . Rep.collapse+    . Rep.zipWith (mapKKK $ \n x -> (fromString n, x)) (recordFieldNames md)+    . Rep.cmap (Proxy @ToJSON) (K . toJSON . unI)+    . from+  where+    md = metadata (Proxy @a)++gparseJSON :: forall a. (Generic a, Constraints a FromJSON) => Value -> Parser a+gparseJSON =+    withObject (recordName md) (fmap to . Rep.sequenceA . aux)+  where+    md = metadata (Proxy @a)++    aux :: Object -> Rep (Parser :.: I) a+    aux obj =+        Rep.cmap+          (Proxy @FromJSON)+          (\(K fld) -> Comp (I <$> getField fld))+          (recordFieldNames md)+      where+        getField :: FromJSON x => String -> Parser x+        getField fld = obj .: fromString fld
+ src/Data/Record/Generic/Lens/VL.hs view
@@ -0,0 +1,222 @@+{-# LANGUAGE FlexibleContexts      #-}+{-# LANGUAGE FlexibleInstances     #-}+{-# LANGUAGE GADTs                 #-}+{-# LANGUAGE KindSignatures        #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RankNTypes            #-}+{-# LANGUAGE ScopedTypeVariables   #-}+{-# LANGUAGE TypeApplications      #-}+{-# LANGUAGE TypeOperators         #-}+{-# LANGUAGE QuantifiedConstraints #-}++-- | van Laarhoven lenses for large records.+-- The type synonym+--+-- @+--   type Lens' s a = forall f. Functor f => (a -> f a) -> s -> f s+-- @+-- Appears below, however it is not exported to avoid conflicts with other+-- libraries defining equivalent synonyms.+module Data.Record.Generic.Lens.VL (+    -- * Lenses for records+    SimpleRecordLens(..)+  , HKRecordLens(..)+  , RegularRecordLens(..)+  , lensesForSimpleRecord+  , lensesForHKRecord+  , lensesForRegularRecord+    -- * Regular records+  , RegularField(..)+  , IsRegularField(..)+    -- * Lenses into 'Rep'+  , RepLens(..)+  , repLenses+    -- * General purpose lenses+  , genericLens+  , normalForm1Lens+  , interpretedLens+  , standardInterpretationLens+  ) where++import Data.Kind++import Data.Record.Generic+import Data.Record.Generic.Transform++import qualified Data.Record.Generic.Rep as Rep++-- | The standard van Laarhoven representation for a monomorphic lens+type Lens' s a = forall f. Functor f => (a -> f a) -> s -> f s++{-------------------------------------------------------------------------------+  Simple records (in contrast to higher-kinded records, see below)+-------------------------------------------------------------------------------}++data SimpleRecordLens a b where+  SimpleRecordLens :: Lens' a b -> SimpleRecordLens a b++-- | Construct lenses for each field in the record+--+-- NOTE: This is of limited use since we cannot pattern match on the resulting+-- 'Rep' in any meaningful way. It is possible to go through the SOP adapter,+-- but if we do, we incur quadratic cost again.+--+-- We can do better for higher-kinded records, and better still for regular+-- higher-kinded records. See 'lensesForHKRecord' and 'lensesForRegularRecord'.+lensesForSimpleRecord :: forall a. Generic a => Rep (SimpleRecordLens a) a+lensesForSimpleRecord =+    Rep.map (\(RepLens l) -> SimpleRecordLens $ \f -> aux l f) repLenses+  where+    aux :: Lens' (Rep I a) (I x) -> Lens' a x+    aux l f a = to <$> l (\(I x) -> I <$> f x) (from a)++{-------------------------------------------------------------------------------+  Higher-kinded records (records with a functor parameter)+-------------------------------------------------------------------------------}++-- | Lens for higher-kinded record+--+-- See 'lensesForHKRecord' for details.+data HKRecordLens d (f :: Type -> Type) tbl x where+  HKRecordLens :: Lens' (tbl f) (Interpret (d f) x) -> HKRecordLens d f tbl x++-- | Lenses for higher-kinded records+--+-- NOTE: The lenses constructed by this function are primarily intended for+-- further processing, either by 'lensesForRegularRecord' or using application+-- specific logic. Details below.+--+-- Suppose we have a record @tbl f@ which is indexed by a functor @f@, and we+-- want to construct lenses from @tbl f@ to each field in the record. Using the+-- @Transform@ infrastructure, we can construct a lens+--+-- > tbl f ~~> Rep I (tbl f) ~~> Rep (Interpret (d f)) (tbl Uninterpreted)+--+-- Using 'repLenses' we can construct a lens of type+--+-- > Rep (Interpret (d f)) (tbl Uninterpreted) ~~> Interpret (d f) x+--+-- for every field of type @x@. Putting these two together gives us a lens+--+-- > tbl f ~~> Interpret (d f) x+--+-- for every field in @tbl Uninterpreted@. We cannot simplify this, because we+-- do not know anything about the shape of @x@; specifically, it might not be+-- equal to @Uninterpreted x'@ for some @x'@, and hence we cannot simplify the+-- target type of the lens. We can do better for records with regular fields;+-- see 'lensesForRegularRecord'.+lensesForHKRecord :: forall d tbl f.+     ( Generic (tbl f)+     , Generic (tbl Uninterpreted)+     , HasNormalForm (d f) (tbl f) (tbl Uninterpreted)+     )+  => Proxy d -> Rep (HKRecordLens d f tbl) (tbl Uninterpreted)+lensesForHKRecord d = Rep.map aux fromRepLenses+  where+    fromRepLenses :: Rep (RepLens (Interpret (d f)) (tbl Uninterpreted)) (tbl Uninterpreted)+    fromRepLenses = repLenses++    aux :: forall x. RepLens (Interpret (d f)) (tbl Uninterpreted) x -> HKRecordLens d f tbl x+    aux (RepLens l) = HKRecordLens $+          genericLens+        . normalForm1Lens d+        . l++{-------------------------------------------------------------------------------+  Regular records+-------------------------------------------------------------------------------}++-- | Proof that @x@ is a regular field+--+-- See 'IsRegularField'+data RegularField f x where+  RegularField :: RegularField f (f x)++-- | Regular record fields+--+-- For a higher-kinded record @tbl f@, parameterized over some functor @f@,+-- we say that the fields are /regular/ iff every field has the form @f x@+-- for some @x@.+class IsRegularField f x where+  isRegularField :: Proxy (f x) -> RegularField f x++instance IsRegularField f (f x) where+  isRegularField _ = RegularField++{-------------------------------------------------------------------------------+  Lenses into regular records+-------------------------------------------------------------------------------}++-- | Lens into a regular record+--+-- See 'lensesForRegularRecord'+data RegularRecordLens tbl f x where+  RegularRecordLens :: Lens' (tbl f) (f x) -> RegularRecordLens tbl f x++-- | Lenses into higher-kinded records with regular fields+--+-- We can use 'lensesForHKRecord' to construct a 'Rep' of lenses into a higher-kinded+-- record. If in addition the record is regular, we can use the record type+-- /itself/ to store all the lenses.+lensesForRegularRecord :: forall d tbl f.+     ( Generic (tbl (RegularRecordLens tbl f))+     , Generic (tbl Uninterpreted)+     , Generic (tbl f)+     , HasNormalForm (d (RegularRecordLens tbl f)) (tbl (RegularRecordLens tbl f)) (tbl Uninterpreted)+     , HasNormalForm (d f) (tbl f) (tbl Uninterpreted)+     , Constraints (tbl Uninterpreted) (IsRegularField Uninterpreted)+     , StandardInterpretation d (RegularRecordLens tbl f)+     , StandardInterpretation d f+     )+  => Proxy d -> tbl (RegularRecordLens tbl f)+lensesForRegularRecord d = to . denormalize1 d $+    Rep.cmap+      (Proxy @(IsRegularField Uninterpreted))+      aux+      (lensesForHKRecord d)+  where+    aux :: forall x.+         IsRegularField Uninterpreted x+      => HKRecordLens d f tbl x+      -> Interpret (d (RegularRecordLens tbl f)) x+    aux (HKRecordLens l) =+        case isRegularField (Proxy @(Uninterpreted x)) of+          RegularField -> toStandardInterpretation d $ RegularRecordLens $+             l . standardInterpretationLens d++{-------------------------------------------------------------------------------+  Lenses into 'Rep'+-------------------------------------------------------------------------------}++data RepLens f a x where+  RepLens :: Lens' (Rep f a) (f x) -> RepLens f a x++repLenses :: Generic a => Rep (RepLens f a) a+repLenses = Rep.map aux Rep.allIndices+  where+    aux :: Rep.Index a x -> RepLens f a x+    aux ix = RepLens $ Rep.updateAtIndex ix++{-------------------------------------------------------------------------------+  General purpose lenses+-------------------------------------------------------------------------------}++genericLens :: Generic a => Lens' a (Rep I a)+genericLens f a = to <$> f (from a)++normalForm1Lens ::+     HasNormalForm (d f) (x f) (x Uninterpreted)+  => Proxy d+  -> Lens' (Rep I (x f)) (Rep (Interpret (d f)) (x Uninterpreted))+normalForm1Lens p f a = denormalize1 p <$> f (normalize1 p a)++interpretedLens :: Lens' (Interpret d x) (Interpreted d x)+interpretedLens f (Interpret x) = Interpret <$> f x++standardInterpretationLens :: forall d f x.+     StandardInterpretation d f+  => Proxy d+  -> Lens' (Interpret (d f) (Uninterpreted x)) (f x)+standardInterpretationLens p f x =+    toStandardInterpretation p <$>+      f (fromStandardInterpretation p x)
+ src/Data/Record/Generic/LowerBound.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE TypeApplications #-}++-- | Simple example of a generic function+module Data.Record.Generic.LowerBound (+    LowerBound(..)+  , glowerBound+  ) where++import Data.Record.Generic+import qualified Data.Record.Generic.Rep as Rep++{-------------------------------------------------------------------------------+  General definition+-------------------------------------------------------------------------------}++-- | Types with a lower bound+class LowerBound a where+  lowerBound :: a++instance LowerBound Word where lowerBound = 0+instance LowerBound Bool where lowerBound = False+instance LowerBound Char where lowerBound = '\x0000'+instance LowerBound ()   where lowerBound = ()+instance LowerBound [a]  where lowerBound = []++{-------------------------------------------------------------------------------+  Generic definition+-------------------------------------------------------------------------------}++glowerBound :: (Generic a, Constraints a LowerBound) => a+glowerBound = to $ Rep.cpure (Proxy @LowerBound) (I lowerBound)
+ src/Data/Record/Generic/Rep.hs view
@@ -0,0 +1,200 @@+{-# LANGUAGE ConstraintKinds     #-}+{-# LANGUAGE RankNTypes          #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications    #-}+{-# LANGUAGE TypeOperators       #-}++-- | Operations on the generic representation+--+-- We also re-export some non-derive functions to clarify where they belong+-- in this list.+--+-- This module is intended for qualified import.+--+-- > import qualified Data.Record.Generic.Rep as Rep+--+-- TODO: Could we provide instances for the @generics-sop@ type classes?+-- Might lessen the pain of switching between the two or using both?+module Data.Record.Generic.Rep (+    Rep(..) -- TODO: Make opaque?+    -- * "Functor"+  , map+  , mapM+  , cmap+  , cmapM+    -- * Zipping+  , zip+  , zipWith+  , zipWithM+  , czipWith+  , czipWithM+    -- * "Foldable"+  , collapse+    -- * "Traversable"+  , sequenceA+    -- * "Applicable"+  , pure+  , cpure+  , ap+    -- * Array-like interface+  , Index -- opaque+  , indexToInt+  , getAtIndex+  , putAtIndex+  , updateAtIndex+  , allIndices+  , mapWithIndex+  ) where++import Prelude hiding (+    map+  , mapM+  , pure+  , sequenceA+  , zip+  , zipWith+  )++import Data.Proxy+import Data.Functor.Identity+import Data.Functor.Product+import Data.SOP.Classes (fn_2)+import Unsafe.Coerce (unsafeCoerce)++import qualified Data.Vector as V++import Data.Record.Generic+import Data.Record.Generic.Rep.Internal++--+-- NOTE: In order to avoid circular definitions, this module is strictly defined+-- in order: every function only depends on the functions defined before it.+--++{-------------------------------------------------------------------------------+  Array-like interface+-------------------------------------------------------------------------------}++newtype Index a x = UnsafeIndex Int++indexToInt :: Index a x -> Int+indexToInt (UnsafeIndex ix) = ix++getAtIndex :: Index a x -> Rep f a -> f x+getAtIndex (UnsafeIndex ix) (Rep v) =+    unsafeCoerce $ V.unsafeIndex v ix++putAtIndex :: Index a x -> f x -> Rep f a -> Rep f a+putAtIndex (UnsafeIndex ix) x (Rep v) = Rep $+    V.unsafeUpd v [(ix, unsafeCoerce x)]++updateAtIndex ::+     Functor m+  => Index a x+  -> (f x -> m (f x))+  -> Rep f a -> m (Rep f a)+updateAtIndex ix f a = (\x -> putAtIndex ix x a) <$> f (getAtIndex ix a)++allIndices :: forall a. Generic a => Rep (Index a) a+allIndices = Rep $ V.generate (recordSize (metadata (Proxy @a))) UnsafeIndex++-- | Map with index+--+-- This is an important building block in this module.+-- Crucially, @mapWithIndex f a@ is lazy in @a@, reading elements from @a@+-- only if and when @f@ demands them.+mapWithIndex ::+     forall f g a. Generic a+  => (forall x. Index a x -> f x -> g x)+  -> Rep f a -> Rep g a+mapWithIndex f as = map' f' allIndices+  where+    f' :: Index a x -> g x+    f' ix = f ix (getAtIndex ix as)++{-------------------------------------------------------------------------------+  "Applicative"+-------------------------------------------------------------------------------}++pure :: forall f a. Generic a => (forall x. f x) -> Rep f a+pure f = Rep (V.replicate (recordSize (metadata (Proxy @a))) f)++cpure ::+     (Generic a, Constraints a c)+  => Proxy c+  -> (forall x. c x => f x)+  -> Rep f a+cpure p f = map' (\Dict -> f) (dict p)++-- | Higher-order version of @<*>@+--+-- Lazy in the second argument.+ap :: forall f g a. Generic a => Rep (f -.-> g) a -> Rep f a -> Rep g a+ap fs as = mapWithIndex f' fs+  where+    f' :: Index a x -> (-.->) f g x -> g x+    f' ix f = f `apFn` getAtIndex ix as++{-------------------------------------------------------------------------------+  "Functor"+-------------------------------------------------------------------------------}++map :: Generic a => (forall x. f x -> g x) -> Rep f a -> Rep g a+map f = mapWithIndex (const f)++mapM ::+     (Applicative m, Generic a)+  => (forall x. f x -> m (g x))+  -> Rep f a -> m (Rep g a)+mapM f = sequenceA . mapWithIndex (const (Comp . f))++cmap ::+     (Generic a, Constraints a c)+  => Proxy c+  -> (forall x. c x => f x -> g x)+  -> Rep f a -> Rep g a+cmap p f = ap $ cpure p (Fn f)++cmapM ::+     forall m f g c a. (Generic a, Applicative m, Constraints a c)+  => Proxy c+  -> (forall x. c x => f x -> m (g x))+  -> Rep f a -> m (Rep g a)+cmapM p f = sequenceA . cmap p (Comp . f)++{-------------------------------------------------------------------------------+  Zipping+-------------------------------------------------------------------------------}++zipWithM ::+     forall m f g h a. (Generic a, Applicative m)+  => (forall x. f x -> g x -> m (h x))+  -> Rep f a -> Rep g a -> m (Rep h a)+zipWithM f a b = sequenceA $+    pure (fn_2 $ \x y -> Comp $ f x y) `ap` a `ap` b++zipWith ::+     Generic a+  => (forall x. f x -> g x -> h x)+  -> Rep f a -> Rep g a -> Rep h a+zipWith f a b = runIdentity $+    zipWithM (\x y -> Identity $ f x y) a b++zip :: Generic a => Rep f a -> Rep g a -> Rep (Product f g) a+zip = zipWith Pair++czipWithM ::+     forall m f g h c a. (Generic a, Applicative m, Constraints a c)+  => Proxy c+  -> (forall x. c x => f x -> g x -> m (h x))+  -> Rep f a -> Rep g a -> m (Rep h a)+czipWithM p f a b = sequenceA $+    cpure p (fn_2 $ \x y -> Comp $ f x y) `ap` a `ap` b++czipWith ::+     (Generic a, Constraints a c)+  => Proxy c+  -> (forall x. c x => f x -> g x -> h x)+  -> Rep f a -> Rep g a -> Rep h a+czipWith p f a b = runIdentity $+    czipWithM p (\x y -> Identity (f x y)) a b
+ src/Data/Record/Generic/Rep/Internal.hs view
@@ -0,0 +1,110 @@+{-# LANGUAGE FlexibleInstances   #-}+{-# LANGUAGE RankNTypes          #-}+{-# LANGUAGE RoleAnnotations     #-}+{-# LANGUAGE TypeOperators       #-}++-- | Definition of 'Rep' and functions that do not depend on ".Generic"+--+-- Defined as a separate module to avoid circular module dependencies.+module Data.Record.Generic.Rep.Internal (+    Rep(..)+    -- * Basic functions+  , map'+  , sequenceA+    -- * Conversion+  , unsafeFromList+  , unsafeFromListAny+  , collapse+  , toListAny+    -- * Auxiliary+  , noInlineUnsafeCo+  ) where++import Prelude hiding (sequenceA)+import qualified Prelude++import Data.Coerce (coerce)+import Data.SOP.BasicFunctors+import Data.Vector (Vector)+import GHC.Exts (Any)+import Unsafe.Coerce (unsafeCoerce)++import qualified Data.Vector as V++{-------------------------------------------------------------------------------+  Representation+-------------------------------------------------------------------------------}++-- | Representation of some record @a@+--+-- The @f@ parameter describes which functor has been applied to all fields of+-- the record; in other words @Rep I@ is isomorphic to the record itself.+newtype Rep f a = Rep (Vector (f Any))++type role Rep representational nominal++{-------------------------------------------------------------------------------+  Basic functions+-------------------------------------------------------------------------------}++-- | Strict map+--+-- @map' f x@ is strict in @x@: if @x@ is undefined, @map f x@ will also be+-- undefined, even if @f@ never needs any values from @x@.+map' :: (forall x. f x -> g x) -> Rep f a -> Rep g a+map' f (Rep v) = Rep $ f <$> v++sequenceA :: Applicative m => Rep (m :.: f) a -> m (Rep f a)+sequenceA (Rep v) = Rep <$> Prelude.sequenceA (fmap unComp v)++{-------------------------------------------------------------------------------+  Conversion+-------------------------------------------------------------------------------}++collapse :: Rep (K a) b -> [a]+collapse (Rep v) = coerce (V.toList v)++-- | Convert 'Rep' to list+toListAny :: Rep f a -> [f Any]+toListAny (Rep v) = V.toList v++-- | Convert list to 'Rep'+--+-- Does not check that the list has the right number of elements.+unsafeFromList :: [b] -> Rep (K b) a+unsafeFromList = Rep . V.fromList . Prelude.map K++-- | Convert list to 'Rep'+--+-- Does not check that the list has the right number of elements, nor the+-- types of those elements.+unsafeFromListAny :: [f Any] -> Rep f a+unsafeFromListAny = Rep . V.fromList++{-------------------------------------------------------------------------------+  Some specialised instances for 'Rep+-------------------------------------------------------------------------------}++instance Show x => Show (Rep (K x) a) where+  show (Rep v) =+      show $ Prelude.map unK (V.toList v)++instance Eq x => Eq (Rep (K x) a) where+  Rep v == Rep v' =+         Prelude.map unK (V.toList v)+      == Prelude.map unK (V.toList v')++{-------------------------------------------------------------------------------+  Auxiliary+-------------------------------------------------------------------------------}++-- | Avoid potential segfault with ghc < 9.0+--+-- See <https://gitlab.haskell.org/ghc/ghc/-/issues/16893>.+-- I haven't actually seen this fail in large-records, but we saw it fail in+-- the compact representation branch of sop-core, and what we do here is not+-- so different, so better to play it safe.+noInlineUnsafeCo :: forall a b. a -> b+{-# NOINLINE noInlineUnsafeCo #-}+noInlineUnsafeCo = unsafeCoerce+
+ src/Data/Record/Generic/SOP.hs view
@@ -0,0 +1,126 @@+{-# LANGUAGE ConstraintKinds         #-}+{-# LANGUAGE DataKinds               #-}+{-# LANGUAGE FlexibleContexts        #-}+{-# LANGUAGE FlexibleInstances       #-}+{-# LANGUAGE GADTs                   #-}+{-# LANGUAGE KindSignatures          #-}+{-# LANGUAGE MultiParamTypeClasses   #-}+{-# LANGUAGE QuantifiedConstraints   #-}+{-# LANGUAGE ScopedTypeVariables     #-}+{-# LANGUAGE StandaloneDeriving      #-}+{-# LANGUAGE TypeApplications        #-}+{-# LANGUAGE TypeFamilies            #-}+{-# LANGUAGE UndecidableInstances    #-}+{-# LANGUAGE UndecidableSuperClasses #-}++-- | Interop with @generics-sop@ generics+module Data.Record.Generic.SOP (+    -- | Translate between SOP representation and large-records representation+    Field(..)+  , fromSOP+  , toSOP+    -- | Translate constraints+  , toDictAll+    -- | Additional SOP functions+  , glowerBound+  ) where++import Data.Kind+import Data.Proxy+import Data.SOP.Dict (all_NP)+import Generics.SOP (SOP(..), NS(..), NP(..), SListI, All, Code, Compose)+import GHC.Exts (Any)+import GHC.TypeLits (Symbol)++import qualified Data.Vector  as V+import qualified Generics.SOP as SOP++import Data.Record.Generic+import Data.Record.Generic.LowerBound hiding (glowerBound)+import Data.Record.Generic.Rep.Internal (noInlineUnsafeCo)++{-------------------------------------------------------------------------------+  Conversion back and forth to generics-sop records++  NOTE: We do /not/ require @SListI (MetadataOf a)@ by default, as this would+  result in quadratic blow-up again. This is only required in this module for+  SOP interop.++  NOTE: We don't currently use @records-sop@, despite it being a /near/ perfect+  fit. The problem is that @records-sop@ is not generalized over a functor,+  which would make these functions less general than we need them to be.+-------------------------------------------------------------------------------}++newtype Field (f :: Type -> Type) (field :: (Symbol, Type)) where+  Field :: f (FieldType field) -> Field f field++deriving instance Show (f x) => Show (Field f '(nm, x))+deriving instance Eq   (f x) => Eq   (Field f '(nm, x))++fromSOP :: SListI (MetadataOf a) => NP (Field f) (MetadataOf a) -> Rep f a+fromSOP =+    Rep . V.fromList . SOP.hcollapse . SOP.hmap conv+  where+    conv :: Field f field -> K (f Any) field+    conv (Field fx) = K $ noInlineUnsafeCo fx++toSOP :: SListI (MetadataOf a) => Rep f a -> Maybe (NP (Field f) (MetadataOf a))+toSOP (Rep v) =+    SOP.hmap conv <$> SOP.fromList (V.toList v)+  where+    conv :: K (f Any) field -> Field f field+    conv (K fx) = Field (noInlineUnsafeCo fx)++{-------------------------------------------------------------------------------+  Translate constraints+-------------------------------------------------------------------------------}++-- | Translate constraints+--+-- When using 'toSOP', if you start with something of type+--+-- > Rep f a+--+-- you end up with something of type+--+-- > NP (Field f) (MetadataOf a)+--+-- When doing so, 'toDictAll' can translate+--+-- > Constraints a (Compose c f)+--+-- (which is useful over the original representation) to+--+-- > All (Compose c (Field f)) (MetadataOf a)+--+-- which is useful for the translated representation.+toDictAll ::+     forall f a c.+     ( Generic a+     , Constraints a (Compose c f)+     , All IsField (MetadataOf a)+     , forall nm x. c (f x) => c (Field f '(nm, x))+     )+  => Proxy f+  -> Proxy a+  -> Proxy c+  -> Dict (All (Compose c (Field f))) (MetadataOf a)+toDictAll _ _ _ =+    case toSOP dictT of+      Nothing -> error "toDictAll: invalid dictionary"+      Just d  -> all_NP (SOP.hcmap (Proxy @IsField) conv d)+  where+    dictT :: Rep (Dict (Compose c f)) a+    dictT = dict (Proxy @(Compose c f))++    conv :: IsField field+         => Field (Dict (Compose c f)) field+         -> Dict (Compose c (Field f)) field+    conv (Field Dict) = Dict++{-------------------------------------------------------------------------------+  Additional SOP generic functions+-------------------------------------------------------------------------------}++glowerBound :: (SOP.Generic a, All LowerBound xs, Code a ~ '[xs]) => a+glowerBound = SOP.to . SOP . Z $ SOP.hcpure (Proxy @LowerBound) (I lowerBound)
+ src/Data/Record/Generic/Show.hs view
@@ -0,0 +1,38 @@+{-# LANGUAGE RecordWildCards     #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications    #-}++module Data.Record.Generic.Show (+    gshowsPrec+  ) where++import Data.Record.Generic+import Data.List (intersperse)+import GHC.Show++import qualified Data.Record.Generic.Rep as Rep++-- | Generic definition of 'showsPrec', compatible with the GHC generated one.+--+-- Typical usage:+--+-- > instance Show T where+-- >   showsPrec = gshowsPrec+gshowsPrec :: forall a. (Generic a, Constraints a Show) => Int -> a -> ShowS+gshowsPrec d =+      aux+    . Rep.collapse+    . Rep.czipWith (Proxy @Show) showField (recordFieldNames md)+    . from+  where+    md = metadata (Proxy @a)++    showField :: Show x => K String x -> I x -> K ShowS x+    showField (K n) (I x) = K $ showString n . showString " = " . showsPrec 0 x++    aux :: [ShowS] -> ShowS+    aux fields = showParen (d >= 11) (+          showString (recordConstructor md) . showString " {"+        . foldr (.) id (intersperse showCommaSpace fields)+        . showString "}"+        )
+ src/Data/Record/Generic/Transform.hs view
@@ -0,0 +1,173 @@+{-# LANGUAGE ConstraintKinds         #-}+{-# LANGUAGE DataKinds               #-}+{-# LANGUAGE DefaultSignatures       #-}+{-# LANGUAGE FlexibleContexts        #-}+{-# LANGUAGE FlexibleInstances       #-}+{-# LANGUAGE KindSignatures          #-}+{-# LANGUAGE MultiParamTypeClasses   #-}+{-# LANGUAGE PolyKinds               #-}+{-# LANGUAGE ScopedTypeVariables     #-}+{-# LANGUAGE TypeApplications        #-}+{-# LANGUAGE TypeFamilies            #-}+{-# LANGUAGE TypeOperators           #-}+{-# LANGUAGE UndecidableInstances    #-}++-- The 'HasNormalForm' constraint on 'normalize' and 'denormalize' is+-- redundant as far as ghc is concerned (it's just 'unsafeCoerce' after all),+-- but essential for type safety of these two functions.+{-# OPTIONS_GHC -Wno-redundant-constraints #-}++module Data.Record.Generic.Transform (+    -- * Interpretation function+    Interpreted+  , Interpret(..)+    -- ** Working with the 'Interpreted' newtype wrapper+  , liftInterpreted+  , liftInterpretedA2+    -- * Normal form+    -- ** Existence+  , HasNormalForm+  , InterpretTo+  , IfEqual+    -- ** Construction+  , normalize+  , denormalize+    -- ** Specialized forms for the common case of a single type argument+  , Uninterpreted+  , DefaultInterpretation+  , normalize1+  , denormalize1+    -- ** Generalization of the default interpretation+  , StandardInterpretation(..)+  , toStandardInterpretation+  , fromStandardInterpretation+  ) where++import Data.Coerce+import Data.Kind+import Data.Proxy+import Data.SOP.BasicFunctors+import GHC.TypeLits+import Unsafe.Coerce (unsafeCoerce)++import Data.Record.Generic++{-------------------------------------------------------------------------------+  Interpretation function+-------------------------------------------------------------------------------}++type family Interpreted (d :: dom) (x :: Type) :: Type++newtype Interpret d x = Interpret (Interpreted d x)++{-------------------------------------------------------------------------------+  Working with the 'Interpreted' newtype wrapper+-------------------------------------------------------------------------------}++liftInterpreted ::+      (Interpreted dx x -> Interpreted dy y)+   -> (Interpret   dx x -> Interpret   dy y)+liftInterpreted f (Interpret x) = Interpret (f x)++liftInterpretedA2 ::+      Applicative m+   => (Interpreted dx x -> Interpreted dy y -> m (Interpreted dz z))+   -> (Interpret   dx x -> Interpret   dy y -> m (Interpret   dz z))+liftInterpretedA2 f (Interpret x) (Interpret y) = Interpret <$> f x y++{-------------------------------------------------------------------------------+  Normal forms+-------------------------------------------------------------------------------}++type HasNormalForm d x y = InterpretTo d (MetadataOf x) (MetadataOf y)++type family InterpretTo d xs ys :: Constraint where+  InterpretTo _ '[]             '[]             = ()+  InterpretTo d ('(f, x) ': xs) ('(f, y) ': ys) = IfEqual x (Interpreted d y)+                                                    (InterpretTo d xs ys)++type family IfEqual x y (r :: k) :: k where+  IfEqual actual   actual k = k+  IfEqual expected actual k = TypeError (+          'Text "Expected "+    ':<>: 'ShowType expected+    ':<>: 'Text " but got "+    ':<>: 'ShowType actual+    )++-- | Construct normal form+--+-- TODO: Documentation.+normalize ::+     HasNormalForm d x y+  => Proxy d+  -> Proxy y+  -> Rep I x -> Rep (Interpret d) y+normalize _ _ = unsafeCoerce++denormalize ::+     HasNormalForm d x y+  => Proxy d+  -> Proxy y+  -> Rep (Interpret d) y -> Rep I x+denormalize _ _ = unsafeCoerce++{-------------------------------------------------------------------------------+  Specialized forms for the common case of a single type argument++  The tests in "Test.Record.Generic.Sanity.Transform" show an example with+  two arguments.+-------------------------------------------------------------------------------}++data Uninterpreted x++data DefaultInterpretation (f :: Type -> Type)++type instance Interpreted (DefaultInterpretation f) (Uninterpreted x) = f x++normalize1 :: forall d f x.+     HasNormalForm (d f) (x f) (x Uninterpreted)+  => Proxy d+  -> Rep I (x f) -> Rep (Interpret (d f)) (x Uninterpreted)+normalize1 _ = normalize (Proxy @(d f)) (Proxy @(x Uninterpreted))++denormalize1 :: forall d f x.+     HasNormalForm (d f) (x f) (x Uninterpreted)+  => Proxy d+  -> Rep (Interpret (d f)) (x Uninterpreted) -> Rep I (x f)+denormalize1 _ = denormalize (Proxy @(d f)) (Proxy @(x Uninterpreted))++{-------------------------------------------------------------------------------+  Generalization of the default interpretation+-------------------------------------------------------------------------------}++class StandardInterpretation d f where+  standardInterpretation ::+       Proxy d+    -> ( Interpreted (d f) (Uninterpreted x) -> f x+       , f x -> Interpreted (d f) (Uninterpreted x)+       )++  default standardInterpretation ::+       Coercible (Interpreted (d f) (Uninterpreted x)) (f x)+    => Proxy d+    -> ( Interpreted (d f) (Uninterpreted x) -> f x+       , f x -> Interpreted (d f) (Uninterpreted x)+       )+  standardInterpretation _ = (coerce, coerce)++instance StandardInterpretation DefaultInterpretation f++toStandardInterpretation :: forall d f x.+     StandardInterpretation d f+  => Proxy d+  -> f x -> Interpret (d f) (Uninterpreted x)+toStandardInterpretation d fx = Interpret $+    snd (standardInterpretation d) fx++fromStandardInterpretation :: forall d f x.+     StandardInterpretation d f+  => Proxy d+  -> Interpret (d f) (Uninterpreted x) -> f x+fromStandardInterpretation d (Interpret fx) =+    fst (standardInterpretation d) fx
+ test/Test/Record/Generic/Infra/Beam/Interpretation.hs view
@@ -0,0 +1,72 @@+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}++-- | Integration of large-generics with mini-beam+--+-- See the @beam-large-package@ for full beam integration.+module Test.Record.Generic.Infra.Beam.Interpretation (+    BeamInterpretation+  , ZipInterpreted(..)+  , gzipBeam+  ) where++import Data.Functor.Identity+import Data.Kind+import Data.Proxy++import Data.Record.Generic+import Data.Record.Generic.Transform+import Data.Record.Generic.Lens.VL++import qualified Data.Record.Generic.Rep as Rep++import Test.Record.Generic.Infra.Beam.Mini++data BeamInterpretation (f :: Type -> Type)++type instance Interpreted (BeamInterpretation f) (table Uninterpreted) = table f+type instance Interpreted (BeamInterpretation f) (Uninterpreted x)     = Columnar f x++instance StandardInterpretation BeamInterpretation (RegularRecordLens tbl f)+instance StandardInterpretation BeamInterpretation Identity++class ZipInterpreted a where+  zipInterpreted ::+       Applicative m+    => (forall x. Columnar' f x -> Columnar' g x -> m (Columnar' h x))+    -> Interpret (BeamInterpretation f) a+    -> Interpret (BeamInterpretation g) a+    -> m (Interpret (BeamInterpretation h) a)++instance Beamable table => ZipInterpreted (table Uninterpreted) where+  zipInterpreted f = liftInterpretedA2 $ zipBeamFieldsM f++instance ZipInterpreted (Uninterpreted x) where+  zipInterpreted f = liftInterpretedA2 $ applyColumnar' (Proxy @x) f++gzipBeam :: forall m table f g h.+     ( Applicative m+     , Generic (table f)+     , Generic (table g)+     , Generic (table h)+     , Generic (table Uninterpreted)+     , Constraints (table Uninterpreted) ZipInterpreted+     , HasNormalForm (BeamInterpretation f) (table f) (table Uninterpreted)+     , HasNormalForm (BeamInterpretation g) (table g) (table Uninterpreted)+     , HasNormalForm (BeamInterpretation h) (table h) (table Uninterpreted)+     )+  => (forall a. Columnar' f a -> Columnar' g a -> m (Columnar' h a))+  -> table f -> table g -> m (table h)+gzipBeam f a b =+    fmap (to . denormalize1 (Proxy @BeamInterpretation)) $+      Rep.czipWithM+        (Proxy @ZipInterpreted)+        (zipInterpreted f)+        (normalize1 (Proxy @BeamInterpretation) (from a))+        (normalize1 (Proxy @BeamInterpretation) (from b))+
+ test/Test/Record/Generic/Infra/Beam/Mini.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE FlexibleContexts     #-}+{-# LANGUAGE FlexibleInstances    #-}+{-# LANGUAGE KindSignatures       #-}+{-# LANGUAGE RankNTypes           #-}+{-# LANGUAGE ScopedTypeVariables  #-}+{-# LANGUAGE TypeApplications     #-}+{-# LANGUAGE TypeFamilies         #-}+{-# LANGUAGE UndecidableInstances #-}++-- TODO: cleanup++module Test.Record.Generic.Infra.Beam.Mini (+    Columnar+  , Columnar'(..)+  , applyColumnar'+  , PrimaryKey+  , Table+  , Lenses+  , Beamable(..)+  , WrapLens(..)+  ) where++import Data.Functor.Identity+import Data.Kind+import Data.Proxy+import Lens.Micro (Lens')++data Nullable (c :: Type -> Type) x++data Lenses (tbl :: (Type -> Type) -> Type) (f :: Type -> Type) (x :: Type)++data WrapLens a b = WrapLens (Lens' a b)++type family Columnar (f :: Type -> Type) x where+  Columnar Identity       x = x+  Columnar (Nullable c)   x = Columnar c (Maybe x)+  Columnar (Lenses tbl f) x = WrapLens (tbl f) (Columnar f x)+  Columnar f              x = f x++newtype Columnar' f a = Columnar' { getColumnar' :: Columnar f a }++applyColumnar' :: forall m f g h x.+     Functor m+  => Proxy x+  -> (Columnar' f x -> Columnar' g x -> m (Columnar' h x))+  -> (Columnar  f x -> Columnar  g x -> m (Columnar  h x))+applyColumnar' _ f fx gx = getColumnar' <$> f (Columnar' fx) (Columnar' gx)++class Beamable (table :: (Type -> Type) -> Type) where+  zipBeamFieldsM ::+       Applicative m+    => (forall a. Columnar' f a -> Columnar' g a -> m (Columnar' h a))+    -> table f -> table g -> m (table h)++-- | Primary key of a table+--+-- In beam this is an associated type of the 'Table' class; we split this off+-- so that we can define the basic table definitions without needing to define+-- the transform ('zipBeamFieldsM') at the same time.+data family PrimaryKey (table :: (Type -> Type) -> Type) :: (Type -> Type) -> Type++class (Beamable table, Beamable (PrimaryKey table)) => Table table where+
+ test/Test/Record/Generic/Infra/Examples.hs view
@@ -0,0 +1,538 @@+{-# LANGUAGE ConstraintKinds         #-}+{-# LANGUAGE DataKinds               #-}+{-# LANGUAGE DeriveGeneric           #-}+{-# LANGUAGE FlexibleContexts        #-}+{-# LANGUAGE FlexibleInstances       #-}+{-# LANGUAGE InstanceSigs            #-}+{-# LANGUAGE MultiParamTypeClasses   #-}+{-# LANGUAGE RecordWildCards         #-}+{-# LANGUAGE ScopedTypeVariables     #-}+{-# LANGUAGE StandaloneDeriving      #-}+{-# LANGUAGE TypeApplications        #-}+{-# LANGUAGE TypeFamilies            #-}+{-# LANGUAGE UndecidableInstances    #-}+{-# LANGUAGE UndecidableSuperClasses #-}+{-# LANGUAGE ViewPatterns            #-}++{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}++-- | Standard Haskell records with a hand-written Generic instance+--+-- @large-records@ and @large-anon@ provide different record representations,+-- but here we want to test the generics infrastructure independent from these+-- libraries. The definitions here are simple, not intended to check for core+-- size; we do that in large-records and large-anon.+--+-- These definitions serve as a simple example of how the 'Generic' class might+-- be instantiated, and are used as a basis for testing.+module Test.Record.Generic.Infra.Examples (+    -- * Simple+    SimpleRecord(..)+  , exampleSimpleRecord+    -- * With type parameters+  , ParamRecord(..)+  , exampleParamRecord+    -- * Higher-kinded+  , Regular(..)+  , Irregular(..)+  , MultiFun(..)+  , exampleRegular+  , exampleIrregular+    -- * Beam-like+  , MixinTable(..)+  , FullTable(..)+  , PrimaryKey(..)+  , exampleMixinTable+  ) where++import Data.Kind++import qualified GHC.Generics as GHC+import qualified Generics.SOP as SOP++import Test.QuickCheck++import Data.Record.Generic+import Data.Record.Generic.Rep.Internal (noInlineUnsafeCo)++import qualified Data.Record.Generic.Rep.Internal as Rep++import Test.Record.Generic.Infra.Beam.Mini+import Data.Functor.Identity++{-------------------------------------------------------------------------------+  Simple record+-------------------------------------------------------------------------------}++data SimpleRecord = MkSimpleRecord {+      simpleRecordField1 :: Word+    , simpleRecordField2 :: Bool+    }+  deriving (Show, Eq, GHC.Generic) -- GHC generics for the GhcGenerics tests++exampleSimpleRecord :: SimpleRecord+exampleSimpleRecord = MkSimpleRecord 5 True++class    (c Word, c Bool) => ConstraintsSimpleRecord c where+instance (c Word, c Bool) => ConstraintsSimpleRecord c where++instance Generic SimpleRecord where+  type Constraints SimpleRecord = ConstraintsSimpleRecord+  type MetadataOf  SimpleRecord = '[ '("simpleRecordField1", Word)+                                   , '("simpleRecordField2", Bool)+                                   ]++  from MkSimpleRecord{..} = Rep.unsafeFromListAny [+        I $ noInlineUnsafeCo simpleRecordField1+      , I $ noInlineUnsafeCo simpleRecordField2+      ]++  to rep =+    let { [ I (noInlineUnsafeCo -> simpleRecordField1)+          , I (noInlineUnsafeCo -> simpleRecordField2)+          ] = Rep.toListAny rep }+    in MkSimpleRecord{..}++  dict :: forall c.+       ConstraintsSimpleRecord c+    => Proxy c -> Rep (Dict c) SimpleRecord+  dict _ = Rep.unsafeFromListAny [+        noInlineUnsafeCo (Dict :: Dict c Word)+      , noInlineUnsafeCo (Dict :: Dict c Bool)+      ]++  metadata _ = Metadata {+        recordName          = "SimpleRecord"+      , recordConstructor   = "MkSimpleRecord"+      , recordSize          = 2+      , recordFieldMetadata = Rep.unsafeFromListAny [+            noInlineUnsafeCo $+              FieldMetadata (Proxy @"simpleRecordField1") FieldLazy+          , noInlineUnsafeCo $+              FieldMetadata (Proxy @"simpleRecordField2") FieldLazy+          ]+      }++instance Arbitrary SimpleRecord where+  arbitrary = MkSimpleRecord <$> arbitrary <*> arbitrary+  shrink (MkSimpleRecord f1 f2) = concat [+        [ MkSimpleRecord f1' f2+        | f1' <- shrink f1+        ]+      , [ MkSimpleRecord f1 f2'+        | f2' <- shrink f2+        ]+      ]++{-------------------------------------------------------------------------------+  Record with some type arguments (but not higher-kinded)+-------------------------------------------------------------------------------}++data ParamRecord a b = MkParamRecord {+      paramRecordField1 :: Word+    , paramRecordField2 :: Bool+    , paramRecordField3 :: Char+    , paramRecordField4 :: a+    , paramRecordField5 :: [b]+    }+  deriving (Eq, Ord, Show, GHC.Generic)++instance SOP.Generic (ParamRecord a b)+  -- For comparison purposes only++class    (c Word, c Bool, c Char, c a, c [b]) => ConstraintsParamRecord a b c+instance (c Word, c Bool, c Char, c a, c [b]) => ConstraintsParamRecord a b c++instance Generic (ParamRecord a b) where+  type Constraints (ParamRecord a b) = ConstraintsParamRecord a b+  type MetadataOf  (ParamRecord a b) = '[ '("paramRecordField1", Word)+                                        , '("paramRecordField2", Bool)+                                        , '("paramRecordField3", Char)+                                        , '("paramRecordField4", a)+                                        , '("paramRecordField5", [b])+                                        ]++  from MkParamRecord{..} = Rep.unsafeFromListAny [+        I $ noInlineUnsafeCo paramRecordField1+      , I $ noInlineUnsafeCo paramRecordField2+      , I $ noInlineUnsafeCo paramRecordField3+      , I $ noInlineUnsafeCo paramRecordField4+      , I $ noInlineUnsafeCo paramRecordField5+      ]++  to rep =+    let { [ I (noInlineUnsafeCo -> paramRecordField1)+          , I (noInlineUnsafeCo -> paramRecordField2)+          , I (noInlineUnsafeCo -> paramRecordField3)+          , I (noInlineUnsafeCo -> paramRecordField4)+          , I (noInlineUnsafeCo -> paramRecordField5)+          ] = Rep.toListAny rep }+    in MkParamRecord{..}++  dict :: forall c.+       ConstraintsParamRecord a b c+    => Proxy c -> Rep (Dict c) (ParamRecord a b)+  dict _ = Rep.unsafeFromListAny [+        noInlineUnsafeCo (Dict :: Dict c Word)+      , noInlineUnsafeCo (Dict :: Dict c Bool)+      , noInlineUnsafeCo (Dict :: Dict c Char)+      , noInlineUnsafeCo (Dict :: Dict c a)+      , noInlineUnsafeCo (Dict :: Dict c [b])+      ]++  metadata _ = Metadata {+        recordName          = "ParamRecord"+      , recordConstructor   = "MkParamRecord"+      , recordSize          = 5+      , recordFieldMetadata = Rep.unsafeFromListAny [+            noInlineUnsafeCo $+              FieldMetadata (Proxy @"paramRecordField1") FieldLazy+          , noInlineUnsafeCo $+              FieldMetadata (Proxy @"paramRecordField2") FieldLazy+          , noInlineUnsafeCo $+              FieldMetadata (Proxy @"paramRecordField3") FieldLazy+          , noInlineUnsafeCo $+              FieldMetadata (Proxy @"paramRecordField4") FieldLazy+          , noInlineUnsafeCo $+              FieldMetadata (Proxy @"paramRecordField5") FieldLazy+          ]+      }++exampleParamRecord :: ParamRecord () Float+exampleParamRecord = MkParamRecord 5 True 'c' () [3.14]++{-------------------------------------------------------------------------------+  Higher kinded record: regular+-------------------------------------------------------------------------------}++-- | Regular example: all fields have an @f@ parameter+data Regular f = MkRegular {+        regularField1 :: f Int+      , regularField2 :: f Bool+      , regularField3 :: f String+      }++exampleRegular :: Regular I+exampleRegular = MkRegular {+      regularField1 = I 5+    , regularField2 = I True+    , regularField3 = I "a"+    }++deriving instance (Show (f Int), Show (f Bool), Show (f String)) => Show (Regular f)+deriving instance (Eq   (f Int), Eq   (f Bool), Eq   (f String)) => Eq   (Regular f)++class    (c (f Int), c (f Bool), c (f String)) => ConstraintsRegular f c+instance (c (f Int), c (f Bool), c (f String)) => ConstraintsRegular f c++instance Generic (Regular f) where+  type Constraints (Regular f) = ConstraintsRegular f+  type MetadataOf  (Regular f) = '[ '("regularField1", f Int)+                                  , '("regularField1", f Bool)+                                  , '("regularField3", f String)+                                  ]+++  from MkRegular{..} = Rep.unsafeFromListAny [+        I $ noInlineUnsafeCo regularField1+      , I $ noInlineUnsafeCo regularField2+      , I $ noInlineUnsafeCo regularField3+      ]++  to rep =+    let { [ I (noInlineUnsafeCo -> regularField1)+          , I (noInlineUnsafeCo -> regularField2)+          , I (noInlineUnsafeCo -> regularField3)+          ] = Rep.toListAny rep }+    in MkRegular{..}++  dict :: forall c.+       ConstraintsRegular f c+    => Proxy c -> Rep (Dict c) (Regular f)+  dict _ = Rep.unsafeFromListAny [+        noInlineUnsafeCo (Dict :: Dict c (f Int))+      , noInlineUnsafeCo (Dict :: Dict c (f Bool))+      , noInlineUnsafeCo (Dict :: Dict c (f String))+      ]++  metadata _ = Metadata {+        recordName          = "Regular"+      , recordConstructor   = "MkRegular"+      , recordSize          = 3+      , recordFieldMetadata = Rep.unsafeFromListAny [+            noInlineUnsafeCo $+              FieldMetadata (Proxy @"regularField1") FieldLazy+          , noInlineUnsafeCo $+              FieldMetadata (Proxy @"regularField2") FieldLazy+          , noInlineUnsafeCo $+              FieldMetadata (Proxy @"regularField3") FieldLazy+          ]+      }++{-------------------------------------------------------------------------------+  Higher kinded record: irregular+-------------------------------------------------------------------------------}++-- | Irregular example: not all fields have an @f@ parameter+data Irregular f = MkIrregular {+        irregularField1 :: f Int+      , irregularField2 :: f Bool+      , irregularField3 :: String+      }++exampleIrregular :: Irregular I+exampleIrregular = MkIrregular {+      irregularField1 = I 1234+    , irregularField2 = I True+    , irregularField3 = "hi"+    }++deriving instance (Show (f Int), Show (f Bool)) => Show (Irregular f)+deriving instance (Eq   (f Int), Eq   (f Bool)) => Eq   (Irregular f)++class    (c (f Int), c (f Bool), c String) => ConstraintsIrregular f c+instance (c (f Int), c (f Bool), c String) => ConstraintsIrregular f c++instance Generic (Irregular f) where+  type Constraints (Irregular f) = ConstraintsIrregular f+  type MetadataOf  (Irregular f) = '[ '("irregularField1", f Int)+                                    , '("irregularField1", f Bool)+                                    , '("irregularField3", String)+                                    ]++  from MkIrregular{..} = Rep.unsafeFromListAny [+        I $ noInlineUnsafeCo irregularField1+      , I $ noInlineUnsafeCo irregularField2+      , I $ noInlineUnsafeCo irregularField3+      ]++  to rep =+    let { [ I (noInlineUnsafeCo -> irregularField1)+          , I (noInlineUnsafeCo -> irregularField2)+          , I (noInlineUnsafeCo -> irregularField3)+          ] = Rep.toListAny rep }+    in MkIrregular{..}++  dict :: forall c.+       ConstraintsIrregular f c+    => Proxy c -> Rep (Dict c) (Irregular f)+  dict _ = Rep.unsafeFromListAny [+        noInlineUnsafeCo (Dict :: Dict c (f Int))+      , noInlineUnsafeCo (Dict :: Dict c (f Bool))+      , noInlineUnsafeCo (Dict :: Dict c String)+      ]++  metadata _ = Metadata {+        recordName          = "Irregular"+      , recordConstructor   = "MkIrregular"+      , recordSize          = 3+      , recordFieldMetadata = Rep.unsafeFromListAny [+            noInlineUnsafeCo $+              FieldMetadata (Proxy @"irregularField1") FieldLazy+          , noInlineUnsafeCo $+              FieldMetadata (Proxy @"irregularField2") FieldLazy+          , noInlineUnsafeCo $+              FieldMetadata (Proxy @"irregularField3") FieldLazy+          ]+      }++{-------------------------------------------------------------------------------+  Higher kinded record: multiple functors+-------------------------------------------------------------------------------}++data MultiFun f g = MkMultiFun {+        multiFunField1 :: f Int+      , multiFunField2 :: g Bool+      , multiFunField3 :: String+      }++deriving instance (Show (f Int), Show (g Bool)) => Show (MultiFun f g)+deriving instance (Eq   (f Int), Eq   (g Bool)) => Eq   (MultiFun f g)++class    (c (f Int), c (g Bool), c String) => ConstraintsMultiFun f g c+instance (c (f Int), c (g Bool), c String) => ConstraintsMultiFun f g c++instance Generic (MultiFun f g) where+  type Constraints (MultiFun f g) = ConstraintsMultiFun f g+  type MetadataOf  (MultiFun f g) = '[ '("multiFunField1", f Int)+                                     , '("multiFunField1", g Bool)+                                     , '("multiFunField3", String)+                                     ]++  from MkMultiFun{..} = Rep.unsafeFromListAny [+        I $ noInlineUnsafeCo multiFunField1+      , I $ noInlineUnsafeCo multiFunField2+      , I $ noInlineUnsafeCo multiFunField3+      ]++  to rep =+    let { [ I (noInlineUnsafeCo -> multiFunField1)+          , I (noInlineUnsafeCo -> multiFunField2)+          , I (noInlineUnsafeCo -> multiFunField3)+          ] = Rep.toListAny rep }+    in MkMultiFun{..}++  dict :: forall c.+       ConstraintsMultiFun f g c+    => Proxy c -> Rep (Dict c) (MultiFun f g)+  dict _ = Rep.unsafeFromListAny [+        noInlineUnsafeCo (Dict :: Dict c (f Int))+      , noInlineUnsafeCo (Dict :: Dict c (g Bool))+      , noInlineUnsafeCo (Dict :: Dict c String)+      ]++  metadata _ = Metadata {+        recordName          = "MultiFun"+      , recordConstructor   = "MkMultiFun"+      , recordSize          = 3+      , recordFieldMetadata = Rep.unsafeFromListAny [+            noInlineUnsafeCo $+              FieldMetadata (Proxy @"multiFunField1") FieldLazy+          , noInlineUnsafeCo $+              FieldMetadata (Proxy @"multiFunField2") FieldLazy+          , noInlineUnsafeCo $+              FieldMetadata (Proxy @"multiFunField3") FieldLazy+          ]+      }++{-------------------------------------------------------------------------------+  Beam-like mixin table++  This is the simpler case, because it contains only Columnar fields.+-------------------------------------------------------------------------------}++data MixinTable (f :: Type -> Type) = MkMixinTable {+      mixinTableField1 :: Columnar f Char+    , mixinTableField2 :: Columnar f Double+    }++exampleMixinTable :: MixinTable Identity+exampleMixinTable = MkMixinTable {+      mixinTableField1 = 'a'+    , mixinTableField2 = 3.14+    }++deriving instance ( Show (Columnar f Char)+                  , Show (Columnar f Double)+                  ) => Show (MixinTable f)+deriving instance ( Eq (Columnar f Char)+                  , Eq (Columnar f Double)+                  ) => Eq (MixinTable f)++class    (c (Columnar f Char), c (Columnar f Double)) => ConstraintsMixinTable f c+instance (c (Columnar f Char), c (Columnar f Double)) => ConstraintsMixinTable f c++instance Generic (MixinTable f) where+  type Constraints (MixinTable f) = ConstraintsMixinTable f+  type MetadataOf  (MixinTable f) = '[ '("mixinTableField1", Columnar f Char)+                                     , '("mixinTableField2", Columnar f Double)+                                     ]++  from MkMixinTable{..} = Rep.unsafeFromListAny [+        I $ noInlineUnsafeCo mixinTableField1+      , I $ noInlineUnsafeCo mixinTableField2+      ]++  to rep =+    let { [ I (noInlineUnsafeCo -> mixinTableField1)+          , I (noInlineUnsafeCo -> mixinTableField2)+          ] = Rep.toListAny rep }+    in MkMixinTable{..}++  dict :: forall c.+       ConstraintsMixinTable f c+    => Proxy c -> Rep (Dict c) (MixinTable f)+  dict _ = Rep.unsafeFromListAny [+        noInlineUnsafeCo (Dict :: Dict c (Columnar f Char))+      , noInlineUnsafeCo (Dict :: Dict c (Columnar f Double))+      ]++  metadata _ = Metadata {+        recordName          = "MixinTable"+      , recordConstructor   = "MkMixinTable"+      , recordSize          = 2+      , recordFieldMetadata = Rep.unsafeFromListAny [+            noInlineUnsafeCo $+              FieldMetadata (Proxy @"mixinTableField1") FieldLazy+          , noInlineUnsafeCo $+              FieldMetadata (Proxy @"mixinTableField2") FieldLazy+          ]+      }++{-------------------------------------------------------------------------------+  Beam-like full table example+-------------------------------------------------------------------------------}++data FullTable (f :: Type -> Type) = MkFullTable {+      fullTableField1 :: PrimaryKey FullTable f+    , fullTableField2 :: Columnar f Bool+    , fullTableField3 :: MixinTable f+    }++data instance PrimaryKey FullTable f = PrimA (Columnar f Int)++deriving instance Show (Columnar f Int) => Show (PrimaryKey FullTable f)+deriving instance Eq   (Columnar f Int) => Eq   (PrimaryKey FullTable f)++deriving instance ( Show (Columnar f Int)+                  , Show (Columnar f Bool)+                  , Show (Columnar f Char)+                  , Show (Columnar f Double)+                  ) => Show (FullTable f)+deriving instance ( Eq (Columnar f Int)+                  , Eq (Columnar f Bool)+                  , Eq (Columnar f Char)+                  , Eq (Columnar f Double)+                  ) => Eq (FullTable f)++class    ( c (PrimaryKey FullTable f)+         , c (Columnar f Bool)+         , c (MixinTable f)+         ) => ConstraintsFullTable f c+instance ( c (PrimaryKey FullTable f)+         , c (Columnar f Bool)+         , c (MixinTable f)+         ) => ConstraintsFullTable f c++instance Generic (FullTable f) where+  type Constraints (FullTable f) = ConstraintsFullTable f+  type MetadataOf  (FullTable f) = '[ '("fullTableField1", PrimaryKey FullTable f)+                                    , '("fullTableField2", Columnar f Bool)+                                    , '("fullTableField3", MixinTable f)+                                    ]++  from MkFullTable{..} = Rep.unsafeFromListAny [+        I $ noInlineUnsafeCo fullTableField1+      , I $ noInlineUnsafeCo fullTableField2+      , I $ noInlineUnsafeCo fullTableField3+      ]++  to rep =+    let { [ I (noInlineUnsafeCo -> fullTableField1)+          , I (noInlineUnsafeCo -> fullTableField2)+          , I (noInlineUnsafeCo -> fullTableField3)+          ] = Rep.toListAny rep }+    in MkFullTable{..}++  dict :: forall c.+       ConstraintsFullTable f c+    => Proxy c -> Rep (Dict c) (FullTable f)+  dict _ = Rep.unsafeFromListAny [+        noInlineUnsafeCo (Dict :: Dict c (PrimaryKey FullTable f))+      , noInlineUnsafeCo (Dict :: Dict c (Columnar f Bool))+      , noInlineUnsafeCo (Dict :: Dict c (MixinTable f))+      ]++  metadata _ = Metadata {+        recordName          = "FullTable"+      , recordConstructor   = "MkFullTable"+      , recordSize          = 3+      , recordFieldMetadata = Rep.unsafeFromListAny [+            noInlineUnsafeCo $+              FieldMetadata (Proxy @"fullTableField1") FieldLazy+          , noInlineUnsafeCo $+              FieldMetadata (Proxy @"fullTableField2") FieldLazy+          , noInlineUnsafeCo $+              FieldMetadata (Proxy @"fullTableField3") FieldLazy+          ]+      }
+ test/Test/Record/Generic/Infra/Util.hs view
@@ -0,0 +1,28 @@+module Test.Record.Generic.Infra.Util (+    expectException+  ) where++import Control.Exception+import Test.Tasty.HUnit++-- | Only used internally in 'expectException'+data Result =+    NoException+  | ExpectedException+  | UnexpectedException SomeException++expectException :: (SomeException -> Bool) -> Assertion -> Assertion+expectException p k = do+    result <- handle (return . aux) (k >> return NoException)+    case result of+      ExpectedException ->+        return ()+      NoException ->+        assertFailure $ "Expected exception, but none was raised"+      UnexpectedException e ->+        assertFailure $ "Raised exception does not match predicate: " ++ show e+  where+    aux :: SomeException -> Result+    aux e | p e       = ExpectedException+          | otherwise = UnexpectedException e+
+ test/Test/Record/Generic/Prop/Show.hs view
@@ -0,0 +1,16 @@+module Test.Record.Generic.Prop.Show (tests) where++import Data.Record.Generic.Show (gshowsPrec)++import Test.Tasty+import Test.Tasty.QuickCheck++import Test.Record.Generic.Infra.Examples++tests :: TestTree+tests = testGroup "Test.Record.Generic.Prop.Show" [+      testProperty "show" prop_show+    ]++prop_show :: SimpleRecord -> Property+prop_show ex = show ex === gshowsPrec 0 ex ""
+ test/Test/Record/Generic/Prop/ToFromJSON.hs view
@@ -0,0 +1,21 @@+module Test.Record.Generic.Prop.ToFromJSON (tests) where++import Data.Aeson.Types (parseEither)++import Data.Record.Generic.JSON++import Test.Tasty+import Test.Tasty.QuickCheck++import Test.Record.Generic.Infra.Examples++tests :: TestTree+tests = testGroup "Test.Record.Prop.ToFromJSON" [+      testProperty "tofromJSON" prop_tofromJSON+    ]++-- | Test that gtoJSON and gfromJSON are inverse+prop_tofromJSON :: SimpleRecord -> Property+prop_tofromJSON ex =+      counterexample (show (gtoJSON ex))+    $ Right ex === parseEither gparseJSON (gtoJSON ex)
+ test/Test/Record/Generic/Sanity/GhcGenerics.hs view
@@ -0,0 +1,126 @@+{-# LANGUAGE FlexibleContexts     #-}+{-# LANGUAGE FlexibleInstances    #-}+{-# LANGUAGE ScopedTypeVariables  #-}+{-# LANGUAGE TypeApplications     #-}+{-# LANGUAGE TypeOperators        #-}+{-# LANGUAGE UndecidableInstances #-}++{-# OPTIONS_GHC -Wno-orphans #-}++module Test.Record.Generic.Sanity.GhcGenerics (tests) where++import Data.Function (on)+import Data.Proxy+import Data.SOP.BasicFunctors+import Generics.Deriving.Eq (GEq'(..), geqdefault)++import qualified GHC.Generics as GHC++import Test.Tasty+import Test.Tasty.HUnit++import Data.Record.Generic.GHC++import qualified Data.Record.Generic     as L+import qualified Data.Record.Generic.Eq  as L (geq)+import qualified Data.Record.Generic.Rep as Rep++import Test.Record.Generic.Infra.Examples++{-------------------------------------------------------------------------------+  Show that we can use geqdefault on a large record+-------------------------------------------------------------------------------}++instance ( L.Generic a+         , L.Constraints a Eq+         ) => GEq' (ThroughLRGenerics a) where+  geq' = L.geq `on` unwrapThroughLRGenerics++allEqualTo :: (GHC.Generic a, GEq' (GHC.Rep a)) => a -> [a] -> Bool+allEqualTo x = all (geqdefault x)++{-------------------------------------------------------------------------------+  Example with GHC field metadata+-------------------------------------------------------------------------------}++class GRecordToTable f where+  gRecordToTable :: f p -> [(String, String)]++instance GRecordToTable f+      => GRecordToTable (GHC.M1 GHC.D c f) where+  gRecordToTable (GHC.M1 x) = gRecordToTable x++instance GRecordToTable f+      => GRecordToTable (GHC.M1 GHC.C c f) where+  gRecordToTable (GHC.M1 x) = gRecordToTable x++instance (GRecordToTable f, GRecordToTable g)+      => GRecordToTable (f GHC.:*: g) where+  gRecordToTable (l GHC.:*: r) = gRecordToTable l ++ gRecordToTable r++instance (GHC.Selector f, Show a)+      => GRecordToTable (GHC.M1 GHC.S f (GHC.K1 GHC.R a)) where+  gRecordToTable f@(GHC.M1 (GHC.K1 x)) = [(GHC.selName f, show x)]++data Table = Table {+      tableFields :: [(String, String)]+    }+  deriving (Show, Eq)++ghcRecordToTable :: (GHC.Generic a, GRecordToTable (GHC.Rep a)) => a -> Table+ghcRecordToTable = Table . gRecordToTable . GHC.from++-- The goal is to reuse the instance for fields+-- TODO: We could potentially extend this to the other metadata as well+largeRecordToTable :: forall a.+     (L.Generic a, L.Constraints a Show)+  => a -> Table+largeRecordToTable = \x ->+    Table {+        tableFields = concat . Rep.collapse $+            Rep.czipWith+              (Proxy @Show)+              aux+              (L.from x)+              (ghcMetadataFields (ghcMetadata (Proxy @a)))+      }+  where+    aux :: Show x => I x -> GhcFieldMetadata x -> K [(String, String)] x+    aux (I x) (GhcFieldMetadata p) = K $ gRecordToTable $ aux' x p++    aux' :: x -> Proxy f -> GHC.M1 GHC.S f (GHC.K1 GHC.R x) p+    aux' x _ = GHC.M1 (GHC.K1 x)++{-------------------------------------------------------------------------------+  Tests proper+-------------------------------------------------------------------------------}++tests :: TestTree+tests = testGroup "Test.Record.Generic.Sanity.GhcGenerics" [+      testCase "allEqualTo"          test_allEqualTo+    , testCase "simpleRecordToTable" test_simpleRecordToTable+    , testCase "largeRecordToTable"  test_largeRecordToTable+    ]++test_allEqualTo :: Assertion+test_allEqualTo =+    assertEqual "" True $+      allEqualTo exampleSimpleRecord [exampleSimpleRecord]++-- Just a sanity check that the standard GHC generic functions works as intended+test_simpleRecordToTable :: Assertion+test_simpleRecordToTable =+    assertEqual "" expectedTable $+      ghcRecordToTable exampleSimpleRecord++test_largeRecordToTable :: Assertion+test_largeRecordToTable =+    assertEqual "" expectedTable $+      largeRecordToTable exampleSimpleRecord++expectedTable :: Table+expectedTable = Table [+      ("simpleRecordField1", "5")+    , ("simpleRecordField2", "True")+    ]+
+ test/Test/Record/Generic/Sanity/Laziness.hs view
@@ -0,0 +1,151 @@+-- {-# LANGUAGE ConstraintKinds           #-}+-- {-# LANGUAGE DataKinds                 #-}+-- {-# LANGUAGE ExistentialQuantification #-}+-- {-# LANGUAGE FlexibleContexts          #-}+-- {-# LANGUAGE FlexibleInstances         #-}+-- {-# LANGUAGE MultiParamTypeClasses     #-}+{-# LANGUAGE ScopedTypeVariables       #-}+-- {-# LANGUAGE TemplateHaskell           #-}+{-# LANGUAGE TypeApplications          #-}+-- {-# LANGUAGE TypeFamilies              #-}+{-# LANGUAGE TypeOperators             #-}+-- {-# LANGUAGE UndecidableInstances      #-}+++-- | Check that the functions on 'Rep' can be called on 'undefined'+module Test.Record.Generic.Sanity.Laziness (tests) where++import Control.Exception+import Data.List (isInfixOf)+import Data.IORef+import Test.Tasty+import Test.Tasty.HUnit++import Data.Record.Generic++import qualified Data.Record.Generic.Rep          as Rep+import qualified Data.Record.Generic.Rep.Internal as Rep++import Test.Record.Generic.Infra.Examples+import Test.Record.Generic.Infra.Util (expectException)++{-------------------------------------------------------------------------------+  Tests proper+-------------------------------------------------------------------------------}++tests :: TestTree+tests = testGroup "Test.Record.Generic.Sanity.Laziness" [+      testCase "mapWithIndex" test_mapWithIndex+    , testCase "ap"           test_ap+    , testCase "map"          test_map+    , testCase "map'"         test_map'+    , testCase "mapM"         test_mapM+    , testCase "cmap"         test_cmap+    , testCase "cmapM"        test_cmapM+    , testCase "zipWithM"     test_zipWithM+    , testCase "czipWithM"    test_czipWithM+    ]++test_mapWithIndex :: Assertion+test_mapWithIndex =+    assertEqual "" expected actual+  where+    expected, actual :: Rep (K Int) SimpleRecord+    expected = Rep.unsafeFromList [0, 1]+    actual   = Rep.mapWithIndex (\ix _ -> K $ Rep.indexToInt ix) undefined++test_ap :: Assertion+test_ap =+    assertEqual "" expected actual+  where+    fns :: Rep (f -.-> I) SimpleRecord+    fns = Rep.map' (\x -> Fn $ \_ -> x) (from exampleSimpleRecord)++    expected, actual :: SimpleRecord+    expected = exampleSimpleRecord+    actual   = to $ Rep.ap fns undefined++test_map :: Assertion+test_map =+    assertEqual "" expected actual+  where+    expected, actual :: Rep (K Int) SimpleRecord+    expected = Rep.unsafeFromList [0, 0]+    actual   = Rep.map (\_ -> K 0) undefined++-- Just to be sure: if we use map' instead of map, we get bottom+test_map' :: Assertion+test_map' = expectException isExpectedException $ do+    assertEqual "" expected actual+  where+    isExpectedException :: SomeException -> Bool+    isExpectedException e = "undefined" `isInfixOf` show e++    expected, actual :: Rep (K Int) SimpleRecord+    expected = Rep.unsafeFromList [0, 0]+    actual   = Rep.map' (\_ -> K 0) undefined++test_mapM :: Assertion+test_mapM = do+    r <- newIORef 1++    let next :: f x -> IO (K Int x)+        next _ = atomicModifyIORef r $ \i -> (i + 1, K i)++    actual :: Rep (K Int) SimpleRecord <- Rep.mapM next undefined+    assertEqual "" expected actual+  where+    expected :: Rep (K Int) SimpleRecord+    expected = Rep.unsafeFromList [1, 2]++test_cmap :: Assertion+test_cmap =+    assertEqual "" expected actual+  where+    expected, actual :: SimpleRecord+    expected = MkSimpleRecord {+                   simpleRecordField1 = 0+                 , simpleRecordField2 = False+                 }+    actual   = to $ Rep.cmap (Proxy @Bounded) (\_ -> I minBound) undefined++test_cmapM :: Assertion+test_cmapM = do+    r <- newIORef False++    let next :: Bounded x => f x -> IO (I x)+        next _ = do+            b <- atomicModifyIORef r $ \b -> (not b, b)+            return . I $ if b then maxBound else minBound++    actual :: SimpleRecord <- to <$> Rep.cmapM (Proxy @Bounded) next undefined+    assertEqual "" expected actual+  where+    expected :: SimpleRecord+    expected = MkSimpleRecord {+                   simpleRecordField1 = 0+                 , simpleRecordField2 = True+                 }++test_zipWithM :: Assertion+test_zipWithM =+    assertEqual "" expected actual+  where+    expected, actual :: Maybe (Rep (K Int) SimpleRecord)+    expected = Just $ Rep.unsafeFromList [0, 0]+    actual   = Rep.zipWithM (\_ _ -> Just $ K 0) undefined undefined++test_czipWithM :: Assertion+test_czipWithM =+    assertEqual "" expected actual+  where+    expected, actual :: Maybe SimpleRecord+    expected = Just $ MkSimpleRecord {+                   simpleRecordField1 = 0+                 , simpleRecordField2 = False+                 }+    actual   = to <$> Rep.czipWithM+                        (Proxy @Bounded)+                        (\_ _ -> Just $ I minBound)+                        undefined+                        undefined
+ test/Test/Record/Generic/Sanity/Lens/VL.hs view
@@ -0,0 +1,246 @@+{-# LANGUAGE FlexibleContexts          #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE ScopedTypeVariables       #-}+{-# LANGUAGE TypeApplications          #-}+{-# LANGUAGE TypeFamilies              #-}++{-# OPTIONS_GHC -Wno-missing-signatures #-}++module Test.Record.Generic.Sanity.Lens.VL (tests) where++import Data.Char (toUpper)+import Data.Functor.Identity+import Data.Maybe (fromJust)+import Data.Proxy+import Data.SOP+import Lens.Micro (Lens', (^.), (&), (%~))++import Test.Tasty+import Test.Tasty.HUnit++import Data.Record.Generic+import Data.Record.Generic.Lens.VL+import Data.Record.Generic.SOP+import Data.Record.Generic.Transform++import qualified Data.Record.Generic.Rep as Rep++import Test.Record.Generic.Infra.Examples+import Test.Record.Generic.Infra.Beam.Interpretation+import Test.Record.Generic.Infra.Beam.Mini++{-------------------------------------------------------------------------------+  Simple example (no type families)+-------------------------------------------------------------------------------}++regularLenses :: Regular (RegularRecordLens Regular f)+regularLenses = lensesForRegularRecord (Proxy @DefaultInterpretation)++MkRegular {+      regularField1 = RegularRecordLens regularLens1+    , regularField2 = RegularRecordLens regularLens2+    , regularField3 = RegularRecordLens regularLens3+    } = regularLenses++{-------------------------------------------------------------------------------+  Example with type families, but still regular+-------------------------------------------------------------------------------}++mixinTableLenses :: MixinTable (RegularRecordLens MixinTable Identity)+mixinTableLenses = lensesForRegularRecord (Proxy @BeamInterpretation)++MkMixinTable {+      mixinTableField1 = RegularRecordLens mixinTableLens1+    , mixinTableField2 = RegularRecordLens mixinTableLens2+    } = mixinTableLenses++{-------------------------------------------------------------------------------+  Irregular example+-------------------------------------------------------------------------------}++-- We cannot define this now:+--+-- > irregularLenses :: Irregular (RegularRecordLens Irregular I)+-- > irregularLenses = lensesForRegularRecord (Proxy @DefaultInterpretation)+--+-- It will complain that @String@ is not equal to+--+-- > Interpreted (DefaultInterpretation (RegularRecordLens Irregular I)) String+--+-- We can use 'repLenses' to nonetheless get lenses for all fields in+-- 'Irregular', and then translate to an NP so that we can pattern match on it+-- in a type-safe way. Of course, the translation to SOP incurs O(N^2)+-- compile-time cost so this is not a proper solution.+--+-- NOTE: There is not much point using 'repLenses'' here; that is primarily+-- useful only if there is some post-processing step (like done by+-- 'lensesForRegularRecord').+irregularLenses :: NP (Field (SimpleRecordLens (Irregular f)))+                      (MetadataOf (Irregular f))+irregularLenses = fromJust $ toSOP rep+  where+    rep :: Rep (SimpleRecordLens (Irregular f)) (Irregular f)+    rep = lensesForSimpleRecord++-- Unlike the beam tutorial, we match to get these lenses in two steps: first,+-- we get 'SimpleRecordLens' out, which does not rely on impredicativity;+-- then we get the Van Laarhoven lenses out in three separate bindings. This+-- avoids problems with ghc type inference which gets very confused by that+-- pattern match.++irregularLens1' :: SimpleRecordLens (Irregular f) (f Int)+irregularLens2' :: SimpleRecordLens (Irregular f) (f Bool)+irregularLens3' :: SimpleRecordLens (Irregular f) String++(    Field irregularLens1'+  :* Field irregularLens2'+  :* Field irregularLens3'+  :* Nil ) = irregularLenses++irregularLens1 :: Lens' (Irregular f) (f Int)+irregularLens2 :: Lens' (Irregular f) (f Bool)+irregularLens3 :: Lens' (Irregular f) String++SimpleRecordLens irregularLens1 = irregularLens1'+SimpleRecordLens irregularLens2 = irregularLens2'+SimpleRecordLens irregularLens3 = irregularLens3'++{-------------------------------------------------------------------------------+  Beam-like example (using the 'Lenses' indirection).++  This still does not support all beam features; in particular, this only works+  for the regular 'MixinTable', not for the complete 'FullTable'. To do that, we+  would need to introduce a separate type class (instead of 'IsRegularField')+  that then needs to be available for every field, so that we can distinguish+  between mixins and normal ('Columnar') fields. For a completely worked out+  exmaple, see the @beam-large-records@ package+  <https://github.com/well-typed/beam-large-records>.+-------------------------------------------------------------------------------}++beamLikeLenses :: forall tbl.+     ( Generic (tbl (Lenses tbl Identity))+     , Generic (tbl Uninterpreted)+     , Generic (tbl Identity)+     , HasNormalForm (BeamInterpretation (Lenses tbl Identity)) (tbl (Lenses tbl Identity)) (tbl Uninterpreted)+     , HasNormalForm (BeamInterpretation Identity) (tbl Identity) (tbl Uninterpreted)+     , Constraints (tbl Uninterpreted) (IsRegularField Uninterpreted)+     )+  => tbl (Lenses tbl Identity)+beamLikeLenses =+    to . denormalize1 (Proxy @BeamInterpretation) $+      Rep.cmap+        (Proxy @(IsRegularField Uninterpreted))+        aux+        (lensesForHKRecord (Proxy @BeamInterpretation))+  where+    aux :: forall x.+         IsRegularField Uninterpreted x+      => HKRecordLens BeamInterpretation Identity tbl x+      -> Interpret (BeamInterpretation (Lenses tbl Identity)) x+    aux (HKRecordLens l) =+        case isRegularField (Proxy @(Uninterpreted x)) of+          RegularField -> Interpret $ WrapLens $+              l+            . standardInterpretationLens (Proxy @BeamInterpretation)+            . unI'++    unI' :: Lens' (Identity x) x+    unI' f (Identity x) = Identity <$> f x++mixinBeamLikeLenses :: MixinTable (Lenses MixinTable Identity)+mixinBeamLikeLenses = beamLikeLenses++MkMixinTable {+      mixinTableField1 = WrapLens mixinBeamLikeLens1+    , mixinTableField2 = WrapLens mixinBeamLikeLens2+    } = mixinBeamLikeLenses++{-------------------------------------------------------------------------------+  Tests proper+-------------------------------------------------------------------------------}++tests :: TestTree+tests = testGroup "Test.Record.Generic.Sanity.Lens.VL" [+      testCase "regular_get"   test_regular_get+    , testCase "regular_set"   test_regular_set+    , testCase "mixin_get"     test_mixin_get+    , testCase "mixin_set"     test_mixin_set+    , testCase "irregular_get" test_irregular_get+    , testCase "irregular_set" test_irregular_set+    , testCase "beamlike_get"  test_beamlike_get+    , testCase "beamlike_set"  test_beamlike_set+    ]++test_regular_get :: Assertion+test_regular_get =+    assertEqual "" (I True)+      (exampleRegular ^. regularLens2)++test_regular_set :: Assertion+test_regular_set =+    assertEqual "" expected $+        exampleRegular+      & regularLens1 %~ mapII negate+      & regularLens3 %~ mapII (map toUpper)+  where+    expected :: Regular I+    expected = MkRegular {+          regularField1 = I (-5)+        , regularField2 = I True+        , regularField3 = I "A"+        }++test_mixin_get :: Assertion+test_mixin_get =+    assertEqual "" 3.14+      (exampleMixinTable ^. mixinTableLens2)++test_mixin_set :: Assertion+test_mixin_set =+    assertEqual "" expected $+        exampleMixinTable+      & mixinTableLens1 %~ succ+      & mixinTableLens2 %~ negate+  where+    expected :: MixinTable Identity+    expected = MkMixinTable {+          mixinTableField1 = 'b'+        , mixinTableField2 = -3.14+        }++test_irregular_get :: Assertion+test_irregular_get =+    assertEqual "" (I True)+      (exampleIrregular ^. irregularLens2)++test_irregular_set :: Assertion+test_irregular_set =+    assertEqual "" expected $+        exampleIrregular+      & irregularLens1 %~ mapII negate+      & irregularLens3 %~ map toUpper+  where+    expected :: Irregular I+    expected = MkIrregular {+          irregularField1 = I (-1234)+        , irregularField2 = I True+        , irregularField3 = "HI"+        }++test_beamlike_get :: Assertion+test_beamlike_get =+    assertEqual "" 3.14+      (exampleMixinTable ^. mixinBeamLikeLens2)++test_beamlike_set :: Assertion+test_beamlike_set =+    assertEqual "" expected $+        exampleMixinTable+      & mixinBeamLikeLens1 %~ succ+      & mixinBeamLikeLens2 %~ negate+  where+    expected :: MixinTable Identity+    expected = MkMixinTable {+          mixinTableField1 = 'b'+        , mixinTableField2 = -3.14+        }
+ test/Test/Record/Generic/Sanity/Rep.hs view
@@ -0,0 +1,169 @@+{-# LANGUAGE FlexibleContexts    #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications    #-}+{-# LANGUAGE TypeOperators       #-}++module Test.Record.Generic.Sanity.Rep (tests) where++import Control.Monad.State (State, evalState, state)+import Data.SOP (NP(..), All, Compose)++import qualified Data.SOP as SOP++import Test.Tasty+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck++import Data.Record.Generic+import Data.Record.Generic.LowerBound+import Data.Record.Generic.SOP hiding (glowerBound)++import qualified Data.Record.Generic.SOP as SOP+import qualified Data.Record.Generic.Rep as Rep++import Test.Record.Generic.Infra.Examples++{-------------------------------------------------------------------------------+  For testing purposes, we compare against proper heterogeneous lists+-------------------------------------------------------------------------------}++compareTyped ::+     forall f a. (+       Generic a+     , Constraints a (Compose Eq f)+     , Constraints a (Compose Show f)+     , All IsField (MetadataOf a)+     )+  => NP (Field f) (MetadataOf a) -> Rep f a -> Assertion+compareTyped expected actual =+    case toSOP actual of+      Nothing ->+        assertFailure "compareTyped: incorrect number of fields"+      Just actual' ->+        case toDictAll (Proxy @f) (Proxy @a) (Proxy @Show) of+          Dict ->+            case toDictAll (Proxy @f) (Proxy @a) (Proxy @Eq) of+              Dict -> go expected actual'+  where+    go :: ( All (Compose Eq   (Field f)) fields+          , All (Compose Show (Field f)) fields+          )+       => NP (Field f) fields -> NP (Field f) fields -> Assertion+    go = assertEqual "compareTyped"++{-------------------------------------------------------------------------------+  Tests+-------------------------------------------------------------------------------}++test_pure :: Assertion+test_pure =+    compareTyped expected actual+  where+    expected :: NP (Field (K Char)) (MetadataOf (ParamRecord () Float))+    expected =+           Field (K 'a')+        :* Field (K 'a')+        :* Field (K 'a')+        :* Field (K 'a')+        :* Field (K 'a')+        :* Nil++    actual :: Rep (K Char) (ParamRecord () Float)+    actual = Rep.pure (K 'a')++test_cpure :: Assertion+test_cpure =+    assertEqual "matches hand-constructed" expected actual+  where+    expected, actual :: ParamRecord () Float+    expected = MkParamRecord 0 False '\x0000' () []+    actual   = glowerBound++test_sequenceA :: Assertion+test_sequenceA =+    compareTyped expected actual+  where+    expected :: NP (Field (K Int)) (MetadataOf (ParamRecord () Float))+    expected =+          flip evalState 0+        $ SOP.hsequence'+        $ SOP.hmap distrib+        $ example+      where+        distrib :: Field (State Int :.: K Int) x+                -> (State Int :.: (Field (K Int))) x+        distrib (Field (Comp x)) = Comp (Field <$> x)++    actual :: Rep (K Int) (ParamRecord () Float)+    actual = flip evalState 0 $ Rep.sequenceA $ SOP.fromSOP example++    example :: NP (Field (State Int SOP.:.: K Int)) (MetadataOf (ParamRecord () Float))+    example =+           Field (Comp (K <$> tick))+        :* Field (Comp (K <$> tick))+        :* Field (Comp (K <$> tick))+        :* Field (Comp (K <$> tick))+        :* Field (Comp (K <$> tick))+        :* Nil++    tick :: State Int Int+    tick = state $ \i -> (i, i + 1)++test_zipWithM :: Assertion+test_zipWithM =+    compareTyped expected actual+  where+    expected :: NP (Field (K Int)) (MetadataOf (ParamRecord () Float))+    expected =+          flip evalState 0+        $ SOP.hsequence'+        $ SOP.hliftA2 tick' x y+      where+        tick' :: Field (K Int) field+              -> Field (K Int) field+              -> (State Int :.: Field (K Int)) field+        tick' (Field a) (Field b) = Comp $ Field <$> tick a b++    actual :: Rep (K Int) (ParamRecord () Float)+    actual = flip evalState 0 $+        Rep.zipWithM tick (fromSOP x) (fromSOP y)++    tick :: K Int x -> K Int x -> State Int (K Int x)+    tick (K a) (K b) = state $ \i -> (K (a + b + i), i + 1)++    x, y :: NP (Field (K Int)) (MetadataOf (ParamRecord () Float))+    x = Field (K 10)+     :* Field (K 11)+     :* Field (K 12)+     :* Field (K 13)+     :* Field (K 14)+     :* Nil+    y = Field (K 20)+     :* Field (K 21)+     :* Field (K 22)+     :* Field (K 23)+     :* Field (K 24)+     :* Nil++test_ord :: Word -> Word -> Bool -> Bool -> Property+test_ord w w' b b'+  | w == w' && b == b' = t1 === t2+  | w == w'            = compare t1 t2 === compare b b'+  | otherwise          = compare t1 t2 === compare w w'+  where+    t1, t2 :: ParamRecord () Float+    t1 = MkParamRecord w  b  'c' () [3.14]+    t2 = MkParamRecord w' b' 'c' () [3.14]++{-------------------------------------------------------------------------------+  All tests+-------------------------------------------------------------------------------}++tests :: TestTree+tests = testGroup "Test.Record.Generic.Sanity.Rep" [+      testCase     "pure"       test_pure+    , testCase     "cpure"      test_cpure+    , testCase     "sequenceA"  test_sequenceA+    , testCase     "zipWithM"   test_zipWithM+    , testProperty "ord"        test_ord+    ]
+ test/Test/Record/Generic/Sanity/Transform.hs view
@@ -0,0 +1,227 @@+{-# LANGUAGE DataKinds             #-}+{-# LANGUAGE FlexibleContexts      #-}+{-# LANGUAGE FlexibleInstances     #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RankNTypes            #-}+{-# LANGUAGE ScopedTypeVariables   #-}+{-# LANGUAGE TypeApplications      #-}+{-# LANGUAGE TypeFamilies          #-}+{-# LANGUAGE UndecidableInstances  #-}++{-# OPTIONS_GHC -Wno-orphans #-}++module Test.Record.Generic.Sanity.Transform (tests) where++import Data.Functor.Identity+import Data.Kind+import Data.Proxy+import Data.SOP.BasicFunctors+import GHC.TypeLits (Nat)++import Test.Tasty+import Test.Tasty.HUnit++import Data.Record.Generic+import Data.Record.Generic.Transform++import qualified Data.Record.Generic.Rep as Rep+import qualified Generics.SOP            as SOP++import Test.Record.Generic.Infra.Beam.Interpretation+import Test.Record.Generic.Infra.Beam.Mini+import Test.Record.Generic.Infra.Examples++{-------------------------------------------------------------------------------+  Motivating example using SOP+-------------------------------------------------------------------------------}++class CanInject x y where+  inject :: x -> y++instance CanInject (I x) (Maybe x) where+  inject (I x) = Just x++instance CanInject String String where+  inject = id++_gjust_SOP :: forall x fields fields'.+     ( SOP.IsProductType (x I)     fields+     , SOP.IsProductType (x Maybe) fields'+     , SOP.AllZip CanInject fields fields'+     )+  => x I -> x Maybe+_gjust_SOP =+    SOP.productTypeTo . aux . SOP.productTypeFrom+  where+    aux :: SOP.NP I fields -> SOP.NP I fields'+    aux = SOP.htrans (Proxy @CanInject) (fmap inject)++{-------------------------------------------------------------------------------+  Simple example+-------------------------------------------------------------------------------}++type instance Interpreted (DefaultInterpretation f) String = String++class InjectInterpreted f g a where+  injectInterpreted ::+       Interpret (DefaultInterpretation f) a+    -> Interpret (DefaultInterpretation g) a++instance InjectInterpreted I Maybe (Uninterpreted a) where+  injectInterpreted = liftInterpreted $ \(I x) -> Just x++instance InjectInterpreted I Maybe String where+  injectInterpreted = liftInterpreted $ id++-- | Generic injection, using LR generics+--+-- The type annotations are just to explain the flow, they are not required+-- for type inference.+gjust :: forall x (f :: Type -> Type) (g :: Type -> Type).+     ( Generic (x f)+     , Generic (x g)+     , Generic (x Uninterpreted)+     , Constraints (x Uninterpreted) (InjectInterpreted f g)+     , HasNormalForm (DefaultInterpretation f) (x f) (x Uninterpreted)+     , HasNormalForm (DefaultInterpretation g) (x g) (x Uninterpreted)+     )+  => x f -> x g+gjust =+      (to+         :: Rep I (x g)+         -> x g)+    . (denormalize1 (Proxy @DefaultInterpretation)+         :: Rep (Interpret (DefaultInterpretation g)) (x Uninterpreted)+         -> Rep I (x g))+    . (Rep.cmap (Proxy @(InjectInterpreted f g)) injectInterpreted+         :: Rep (Interpret (DefaultInterpretation f)) (x Uninterpreted)+         -> Rep (Interpret (DefaultInterpretation g)) (x Uninterpreted))+    . (normalize1 (Proxy @DefaultInterpretation)+         :: Rep I (x f)+         -> Rep (Interpret (DefaultInterpretation f)) (x Uninterpreted))+    . (from+         :: x f+         -> Rep I (x f))++justIrregular :: Irregular I -> Irregular Maybe+justIrregular = gjust++{-------------------------------------------------------------------------------+  Example with two variables+-------------------------------------------------------------------------------}++data Skolem (n :: Nat) x++data DefInt2 (f :: Type -> Type) (g :: Type -> Type)++type instance Interpreted (DefInt2 f g) (Skolem 0 x) = f x+type instance Interpreted (DefInt2 f g) (Skolem 1 x) = g x+type instance Interpreted (DefInt2 f g) String       = String++class SwapInterpreted f g a where+  swapInterpreted ::+       Interpret (DefInt2 f g) a+    -> Interpret (DefInt2 g f) a++instance SwapInterpreted I Identity (Skolem 0 x) where+  swapInterpreted = liftInterpreted $ \(I x) -> Identity x++instance SwapInterpreted I Identity (Skolem 1 y) where+  swapInterpreted = liftInterpreted $ \(Identity x) -> I x++instance SwapInterpreted f g String where+  swapInterpreted = liftInterpreted $ id++gswap :: forall x (f :: Type -> Type) (g :: Type -> Type).+     ( Generic (x f g)+     , Generic (x g f)+     , Generic (x (Skolem 0) (Skolem 1))+     , Constraints (x (Skolem 0) (Skolem 1)) (SwapInterpreted f g)+     , HasNormalForm (DefInt2 f g) (x f g) (x (Skolem 0) (Skolem 1))+     , HasNormalForm (DefInt2 g f) (x g f) (x (Skolem 0) (Skolem 1))+     )+  => x f g -> x g f+gswap =+      to+    . denormalize (Proxy @(DefInt2 g f)) (Proxy @(x (Skolem 0) (Skolem 1)))+    . Rep.cmap (Proxy @(SwapInterpreted f g)) swapInterpreted+    . normalize (Proxy @(DefInt2 f g)) (Proxy @(x (Skolem 0) (Skolem 1)))+    . from++swapMultiFun :: MultiFun I Identity -> MultiFun Identity I+swapMultiFun = gswap++{-------------------------------------------------------------------------------+  Beam test+-------------------------------------------------------------------------------}++instance Beamable (PrimaryKey FullTable) where+  -- The GHC.Generics instance would normally be fine for primary keys+  zipBeamFieldsM f (PrimA x) (PrimA y) = PrimA <$>+      applyColumnar' (Proxy @Int) f x y++instance Beamable FullTable  where zipBeamFieldsM = gzipBeam+instance Beamable MixinTable where zipBeamFieldsM = gzipBeam++instance Table FullTable++{-------------------------------------------------------------------------------+  Tests+-------------------------------------------------------------------------------}++tests :: TestTree+tests = testGroup "Test.Record.Generic.Sanity.Transform" [+      testCase "gjust"    test_gjust+    , testCase "gswap"    test_gswap+    , testCase "gzipBeam" test_gzipBeam+    ]++test_gjust :: Assertion+test_gjust =+    assertEqual ""+      (justIrregular $ MkIrregular (I    5) (I    True) "hi")+      (                MkIrregular (Just 5) (Just True) "hi")++test_gswap :: Assertion+test_gswap =+    assertEqual ""+      (swapMultiFun $ MkMultiFun (I        5) (Identity True) "hi")+      (               MkMultiFun (Identity 5) (I        True) "hi")++data Pair x = Pair x x+  deriving (Show, Eq)++test_gzipBeam :: Assertion+test_gzipBeam =+    assertEqual ""+      (unI (zipBeamFieldsM pairup parentTable parentTable))+      parentTable'+  where+    pairup :: Columnar' I x -> Columnar' I x -> I (Columnar' Pair x)+    pairup (Columnar' (I x)) (Columnar' (I y)) = I (Columnar' $ Pair x y)++    parentTable :: FullTable I+    parentTable = MkFullTable {+          fullTableField1 = PrimA (I 5)+        , fullTableField2 = I True+        , fullTableField3 = mixinTable+        }++    mixinTable :: MixinTable I+    mixinTable = MkMixinTable {+          mixinTableField1 = I 'x'+        , mixinTableField2 = I 3.14+        }++    parentTable' :: FullTable Pair+    parentTable' = MkFullTable {+          fullTableField1 = PrimA (Pair 5 5)+        , fullTableField2 = Pair True True+        , fullTableField3 = mixinTable'+        }++    mixinTable' :: MixinTable Pair+    mixinTable' = MkMixinTable {+          mixinTableField1 = Pair 'x' 'x'+        , mixinTableField2 = Pair 3.14 3.14+        }
+ test/TestLargeGenerics.hs view
@@ -0,0 +1,22 @@+module Main where++import Test.Tasty++import qualified Test.Record.Generic.Prop.Show+import qualified Test.Record.Generic.Prop.ToFromJSON+import qualified Test.Record.Generic.Sanity.GhcGenerics+import qualified Test.Record.Generic.Sanity.Laziness+import qualified Test.Record.Generic.Sanity.Lens.VL+import qualified Test.Record.Generic.Sanity.Rep+import qualified Test.Record.Generic.Sanity.Transform++main :: IO ()+main = defaultMain $ testGroup "TestLargeGenerics" [+      Test.Record.Generic.Sanity.Rep.tests+    , Test.Record.Generic.Sanity.Transform.tests+    , Test.Record.Generic.Sanity.GhcGenerics.tests+    , Test.Record.Generic.Sanity.Laziness.tests+    , Test.Record.Generic.Sanity.Lens.VL.tests+    , Test.Record.Generic.Prop.Show.tests+    , Test.Record.Generic.Prop.ToFromJSON.tests+    ]