6

SQLフラグメントをモノイドとして構成できるSQLコンビネーターを書いています。私はおおよそこのようなタイプを持っています(これは単純化された実装です):

data SQLFragment = { selects :: [String], froms :[String], wheres :: [String]}

instance Monoid SQL Fragment where ...

これにより、頻繁に使用する SQL のビットを簡単に組み合わせて、次のようなことを行うことができます。

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"

それは非常にうまく機能し、私はそれに満足しています。今、私は and を使用MySQL.Simpleして実行するには、行のタイプを知る必要があります。

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

それが私が必要な理由です

 rows :: [(String, String)]

この明示的な (そして役に立たない) 型シグネチャを手動で追加することを避けるために、私は次のアイデアを思いつきました: ファントム型を my に追加し、SQLFragmentそれを使用して関数の型を強制query_します。だから私はこのようなものを持つことができました

email = select "email" <> from "user" :: SQLFragment String
name  = select "name" <> from "user" :: SQLFragment String
administrators = from "user" <> where_ "isAdmin = 1" :: SQLFragment ()

など...

それから私はすることができます

query_ :: SQL.Connection -> SQLFragment a -> IO [a]
query_ con q = SQL.query_ conn (toSql q)

私の最初の問題は、もう使え<>ないということSQLFragment aですMonoid<>2 つ目は、ファントム タイプを正しく計算するために new を実装する方法です。

私は醜いと思う方法を見つけました。もっと良い解決策があることを願っています。のを作成し、typed versionであるSQLFragmentファントム属性を使用しますHList

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

次に、新しいtyped演算子を作成します。!<>! これは、型シグネチャを理解していないため、記述しません

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

これで、型指定されたフラグメントを結合してを追跡することができなくなりました (まだタプルではなく、本当に奇妙なものですが)。

この奇妙な型をタプルに変換するために、型ファミリを作成します。

type family Result e :: *

いくつかのタプルに対してインスタンス化します

別の解決策は、おそらく型ファミリを使用し、タプルのすべての組み合わせを手動で記述することです

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)

など...

そして、それは機能します。Resultファミリを使用して関数を記述できます

execute :: (SQL.QueryResults (Result e)) => 
        SQL.Connection -> TQuery e -> SQL.Connection -> IO [Result e]
execute conn (TQuery q _ ) = SQL.query_ conn (toSql q)

私の主なプログラムは次のようになります:

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

そしてそれは動作します!

ただし、特に使用せずに、HList可能であれば拡張機能をできるだけ少なくする、より良い方法はありますか?

どういうわけかファントムタイプを「隠す」場合(したがって、リアルを持ち、代わりにMonoid使用できるようになります)タイプを取り戻す方法はありますか?<>!<>!

4

1 に答える 1