I'm writing a SQL combinator which allows SQL fragments to be composed as a Monoid. I have roughly a type like this (this is a simplified implementation) :
data SQLFragment = { selects :: [String], froms :[String], wheres :: [String]}
instance Monoid SQL Fragment where ...
This allows to me to combine easily bits of SQL I use often and do things like :
email = select "email" <> from "user"
name = select "name" <> from "user"
administrators = from "user" <> where_ "isAdmin = 1"
toSql $ email <> name <> administrators
=> "SELECT email, name FROM user WHERE isAdmin = 1"
That works very well and I'm happy with it.
Now I use MySQL.Simple
and to be executed it needs to know the type of a row.
main = do
conn <- SQL.connect connectInfo
rows <- SQL.query_ conn $ toSql (email <> name <> administrators)
forM_ (rows :: [(String, String)]) print
Which is why I need the
rows :: [(String, String)]
To avoid to have add manually this explicit (and useless) type signature I had the following idea :
I add a phantom type to my SQLFragment
and use it to force the type of the query_
function. So I could have something like this
email = select "email" <> from "user" :: SQLFragment String
name = select "name" <> from "user" :: SQLFragment String
administrators = from "user" <> where_ "isAdmin = 1" :: SQLFragment ()
etc ...
Then I can do
query_ :: SQL.Connection -> SQLFragment a -> IO [a]
query_ con q = SQL.query_ conn (toSql q)
My first problem is I can't use <>
anymore because SQLFragment a
is not a Monoid
anymore.
The second is how do I implement my new <>
to compute correctly the phantom type ?
I found a way which I think is ugly and I hope there is a much better solution.
I created a typed version
of SQLFragment
and use phantom attribute which is a HList
.
data TQuery e = TQuery
{ fragment :: SQLFragment
, doNotUse :: e
}
then I create a new typed
operator : !<>!
which I don't undestand the type signature so I don't write it
(TQuery q e) !<>! (TQuery q' e') = TQuery (q<>q') (e.*.e')
Now I can't combine my typed fragment and keep track of the type (even though it's not yet a tuple but something really weird).
To convert this weird type to a tuple I create a type family :
type family Result e :: *
and instantiate it for some tuples
Another solution would be probably to use a type family and write manually every combination of tuples
type instance Result (HList '[a]) = (SQL.Only a)
type instance Result (HList '[HList '[a], b]) = (a, b)
type instance Result (HList '[HList '[HList '[a], b], c]) = (a, b, c)
type instance Result (HList '[HList '[HList '[HList '[a], b], c], d]) = (a, b, c, d)
type instance Result (HList '[HList '[HList '[HList '[HList '[a], b], c], d], e]) = (a, b, c,d, e)
etc ...
And that works. I can write my function using the Result
family
execute :: (SQL.QueryResults (Result e)) =>
SQL.Connection -> TQuery e -> SQL.Connection -> IO [Result e]
execute conn (TQuery q _ ) = SQL.query_ conn (toSql q)
My main program looks like :
email = TQuery (select "email" <> from "user") ((undefined :: String ) .*. HNil)
name = TQuery (select "name" <> from "user" ) ((undefined :: String ) .*. HNil)
administrators = TQuery (from "user" <> where_ "isAdmin = 1") (HNil)
main = do
conn <- SQL.connect connectInfo
rows <- execute conn $ email !<>! name !<>! administrators
forM_ rows print
and it works!
However is there a better way to do it , especially without using HList
and if possible less extensions as possible ?
If I "hide" somehow the phantom type (so I can have a real Monoid
and be able to use <>
instead of !<>!
) is there a way to get the type back?
strQuery :: Connection -> Query -> IO [(String, String)]
;strQuery = SQL.query_
, much like writing a functionreadInt :: String -> Int
;readInt = read
? If you're always getting the same return type back, or just one of a handful of types, then this approach should be pretty manageable and doesn't require any fancy plumbing to work. Otherwise I see no problem with having to specify the type inline. – Liberal(TQuery q e) !<>! (TQuery q' e') = TQuery (q<>q) (e.*.e')
perhaps you meantq<>q'
? – MacintoshTQuery
type, the!<>!
operator, and the.*.
one, which I cannot find with hoogle. Could you explain? – Macintosh.*.
comes from HList , it's the heterogeneous version of:
. Prepend something to a HList. – Contour