我正在编写一个 SQL 组合器,它允许将 SQL 片段组合为 Monoid。我大致有这样的类型(这是一个简化的实现):
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"
这很好用,我很满意。现在我使用MySQL.Simple
并执行它需要知道行的类型。
main = do
conn <- SQL.connect connectInfo
rows <- SQL.query_ conn $ toSql (email <> name <> administrators)
forM_ (rows :: [(String, String)]) print
这就是为什么我需要
rows :: [(String, String)]
为了避免手动添加这个显式(且无用)的类型签名,我有以下想法:我添加了一个幻像类型SQLFragment
并使用它来强制函数的类型query_
。所以我可以有这样的东西
email = select "email" <> from "user" :: SQLFragment String
name = select "name" <> from "user" :: SQLFragment String
administrators = from "user" <> where_ "isAdmin = 1" :: SQLFragment ()
ETC ...
那我可以做
query_ :: SQL.Connection -> SQLFragment a -> IO [a]
query_ con q = SQL.query_ conn (toSql q)
我的第一个问题是我不能再使用<>
了,因为不再SQLFragment a
是 aMonoid
了。第二个是如何实现我的 new<>
以正确计算幻像类型?
我找到了一种我认为丑陋的方法,我希望有一个更好的解决方案。我创建了一个typed version
ofSQLFragment
并使用了一个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)
ETC ...
那行得通。我可以使用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
并且能够使用<>
而不是!<>!
)有没有办法让这个类型回来?