How can I use HaskellDB with polymorphic fields? (Problems with overlapping instances)
Asked Answered
O

1

49

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!

Oversupply answered 12/11, 2011 at 14:49 Comment(2)
According to the user guide haskell.org/ghc/docs/latest/html/users_guide/… IncoherentInstances would need to be added to Database.HaskellDB.HDBRec to take effect.Disconsider
Specifically: The willingness to be overlapped or incoherent is a property of the instance declaration itself, controlled by the presence or otherwise of the -XOverlappingInstances and -XIncoherentInstances flags when that module is being defined.Disconsider
O
2

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.

Oehsen answered 9/7, 2012 at 6:34 Comment(4)
Not too late at all! This is simply some interesting hobby work, so there are no deadlines :) However, this doesn't seem to solve the problem. I don't want to define masterHead in terms of book as it's intended to be polymorphic on the entity type. There are other entities that also have masterHead (publishers, editions). The crux of this is that these entities types are intended to be defined outside the package with masterHead, as the masterHead stuff is to be provided from a basic in-house framework.Oversupply
@ocharles, so you want masterHead to take a parameter of the base table instead of using specifically bookRevision?Oehsen
Ok, looked again - yea, masterHead is also parametric because those other types also have the idea of 'masterHead'. I think what I also wanted was to do this without having to pass extra types in. It's the act of using masterHead in a larger equation which will finally determine its type.Oversupply
@ocharles, I think it is likely there is no way to do it without informing masterHead of the entity type in some way. There are a few more tricks that could be tried though, perhaps an existential type.Oehsen

© 2022 - 2024 — McMap. All rights reserved.