How are tuples combined in phantom types in Haskell?

I am writing a SQL combiner that allows you to compose SQL fragments as a monoid. I have something like this (this is a simplified implementation):

data SQLFragment = { selects :: [String], froms :[String], wheres :: [String]} instance Monoid SQL Fragment where ... 

This allows me to easily combine the SQL bits that I often use 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" 

It works very well and I am pleased with it. Now I am using MySQL.Simple , and to execute it, it must know the type of string.

 main = do conn <- SQL.connect connectInfo rows <- SQL.query_ conn $ toSql (email <> name <> administrators) forM_ (rows :: [(String, String)]) print 

That's why i need

  rows :: [(String, String)] 

In order not to manually add this explicit (and useless) type signature, I had the following idea: I add the phantom type to my SQLFragment and use it to force the query_ function query_ . So I could be 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 that I can no longer use <> because SQLFragment a no longer Monoid . Secondly, how can I implement my new <> for proper phantom type calculation?

I found a way that I think is ugly, and I hope there is a much better solution. I created a typed version from SQLFragment and used the phantom attribute, which is an HList .

 data TQuery e = TQuery { fragment :: SQLFragment , doNotUse :: e } 

then I create a new typed operator typed !<>! I am not an undestand type signature, so I do not write it

 (TQuery qe) !<>! (TQuery q' e') = TQuery (q<>q') (e.*.e') 

Now I can’t combine my typed fragment and keep track of the type (although it is not yet a tuple, but something really strange).

To convert this strange type to a tuple, I create a type family:

 type family Result e :: * 

and create an instance for some sets

Another solution would probably be to use a type family and manually record each tuple combination

 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 it 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 is as follows:

 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 this, especially without using an HList and, if possible, smaller extensions?

If I somehow β€œhide” the phantom type (so that I can have a real Monoid and be able to use <> instead of !<>! ), Is there a way to return the type?

+6
source share
1 answer

Consider using haskelldb , which poses a problem with a typed database query. Entries in haskelldb work fine, but they do not provide many operations, and types are longer because they do not use -XDataKinds .

I have suggestions for your current code:

 newtype TQuery (e :: [*]) = TQuery SQLFragment 

better because e is actually a phantom type. Then your add operation might look like this:

 (!<>!) :: TQuery a -> TQuery b -> TQuery (HAppendR ab) TQuery a !<>! TQuery b = TQuery (a <> b) 

Result then looks a lot cleaner:

 type family Result (a :: [*]) type instance Result '[a]) = (SQL.Only a) type instance Result '[a, b] = (a, b) type instance Result '[a, b, c] = (a, b, c) type instance Result '[a, b, c, d] = (a, b, c, d) type instance Result '[a, b, c, d, e] = (a, b, c,d, e) -- so you might match the 10-tuple mysql-simple offers 

If you want to stay with HList + mysql-simple and the duplicated parts of haskelldb, instance QueryResults (Record r) might be suitable. An unpublished reading instance solves a very similar problem and may be worth a look.

+2
source

Source: https://habr.com/ru/post/970325/


All Articles