-
Notifications
You must be signed in to change notification settings - Fork 1
Description
TLDR; It would be nice to be able to specify a non-database-level type in an HGraph and have an equivalent value of a different type be actually inserted into the database.
Long Version
Sometimes the database-level representation of a value is difficult to use or fails to properly constrain its inhabitants. For example, suppose we have a database table with two nullable fields, but at the application level we expect to always have values for both fields or neither field:
share [mkPersist sqlSettings, mkMigrate "migration"] [persistLowerCase|
Raw sql=foo
bar Maybe Int
baz Maybe Int
|]
By default, Raw would look like this in Haskell:
data Raw = Raw { rawBar :: Maybe Int, rawBaz :: Maybe Int }However, this admits more values than we want. We might prefer this representation:
newtype Nice = Nice (Maybe (Int, Int))Then we can use a type family and some other machinery...
type family RawEntity r :: *
data Model record
= Model
{ modelKey :: Key (RawEntity record)
, modelVal :: record
}
-- Horrible lawless typeclass forgive meeee
class IsModel record where
fromRaw :: RawEntity record -> record
toRaw :: record -> RawEntity record
fromRawEntity
:: (PersistEntity (RawEntity record), IsModel record)
=> Entity (RawEntity record)
-> Model record
fromRawEntity (Entity key val) = Model key (fromRaw val)
toRawEntity
:: (PersistEntity (RawEntity record), IsModel record)
=> Model record
-> Entity (RawEntity record)
toRawEntity (Model key val) = Entity key (toRaw val)... to convert between an Entity Raw and a Model Nice:
type instance RawEntity Nice = Raw
instance IsModel Nice where
fromRaw (Raw mBar mBaz) = Nice (liftA2 (,) mBar mBaz)
toRaw (Nice mPair) = Raw (fst <$> mPair) (snd <$> mPair)
nice = fromRawEntity (Entity someRawId (Raw Nothing Nothing))
raw = toRawEntity (PseudoEntity someRawId (Nice Nothing))Anyhoo, I made a half-hearted attempt to write an InsertElement instance for something like this:
instance
(IsModel record, RawEntity record ~ raw, InsertElement raw (Entity raw) backend baseBackend) =>
InsertElement record (Model record) backend baseBackend where
insertElement model = do
Entity key raw <- insertElement $ toRaw model
return $ Model key $ fromRaw rawBut that blows up with a fundeps conflict because the other instance we use looks like this:
instance
(PersistEntity a, PersistEntityBackend a ~ baseBackend, BaseBackend backend ~ baseBackend, PersistStoreWrite backend) =>
InsertElement a (Entity a) backend baseBackend where ...In InsertElement a b backend baseBackend, a is supposed to uniquely determine b, and I'm trying to add an instance where b is Model a for a, but we've already fixed b as Entity a for a.