How can I use HaskellDB with polymorphic fields? (Problems with overlapping instances)
I have a schema which has 6 different types of entities, but they all have a lot of things in common. I figured I could probably abstract a lot of this commonality out at the type level, but I've hit a problem with HaskellDB and overlapping instances. Here's the code I started with, which works fine:
import Database.HaskellDB
import Database.HaskellDB.DBLayout
data Revision a = Revision deriving Eq
data Book = Book
instance FieldTag (Revision a) where
fieldName _ = "rev_id"
revIdField :: Attr (Revision Book) (Revision Book)
revIdField = mkAttr undefined
branch :: Table (RecCons (Revision Book) (Expr (Revision Book)) RecNil)
branch = baseTable "branch" $ hdbMakeEntry undefined
bookRevision :: Table (RecCons (Revision Book) (Expr (Revision Book)) RecNil)
bookRevision = baseTable "book_revision" $ hdbMakeEntry undefined
masterHead :: Query (Rel (RecCons (Revision Book) (Expr (Revision Book)) RecNil))
masterHead = do
revisions <- table bookRevision
branches <- table branch
restrict $ revisions ! revIdField .==. branches ! revIdField
return revisions
This works fine, but branch
is too specific. What I actually want to express is the following:
branch :: Table (RecCons (Revision entity) (Expr (Revision entity)) RecNil)
branch = baseTable "branch" $ hdbMakeEntry undefined
However, with this change, I get the following error:
Overlapping instances for HasField
(Revision Book)
(RecCons (Revision entity0) (Expr (Revision entity0)) RecNil)
arising from a use of `!'
Matching instances:
instance [overlap ok] HasField f r => HasField f (RecCons g a r)
-- Defined in Database.HaskellDB.HDBRec
instance [overlap ok] HasField f (RecCons f a r)
-- Defined in Database.HaskellDB.HDBRec
(The choice depends on the instantiation of `entity0'
To pick the first instance above, use -XIncoherentInstances
when compiling the other instance declarations)
In the second argument of `(.==.)', namely `branches ! revIdField'
In the second argument of `($)', namely
`revisions ! revIdField .==. branches ! revIdField'
In a stmt of a 'do' expression:
restrict $ revisions ! revIdField .==. branches ! revIdField
I've tried blindly throwing -XOverlappingInstances
and -XIncoherentInstances
at this, but that doesn't help (and I'd like to actually understand why replacing a concrete type with a type variable causes this to be so problematic).
Any help and advice would be much appreciated!
Solution 1:
With the age of this question, it is probably too late of an answer to make any difference to you, but maybe if some other person comes along with a similar problem...
It comes down to the fact that it cannot be inferred that you want entity
to refer to Book
when branch
is used in masterHead
. The part of the error message that reads
The choice depends on the instantiation of `entity0'
tells you where you need to remove the ambiguity, specifically that you need to give more information about what entity0
should be. You can give some type annotations to help things out.
First, define branch
as
type BranchTable entity = Table (RecCons (Revision entity) (Expr (Revision entity)) RecNil)
branch :: BrancTable entity
branch = baseTable "branch" $ hdbMakeEntry undefined
and then change masterHead
to read
masterHead :: Query (Rel (RecCons (Revision Book) (Expr (Revision Book)) RecNil))
masterHead = do
revisions <- table bookRevision
branches <- table (branch :: BranchTable Book)
restrict $ revisions ! revIdField .==. branches ! revIdField
return revisions
Note the type annotation applied to branch
: branch :: BranchTable Book
which serves to remove the ambiguity that was causing the type error.
To make masterHead
applicable to anything with a Revision e
field in it, you can use this definition:
masterHead :: (ShowRecRow r, HasField (Revision e) r) => Table r -> e -> Query (Rel r)
masterHead revTable et =
do revisions <- table revTable
branches <- table branch'
restrict $ revisions ! revIdField' .==. branches ! revIdField'
return revisions
where (branch', revIdField') = revBundle revTable et
revBundle :: HasField (Revision e) r => Table r -> e -> (BranchTable e, Attr (Revision e) (Revision e))
revBundle table et = (branch, revIdField)
The et
argument is needed to specify what the e
type should be and can just be undefined
ascribed to the proper type as in
masterHead bookRevision (undefined :: Book)
which generates the SQL
SELECT rev_id1 as rev_id
FROM (SELECT rev_id as rev_id2
FROM branch as T1) as T1,
(SELECT rev_id as rev_id1
FROM book_revision as T1) as T2
WHERE rev_id1 = rev_id2
this does require FlexibleContexts
though, but it can be applied to the asker's module without recompiling HaskellDB.