packages feed

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")
        ]