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
使用できるようになります)タイプを取り戻す方法はありますか?<>
!<>!