Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

force ambiguous instance with phantom type

Tags:

haskell

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).

like image 938
mb14 Avatar asked Mar 08 '26 21:03

mb14


1 Answers

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.

like image 54
Ben Avatar answered Mar 10 '26 15:03

Ben



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!