I'm testing some code for a little experiment I'm doing but right at the beginning I've hit a roadblock that I don't see how to fix.
data DatabaseDriver a b where
DatabaseDriver :: (Table table, Field field) => {
dbInsert :: table -> [field] -> String
, dbSelect :: table -> [field] -> String
} -> DatabaseDriver a b
class Table a where
tableName :: a -> String
class Field a where
fieldName :: a -> String
fieldValue :: a -> String
psqlDriver = DatabaseDriver insert select
where
insert t fs = "insert into " ++ tableName t ++ " (" ++ fieldNames fs ++ ") values (" ++ fieldValues fs ++ ")"
select t fs = "select " ++ fieldNames fs ++ " from " ++ tableName t
fieldNames = joinComma fieldName
fieldValues = joinComma fieldValue
joinComma f = foldl (\a n -> a ++ ", " ++ n) "" . map f
Ok, so this is some test code, the driver functions would get much more complicated than this but even in this test I get the error "Ambiguous type variable 'a0' in the constraint: (Field a0) arising from a use of `fieldName'. So the compiler does see that fieldName is applied to a field but apparently it wants a more concrete type here. I guess having the functions remain polymorphic makes pgsqlDriver not a concrete class?
But the idea would be that these functions are polymorphic. That's the reason I chose to use a GADT here, so I could put type constraints on the parameters to these driver functions without having to repeat them in every instantiation. The plan would be that the defined database driver could work with any Field and Table instances. Can this simply not be done and my DatabaseDriver type would also have to be a type class?
There are three options here. The first option is to add
{-# LANGUAGE NoMonomorphismRestriction #-}
to the top of your module file.
The second option is to add an explicit type signature to psqlDriver.
psqlDriver :: (Field field, Table table) => (table -> [field] -> String) -> DatabaseDriver a b
The reason for all of this is a little nuanced, and more details can be found here
The third option is to change the definition of psqlDriver
to
psqlDriver = DatabaseDriver insert select
However, this is truly ambiguous - there is not reason to prefer any particular instance of Table
or Field
over another. Perhaps you mean to define DatabaseDriver
as follows.
data DatabaseDriver table field where
DatabaseDriver :: (Table table, Field field) => {
dbInsert :: table -> [field] -> String
, dbSelect :: table -> [field] -> String
} -> DatabaseDriver table field
If the original definition of DatabaseDriver
is rewritten as an ADT, it is more obvious why.
As it is currently written in the question, the translation to an ADT is
{-# LANGUAGE ExistentialQuantification #-}
data DatabaseDriver a b
= forall table field .
(Table table, Field field) => DatabaseDriver
{ dbInsert :: table -> [field] -> String
, dbSelect :: table -> [field] -> String
}
Notice the nested forall table field
, and how table
and field
have no relationship to a
or b
.
The intended translation is either
data DatabaseDriver table field
= (Table table, Field field) => DatabaseDriver
{ dbInsert :: table -> [field] -> String
, dbSelect :: table -> [field] -> String
}
or most likely
data DatabaseDriver table field
= DatabaseDriver
{ dbInsert :: table -> [field] -> String
, dbSelect :: table -> [field] -> String
}
Having the type class constraints in the definition of DatabaseDriver
does not let you remove the type class constraints from any use of DatabaseDriver
, in particular psqlDriver
. In both of the ADT translations above, the type of psqlDriver
is
psqlDriver :: (Table table, Field field) => DatabaseDriver table field