44{-# LANGUAGE BlockArguments #-}
55{-# LANGUAGE ViewPatterns #-}
66{-# LANGUAGE TypeFamilies #-}
7- module Rel8.TH (deriveRel8able ) where
8-
9- import Prelude ((.) , pure , (<$>) , ($) , fail , map , id , (==) , (<>) , show , last , error , otherwise )
10- import Language.Haskell.TH (Name , Q , Dec , conT , Type (AppT , ConT , VarT , TupleT ), newName , conP , varP , nameBase , conE , varE , appsE , TyVarBndr (.. ), varT , tupleT )
11- import Language.Haskell.TH.Datatype (reifyDatatype , ConstructorInfo (ConstructorInfo ), DatatypeInfo (DatatypeInfo ), datatypeCons , constructorFields , ConstructorVariant (RecordConstructor ), constructorVariant , constructorName , datatypeVars )
7+ {-# LANGUAGE PolyKinds #-}
8+ {-# LANGUAGE TypeApplications #-}
9+ {-# LANGUAGE RecordWildCards #-}
10+ module Rel8.TH (deriveRel8able , parseDatatype ) where
11+
12+ import Prelude
13+ import Language.Haskell.TH (Q )
14+ import qualified Language.Haskell.TH as TH
15+ import qualified Language.Haskell.TH.Syntax as TH
16+ import Language.Haskell.TH.Datatype (reifyDatatype , ConstructorInfo (ConstructorInfo ), DatatypeInfo (.. ), datatypeCons , constructorFields , ConstructorVariant (RecordConstructor ), constructorVariant , constructorName , datatypeVars )
17+ import qualified Language.Haskell.TH.Datatype as TH.Datatype
1218import Rel8.Generic.Rel8able ( Rel8able (.. ) )
1319import Rel8.Schema.Result (Result )
1420import Data.Foldable (foldl' , toList )
@@ -21,84 +27,191 @@ import Data.Functor ( (<&>) )
2127import Data.List.NonEmpty ( NonEmpty ( (:|) ) )
2228import Rel8.Column ( Column )
2329import Rel8.Expr ( Expr )
24- import Rel8.Table ( Columns )
25-
26- deriveRel8able :: Name -> Q [Dec ]
27- deriveRel8able name = do
28- DatatypeInfo { datatypeVars = (last -> fBinder), datatypeCons = [ ConstructorInfo { constructorName, constructorFields = f1: fs, constructorVariant = RecordConstructor (fieldName1: fieldNames) } ]} <- reifyDatatype name
29-
30- let f = case fBinder of
31- PlainTV a _ -> a
32- KindedTV a _ _ -> a
33-
34- contextName <- newName " context"
35- name1 <- newName $ nameBase fieldName1
36- names <- for fieldNames $ newName . nameBase
37-
38- let allNames = name1 :| names
39-
40- let
41- unpackP =
42- foldl'
43- (\ e n -> [p | HProduct $e (HIdentity $( varP n )) |])
44- [p | HIdentity $( varP name1 ) |]
45- names
46-
47- unmk (x :| xs) =
48- foldl'
49- (\ e n -> [| HProduct $ e (HIdentity $ n) | ])
50- [| HIdentity $ x | ]
51- xs
52-
53- mk xs = appsE (conE constructorName : toList xs)
54-
55- id
56- [d | instance Rel8able $( conT name ) where
57- type GColumns $( conT name) =
58- $(
59- foldl'
60- (\t x -> [t| HProduct $t $(unColumn f x) |])
61- (unColumn f f1)
62- fs
63- )
64-
65- type GFromExprs $ ( conT name ) =
66- $ ( conT name ) Result
67-
68- gfromColumns $ ( varP contextName ) $ unpackP =
69- case $ ( varE contextName ) of
70- SExpr -> $ ( mk $ varE <$> allNames )
71- SField -> $ ( mk $ varE <$> allNames )
72- SName -> $ ( mk $ varE <$> allNames )
73- SResult -> $ ( mk $ allNames <&> \ x -> [| runIdentity $ ( varE x ) | ] )
74-
75- gtoColumns $ (varP contextName) $ ( conP constructorName (map varP (name1: names)) ) =
76- case $ ( varE contextName ) of
77- SExpr -> $ ( unmk $ varE <$> allNames )
78- SField -> $ ( unmk $ varE <$> allNames )
79- SName -> $ ( unmk $ varE <$> allNames )
80- SResult -> $ ( unmk $ allNames <&> \ x -> [| Identity $ ( varE x ) | ] )
81-
82- gfromResult $ unpackP =
83- $ ( mk $ allNames <&> \ x -> [| runIdentity $ ( varE x ) | ] )
84-
85- gtoResult $ ( conP constructorName (map varP (name1: names)) ) =
86- $ ( unmk $ allNames <&> \ x -> [| Identity $ ( varE x ) | ] )
87- | ]
88-
89-
90- unColumn :: Name -> Type -> Q Type
91- unColumn _ (AppT (AppT (ConT _Column) _f) t) | _Column == ''Column = [t | HIdentity $(pure t) |]
92- unColumn f t = [t | Columns $(instantiate t) |]
30+ import Rel8.Table (Columns , toColumns , fromColumns , fromResult , toResult , FromExprs )
31+ import Rel8.Schema.Kind (Context )
32+ import Data.List (unsnoc )
33+ import Debug.Trace
34+ import Rel8.Schema.HTable.Label (HLabel (.. ))
35+ import Data.Data (constrFields )
36+ import Data.Aeson (parseIndexedJSON )
37+
38+
39+ -- We derive a Rel8able instance using TH.
40+ -- At it's core a Rel8able instance is a bijection between a datatype and the the SQL columns corresponding to its fields.
41+ -- We only support datatypes with one constructor.
42+ -- The datatype must have exactly one type arg and it is the index for our HKD stuff.
43+ -- Question: Can we support multiple type args?
44+ ---
45+ -- We have three types of fields:
46+ -- 1) Column f Text : Directly using Column, easy. This is just a special case of (3)
47+ -- 2) OtherType f : They embed another Rel8able type
48+ -- 3) TabledType : They embed a type with a table instance.
49+ -- eg, we might see something like (Column f Text, Column f Bool). (,) has a Table instance,
50+ -- so we know how to map this type to SQL columns.
51+ --
52+ -- We represent a vector of SQL columns with basically:
53+ -- HLabel "field label" (HIdentity Text) `HProduct` HLabel "another field" (HIdentity Bool) ...
54+ -- Nothing too complicated here. I'm not sure if we are allowed to leave the HLabels out or if that will cause everything to explode.
55+ -- This H* stuff is also used to thread around contexts if you look at the definitions of these things
56+
57+ data ParsedDatatype =
58+ ParsedDatatype
59+ { name :: TH. Name
60+ , conName :: TH. Name
61+ , fBinder :: TH. Name
62+ , fields :: [ParsedField ]
63+ }
64+ deriving (Show )
65+
66+ data ParsedField =
67+ ParsedField
68+ { fieldSelector :: Maybe TH. Name
69+ , fieldVariant :: ParsedFieldVariant
70+ , fieldType :: TH. Type
71+ , fieldColumnType :: TH. Type
72+ , fieldFreshName :: TH. Name
73+ }
74+ deriving (Show )
75+
76+ data ParsedFieldVariant =
77+ ColumnField
78+ | Rel8ableField
79+ deriving (Show )
80+
81+ -- | 'fail' but indicate that the failure is coming from our code
82+ prettyFail :: String -> Q a
83+ prettyFail str = fail $ " deriveRel8able: " ++ str
84+
85+ parseDatatype :: DatatypeInfo -> Q ParsedDatatype
86+ parseDatatype datatypeInfo = do
87+ constructor <-
88+ -- Check that it only has one constructor
89+ case datatypeCons datatypeInfo of
90+ [cons] -> pure cons
91+ _ -> prettyFail " exepecting a datatype with exactly 1 constructor"
92+ let conName = TH.Datatype. constructorName constructor
93+ let name = datatypeName datatypeInfo
94+ fBinder <- case unsnoc $ datatypeInstTypes datatypeInfo of
95+ Just (_, candidate) -> parseFBinder candidate
96+ Nothing -> prettyFail " expecting the datatype to have a context type parameter like `data Foo f = ...`"
97+ let fieldSelectors = case constructorVariant constructor of
98+ -- Only record constructors have field names
99+ RecordConstructor names -> map Just names
100+ _ -> repeat Nothing
101+ let columnName = ''Column
102+ fields <-
103+ mapM (uncurry $ parseField columnName fBinder) $
104+ zip (constructorFields constructor) fieldSelectors
105+ -- TODO: check that we have at least one field, fail otherwise
106+ pure ParsedDatatype {.. }
107+
108+ parseFBinder :: TH. Type -> Q TH. Name
109+ parseFBinder (TH. SigT x (TH. ConT kind))
110+ | kind == ''Context = parseFBinder x
111+ | otherwise = prettyFail $ " expected kind encountered for the context type argument: " ++ show kind
112+ parseFBinder (TH. VarT name) = pure name
113+ parseFBinder typ = prettyFail $ " unexpected type encountered while looking for the context type argument to the datatype: " ++ show typ
114+
115+ parseField :: TH. Name -> TH. Name -> TH. Type -> Maybe TH. Name -> Q ParsedField
116+ parseField columnName fBinder fieldType fieldSelector
117+ | (TH. ConT columnCandidate `TH. AppT ` TH. VarT fBinderCandidate `TH. AppT ` subType) <- fieldType
118+ , columnCandidate == columnName
119+ , fBinderCandidate == fBinder
120+ = do
121+ n <- TH. newName " x"
122+ pure $ ParsedField { fieldSelector = fieldSelector, fieldVariant = ColumnField , fieldType = subType, fieldColumnType = TH. ConT ''HIdentity `TH. AppT ` subType, fieldFreshName = n}
123+ | (subType `TH. AppT ` TH. VarT name ) <- fieldType
124+ , name == fBinder
125+ -- actually might want [t|Columns ($(pure subType) Expr)|]?
126+ = do
127+ n <- TH. newName " x"
128+ columnType <- [t |Columns ($(pure subType) Expr)|]
129+ pure $ ParsedField { fieldSelector = fieldSelector, fieldVariant = Rel8ableField , fieldType = subType, fieldColumnType = columnType, fieldFreshName = n}
130+ | otherwise = prettyFail $ " Field of unexpected type: " ++ show fieldType
131+
132+ generateGColumns :: ParsedDatatype -> Q TH. Type
133+ generateGColumns ParsedDatatype {.. } =
134+ foldr1 (\ x y -> [t |HProduct $x $y|]) $ map generateGColumn fields
93135 where
94- instantiate = \ case
95- VarT v | v == f -> [t | Expr |]
96- | otherwise -> varT v
97-
98- AppT x y -> [t | $(instantiate x) $(instantiate y) |]
99-
100- TupleT n -> tupleT n
101-
102- ConT n -> conT n
136+ generateGColumn ParsedField {.. } =
137+ [t | $(pure fieldColumnType)|]
138+ >>= labelled fieldSelector
139+ labelled Nothing x = pure x
140+ labelled (Just (TH. Name (TH. OccName fieldSelector) _)) x = [t |HLabel $(TH.litT $ TH.strTyLit fieldSelector) $(pure x)|]
141+
142+ generateColumnsE :: ParsedDatatype -> (TH. Exp -> TH. Exp ) -> (TH. Type -> TH. Exp -> TH. Exp ) -> TH. Exp
143+ generateColumnsE ParsedDatatype {.. } f g =
144+ foldr1 (\ x y -> TH. ConE 'HProduct `TH. AppE ` x `TH. AppE ` y) $ map generateColumnE fields
145+ where
146+ generateColumnE ParsedField {.. } =
147+ labelled fieldSelector $
148+ case fieldVariant of
149+ ColumnField -> TH. ConE 'HIdentity `TH. AppE ` (f $ TH. VarE fieldFreshName)
150+ Rel8ableField -> (g fieldType $ TH. VarE fieldFreshName)
151+ labelled Nothing x = x
152+ labelled (Just _) x = TH. ConE 'HLabel `TH. AppE `x
153+
154+ generateColumnsP :: ParsedDatatype -> TH. Pat
155+ generateColumnsP ParsedDatatype {.. } =
156+ foldr1 (\ x y -> TH. ConP 'HProduct [] [x, y]) $ map generateColumnP fields
157+ where
158+ generateColumnP ParsedField {.. } =
159+ labelled fieldSelector $
160+ case fieldVariant of
161+ ColumnField -> TH. ConP 'HIdentity [] [TH. VarP fieldFreshName]
162+ Rel8ableField -> TH. VarP fieldFreshName
163+ labelled Nothing x = x
164+ labelled (Just _) x = TH. ConP 'HLabel [] [x]
165+
166+ generateConstructorE :: ParsedDatatype -> (TH. Exp -> TH. Exp ) -> (TH. Type -> TH. Exp -> TH. Exp ) -> Q TH. Exp
167+ generateConstructorE parsedDatatype f g =
168+ pure $ foldl' TH. AppE (TH. ConE (conName parsedDatatype)) . map generateFieldE $ fields parsedDatatype
169+ where
170+ generateFieldE ParsedField {.. } =
171+ case fieldVariant of
172+ ColumnField -> f . TH. VarE $ fieldFreshName
173+ Rel8ableField -> g fieldType . TH. VarE $ fieldFreshName
103174
104- other -> error $ show other
175+ deriveRel8able :: TH. Name -> Q [TH. Dec ]
176+ deriveRel8able name = do
177+ datatypeInfo <- reifyDatatype name
178+ parsedDatatype <- parseDatatype datatypeInfo
179+ let gColumns = generateGColumns parsedDatatype
180+ let constructorE = generateConstructorE parsedDatatype
181+ let constructorP = pure $ TH. ConP (conName parsedDatatype) [] . map (TH. VarP . fieldFreshName) $ fields parsedDatatype
182+ let columnsE f g = pure $ generateColumnsE parsedDatatype f g
183+ let columnsP = pure $ generateColumnsP parsedDatatype
184+ contextName <- TH. newName " context"
185+ [d |
186+ instance Rel8able $(TH.conT name) where
187+ -- Really the Generic code substitutes Expr for f and then does stuff. Maybe we want to move closer to that?
188+ type GColumns $( TH.conT name) =
189+ $( gColumns )
190+
191+ type GFromExprs $( TH.conT name ) =
192+ $( TH.conT name ) Result
193+
194+ -- the rest of the definition is just a few functions to go back and forth between Columns and the datatype
195+
196+ gfromColumns $( TH.varP contextName ) $(columnsP)=
197+ case $( TH.varE contextName ) of
198+ SResult -> $(constructorE (\x -> TH.VarE 'runIdentity `TH.AppE` x) (\_ x -> TH.VarE 'fromColumns `TH.AppE` x))
199+ SExpr -> $(constructorE id (\_ x -> TH.VarE 'fromColumns `TH.AppE` x))
200+ SField -> $(constructorE id (\_ x -> TH.VarE 'fromColumns `TH.AppE` x))
201+ SName -> $(constructorE id (\_ x -> TH.VarE 'fromColumns `TH.AppE` x))
202+
203+ gtoColumns $(TH.varP contextName) $( constructorP ) =
204+ case $( TH.varE contextName ) of
205+ SExpr -> $(columnsE id (\_ x -> TH.VarE 'toColumns `TH.AppE` x))
206+ SField -> $(columnsE id (\_ x -> TH.VarE 'toColumns `TH.AppE` x))
207+ SName -> $(columnsE id (\_ x -> TH.VarE 'toColumns `TH.AppE` x))
208+ SResult -> $(columnsE (\x -> TH.ConE 'Identity `TH.AppE` x) (\_ x -> TH.VarE 'toColumns `TH.AppE` x))
209+
210+ gfromResult $columnsP =
211+ -- TODO: get rid of type application. Use a signature that references the generic value instead
212+ $( constructorE (\x -> TH.VarE 'runIdentity `TH.AppE` x) (\ft x -> (TH.VarE 'fromResult `TH.AppTypeE` TH.ConT ''Result `TH.AppTypeE` (ft `TH.AppT` TH.ConT ''Result) `TH.AppE` x)))
213+
214+ gtoResult $constructorP =
215+ -- TODO: get rid of type application. Use a signature that references the generic value instead
216+ $( columnsE (\x -> TH.ConE 'Identity `TH.AppE` x) (\ft x -> (TH.VarE 'toResult `TH.AppTypeE` TH.ConT ''Result `TH.AppTypeE` (ft `TH.AppT` TH.ConT ''Result) `TH.AppE` x)))
217+ |]
0 commit comments