I'm using mysql-simple and I'm trying to use phantom type to avoid to have to use explicit type signature.
Let's say I'm trying to execute the following query SELECT firstname, lastname FROM users.
I would try something like this :
{-# LANGUAGE OverloadedStrings #-}
import Database.MySQL.Simple
myQuery :: Query
myQuery = "SELECT firstname, lastname FROM users"
main = do
conn <-connect defaultConnectInfo
rows <- queryT conn myQuery
mapM_ print rows
This doesn't work because the compiler can't deduce the type of rows. The solution is to add a type signature like this :
{-# LANGUAGE OverloadedStrings #-}
import Database.MySQL.Simple
myQuery :: Query
myQuery = "SELECT firstname, lastname FROM users"
main = do
conn <-connect defaultConnectInfo
rows <- queryT conn myQuery
mapM_ print (rows :: [(String, String)]) ------<< Here
The compiler deduces that each row is (String, String) and everythign works fine.
This solution is however not satisfying because if I modify myQuery I need to modify the rows type signature. Moreover, this is a simpified example. In the real code , the query comes from a query combinator (which hold the row type), so there is no way to hardcode the row type.
I tried using a typed query using a phantom type
{-# LANGUAGE OverloadedStrings #-}
import Database.MySQL.Simple
data QueryT a = QueryT Query ---------------<< Phantom type
queryT :: Connection -> QueryT a -> IO [a]
queryT conn (QueryT q) = query_ conn q
myQuery :: QueryT (String, String) ----------<< Query holding it's row type
myQuery = QueryT $ "SELECT firstname, lastname FROM users"
main = do
conn <-connect defaultConnectInfo
rows <- queryT conn myQuery
mapM_ print rows
This doesn't work. I got the following message :
stack.hs:7:26:
No instance for (Database.MySQL.Simple.QueryResults.QueryResults a)
arising from a use of `query_'
Possible fix:
add (Database.MySQL.Simple.QueryResults.QueryResults
a) to the context of
the type signature for queryT :: Connection -> QueryT a -> IO [a]
In the expression: query_ conn q
In an equation for `queryT': queryT conn (QueryT q) = query_ conn q
If I replace the phantom type by (String, String) in queryT type signature
queryT :: Connection -> QueryT a -> IO [(String, String)]
Things work again. So what is the difference ?
Why the type inference can't infere that rows is of type [(String, String)] ?
(I've tried functional dependencies and type families but that doesn't seem to help either).
The problem isn't with inferring (String, String) for the row type where you call queryT; that gets inferred just fine.
The issue is in compiling queryT itself; that's polymorphic in a so the compiler isn't supposed to infer (String, String), it's supposed to compile code that will work for any type a. But queryT gets its result by calling query_, and query_ isn't able to work for any type at all, only for the types in the QueryResult type class.
But you don't want to be able to run queryT on queries that claim to produce Either (IO [a -> Int]) (Maybe Void) as the row type any more than query_ wants that as a possible row type, so the solution is to limit queryT to operate on queries whose phantom type is in QueryResult too:
queryT :: QueryResult a => Connection -> QueryT a -> IO [a]
That's exactly what was meant by the suggested "possible fix" of adding QueryResult a to the context of queryT. The "context" of a type signature is everything before the =>, where you write constraints on the type variables involved.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With