large-records-0.1.0.0: test/Test/Record/Sanity/GhcGenerics.hs
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -Wno-orphans #-}
-- {-# OPTIONS_GHC -ddump-splices #-}
module Test.Record.Sanity.GhcGenerics (tests) where
import Data.Function (on)
import Data.Proxy
import Data.Record.Generic.GHC
import Data.Record.TH
import Data.SOP.BasicFunctors
import Test.Tasty
import Test.Tasty.HUnit
import qualified Data.Record.Generic as LR
import qualified Data.Record.Generic.Eq as LR
import qualified Data.Record.Generic.Rep as Rep
import qualified Generics.Deriving.Eq as GHC
import qualified GHC.Generics as GHC
{-------------------------------------------------------------------------------
Example large record
-------------------------------------------------------------------------------}
largeRecord defaultPureScript [d|
data LargeRecord = MkLargeRecord {
largeField1 :: Int
, largeField2 :: Bool
}
|]
example :: LargeRecord
example = [lr| MkLargeRecord |] 1 True
{-------------------------------------------------------------------------------
Show that we can use geqdefault on a large record
-------------------------------------------------------------------------------}
instance ( LR.Generic a
, LR.Constraints a Eq
) => GHC.GEq' (ThroughLRGenerics a) where
geq' = LR.geq `on` unwrapThroughLRGenerics
allEqualTo :: (GHC.Generic a, GHC.GEq' (GHC.Rep a)) => a -> [a] -> Bool
allEqualTo x = all (GHC.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)
simpleRecordToTable :: (GHC.Generic a, GRecordToTable (GHC.Rep a)) => a -> Table
simpleRecordToTable = Table . gRecordToTable . GHC.from
data SimpleRecord = MkSimpleRecord {
simpleField1 :: Int
, simpleField2 :: Bool
}
deriving (GHC.Generic)
-- The goal is to reuse the instance for fields
-- TODO: We could potentially extend this to the other metadata as well
largeRecordToTable :: forall a.
(LR.Generic a, LR.Constraints a Show)
=> a -> Table
largeRecordToTable = \x ->
Table {
tableFields = concat . Rep.collapse $
Rep.czipWith
(Proxy @Show)
aux
(LR.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.Sanity.GhcGenerics" [
testCase "allEqualTo" test_allEqualTo
, testCase "simpleRecordToTable" test_simpleRecordToTable
, testCase "largeRecordToTable" test_largeRecordToTable
]
test_allEqualTo :: Assertion
test_allEqualTo = assertEqual "" (allEqualTo example [example]) True
-- Just a sanity check that the standard GHC generic functions works as intended
test_simpleRecordToTable :: Assertion
test_simpleRecordToTable =
assertEqual "" (simpleRecordToTable r) tbl
where
r :: SimpleRecord
r = MkSimpleRecord 1 True
tbl :: Table
tbl = Table [
("simpleField1", "1")
, ("simpleField2", "True")
]
test_largeRecordToTable :: Assertion
test_largeRecordToTable =
assertEqual "" (largeRecordToTable example) tbl
where
tbl :: Table
tbl = Table [
("largeField1", "1")
, ("largeField2", "True")
]