@@ -6,7 +6,26 @@
6{-# LANGUAGE UndecidableInstances #-}
6{-# LANGUAGE UndecidableInstances #-}
7{-# LANGUAGE CPP #-}
7{-# LANGUAGE CPP #-}
9−module ProjectM36.Tupleable where
9+module ProjectM36.Tupleable
12+ , tupleAssocsEqualityPredicate
13+ , partitionByAttributes
22+ , genericToAttributes
25+ , defaultTupleableOptions
11import Data.Foldable
30import Data.Foldable
12import Data.List (partition)
31import Data.List (partition)
@@ -121,56 +140,109 @@ validateAttributes actualAttrs expectedAttrs val
122 nonMatchingAttrs = attributeNamesNotContained actualAttrs expectedAttrs
141 nonMatchingAttrs = attributeNamesNotContained actualAttrs expectedAttrs
124−
143+-- | Types that can be converted to and from 'RelationTuple'.
145+-- deriving without customization:
147+-- > data Example = Example
148+-- > { foo :: Integer
151+-- > deriving (Generic)
153+-- > instance Tupleable Example
155+-- deriving with customization using "ProjectM36.Tupleable.Deriving":
157+-- > data Example = Example
158+-- > { exampleFoo :: Integer
159+-- > , exampleBar :: Text
161+-- > deriving stock (Generic)
162+-- > deriving (Tupleable)
163+-- > via Codec (Field (DropPrefix "example" >>> CamelCase)) Example
125class Tupleable a where
164class Tupleable a where
126 toTuple :: a -> RelationTuple
165 toTuple :: a -> RelationTuple
128 fromTuple :: RelationTuple -> Either RelationalError a
167 fromTuple :: RelationTuple -> Either RelationalError a
130− toAttributes :: proxy a -> Attributes
169+ toAttributes :: Proxy a -> Attributes
132 default toTuple :: (Generic a, TupleableG (Rep a)) => a -> RelationTuple
171 default toTuple :: (Generic a, TupleableG (Rep a)) => a -> RelationTuple
133− toTuple v = toTupleG (from v)
172+ toTuple = genericToTuple defaultTupleableOptions
135 default fromTuple :: (Generic a, TupleableG (Rep a)) => RelationTuple -> Either RelationalError a
174 default fromTuple :: (Generic a, TupleableG (Rep a)) => RelationTuple -> Either RelationalError a
175+ fromTuple = genericFromTuple defaultTupleableOptions
177+ default toAttributes :: (Generic a, TupleableG (Rep a)) => Proxy a -> Attributes
178+ toAttributes = genericToAttributes defaultTupleableOptions
180+-- | Options that influence deriving behavior.
181+newtype TupleableOptions = TupleableOptions {
182+ -- | A function that translates record field names into attribute names.
183+ fieldModifier :: T.Text -> T.Text
186+-- | The default options for deriving Tupleable instances.
188+-- These options can be customized by using record update syntax. For example,
190+-- > defaultTupleableOptions
191+-- > { fieldModifier = \fieldName ->
192+-- > case Data.Text.stripPrefix "example" fieldName of
193+-- > Nothing -> fieldName
194+-- > Just attributeName -> attributeName
197+-- will result in record field names being translated into attribute names by
198+-- removing the prefix "example" from the field names.
199+defaultTupleableOptions :: TupleableOptions
200+defaultTupleableOptions = TupleableOptions {
204+genericToTuple :: (Generic a, TupleableG (Rep a)) => TupleableOptions -> a -> RelationTuple
205+genericToTuple opts v = toTupleG opts (from v)
207+genericFromTuple :: (Generic a, TupleableG (Rep a)) => TupleableOptions -> RelationTuple -> Either RelationalError a
136− fromTuple tup = to <$> fromTupleG tup
208+genericFromTuple opts tup = to <$> fromTupleG opts tup
138− default toAttributes :: (Generic a, TupleableG (Rep a)) => proxy a -> Attributes
210+genericToAttributes :: forall a. (Generic a, TupleableG (Rep a)) => TupleableOptions -> Proxy a -> Attributes
139− toAttributes _ = toAttributesG (from (undefined :: a))
211+genericToAttributes opts _ = toAttributesG opts (from (undefined :: a))
141class TupleableG g where
213class TupleableG g where
142− toTupleG :: g a -> RelationTuple
214+ toTupleG :: TupleableOptions -> g a -> RelationTuple
143− toAttributesG :: g a -> Attributes
215+ toAttributesG :: TupleableOptions -> g a -> Attributes
144− fromTupleG :: RelationTuple -> Either RelationalError (g a)
216+ fromTupleG :: TupleableOptions -> RelationTuple -> Either RelationalError (g a)
145 isRecordTypeG :: g a -> Bool
217 isRecordTypeG :: g a -> Bool
147--data type metadata
219--data type metadata
148instance (Datatype c, TupleableG a) => TupleableG (M1 D c a) where
220instance (Datatype c, TupleableG a) => TupleableG (M1 D c a) where
149− toTupleG (M1 v) = toTupleG v
221+ toTupleG opts (M1 v) = toTupleG opts v
150− toAttributesG (M1 v) = toAttributesG v
222+ toAttributesG opts (M1 v) = toAttributesG opts v
151− fromTupleG v = M1 <$> fromTupleG v
223+ fromTupleG opts v = M1 <$> fromTupleG opts v
152 isRecordTypeG (M1 v) = isRecordTypeG v
224 isRecordTypeG (M1 v) = isRecordTypeG v
154--constructor metadata
226--constructor metadata
155instance (Constructor c, TupleableG a, AtomableG a) => TupleableG (M1 C c a) where
227instance (Constructor c, TupleableG a, AtomableG a) => TupleableG (M1 C c a) where
156− toTupleG (M1 v) = RelationTuple attrs atoms
228+ toTupleG opts (M1 v) = RelationTuple attrs atoms
158− attrsToCheck = toAttributesG v
230+ attrsToCheck = toAttributesG opts v
159 counter = V.generate (V.length attrsToCheck) id
231 counter = V.generate (V.length attrsToCheck) id
160 attrs = V.zipWith (\num attr@(Attribute name typ) -> if T.null name then
232 attrs = V.zipWith (\num attr@(Attribute name typ) -> if T.null name then
161 Attribute ("attr" <> T.pack (show (num + 1))) typ
233 Attribute ("attr" <> T.pack (show (num + 1))) typ
163 attr) counter attrsToCheck
235 attr) counter attrsToCheck
164 atoms = V.fromList (toAtomsG v)
236 atoms = V.fromList (toAtomsG v)
165− toAttributesG (M1 v) = toAttributesG v
237+ toAttributesG opts (M1 v) = toAttributesG opts v
166− fromTupleG tup = M1 <$> fromTupleG tup
238+ fromTupleG opts tup = M1 <$> fromTupleG opts tup
167 isRecordTypeG (M1 v) = isRecordTypeG v
239 isRecordTypeG (M1 v) = isRecordTypeG v
169-- product types
241-- product types
170instance (TupleableG a, TupleableG b) => TupleableG (a :*: b) where
242instance (TupleableG a, TupleableG b) => TupleableG (a :*: b) where
171 toTupleG = error "toTupleG"
243 toTupleG = error "toTupleG"
172− toAttributesG ~(x :*: y) = toAttributesG x V.++ toAttributesG y --a bit of extra laziness prevents whnf so that we can use toAttributes (undefined :: Test2T Int Int) without throwing an exception
244+ toAttributesG opts ~(x :*: y) = toAttributesG opts x V.++ toAttributesG opts y --a bit of extra laziness prevents whnf so that we can use toAttributes (undefined :: Test2T Int Int) without throwing an exception
173− fromTupleG tup = (:*:) <$> fromTupleG tup <*> fromTupleG processedTuple
245+ fromTupleG opts tup = (:*:) <$> fromTupleG opts tup <*> fromTupleG opts processedTuple
175 processedTuple = if isRecordTypeG (undefined :: a x) then
247 processedTuple = if isRecordTypeG (undefined :: a x) then
@@ -181,18 +253,22 @@ instance (TupleableG a, TupleableG b) => TupleableG (a :*: b) where
181--selector/record
253--selector/record
182instance (Selector c, AtomableG a) => TupleableG (M1 S c a) where
254instance (Selector c, AtomableG a) => TupleableG (M1 S c a) where
183 toTupleG = error "toTupleG"
255 toTupleG = error "toTupleG"
184− toAttributesG m@(M1 v) = V.singleton (Attribute name aType)
256+ toAttributesG opts m@(M1 v) = V.singleton (Attribute modifiedName aType)
186 name = T.pack (selName m)
258 name = T.pack (selName m)
259+ modifiedName = if T.null name then
262+ fieldModifier opts name
187 aType = toAtomTypeG v
263 aType = toAtomTypeG v
188− fromTupleG tup = if null name then -- non-record type, just pull off the first tuple item
264+ fromTupleG opts tup = if null name then -- non-record type, just pull off the first tuple item
189 M1 <$> atomv (V.head (tupleAtoms tup))
265 M1 <$> atomv (V.head (tupleAtoms tup))
191− atom <- atomForAttributeName (T.pack name) tup
267+ atom <- atomForAttributeName (fieldModifier opts (T.pack name)) tup
192 val <- atomv atom
268 val <- atomv atom
193 pure (M1 val)
269 pure (M1 val)
195− expectedAtomType = atomType (V.head (toAttributesG (undefined :: M1 S c a x)))
271+ expectedAtomType = atomType (V.head (toAttributesG opts (undefined :: M1 S c a x)))
196 atomv atom = maybe (Left (AtomTypeMismatchError
272 atomv atom = maybe (Left (AtomTypeMismatchError
197 expectedAtomType
273 expectedAtomType
198 (atomTypeForAtom atom)
274 (atomTypeForAtom atom)
@@ -203,8 +279,8 @@ instance (Selector c, AtomableG a) => TupleableG (M1 S c a) where
203--constructors with no arguments
279--constructors with no arguments
204--basically useless but orthoganal to relationTrue
280--basically useless but orthoganal to relationTrue
205instance TupleableG U1 where
281instance TupleableG U1 where
206− toTupleG _= emptyTuple
282+ toTupleG _ _ = emptyTuple
207− toAttributesG _ = emptyAttributes
283+ toAttributesG _ _ = emptyAttributes
208− fromTupleG _ = pure U1
284+ fromTupleG _ _ = pure U1
209 isRecordTypeG _ = False
285 isRecordTypeG _ = False