-- | Typeclass and an instance generator for the typeclass to convert -- plain Haskell records to their vinyl representation. {-# LANGUAGE DataKinds #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilyDependencies #-} {-# LANGUAGE TypeOperators #-} module Frames.SQL.Beam.Postgres.Vinylize where import Data.Proxy import Data.Vinyl import qualified Data.Vinyl.Functor as VF import qualified Database.Beam as B import Frames.Col import Frames.SQL.Beam.Postgres.Helpers (fNamesTypeLevel) import Generics.SOP import qualified Generics.SOP.NP as GSN import GHC.TypeLits import Language.Haskell.TH -- | Type family that generates the column types for the vinyl representation. type family ZipTypes (ns :: [Symbol]) (ys :: [*]) = (zs :: [(Symbol, *)]) | zs -> ns ys type instance ZipTypes '[] '[] = '[] type instance ZipTypes (n ': ns) (y ': ys) = '( n, y) ': (ZipTypes ns ys) -- | Typeclass for converting a plain Haskell record to it's vinyl -- representation. class GenericVinyl a names rs | a -> names rs where type FieldNames a :: [Symbol] createRecId :: a -> Rec VF.ElField (ZipTypes names rs) -- | Helps generate an instance for @GenericVinyl@, given a plain -- Haskell record declaration name. Uses Template Haskell, so -- if, say, the record is named @MyRecord@, then first you must -- invoke @deriveGeneric ''MyRecord@ to get the Sum-of-Products (SOP) -- representation (imported from @generic-sop@) of the record in-scope, -- in the current module. This is followed by invoking -- @deriveVinyl ''MyRecord@, which makes use of the SOP representation -- of the plain record and generates a @GenericVinyl@ instance for the record. deriveVinyl :: Name -> DecsQ deriveVinyl name = entireInstance where n = conT name typeList1 = fNamesTypeLevel name entireInstance= [d| instance (((Code ($(n) B.Identity)) ~ '[rs]), (ns3 ~ FieldNames ($(n) B.Identity)) ) => GenericVinyl ($(n) B.Identity) ns3 rs where type FieldNames ($(n) B.Identity) = $(typeList1) createRecId r = withNames $ go transformedNP where SOP (Z prod) = from r transformedNP = GSN.fromI_NP prod go = GSN.cata_NP RNil (:&) |]