gpt4 book ai didi

haskell - 如何在 Haskell 中连接幻像类型中的元组?

转载 作者:行者123 更新时间:2023-12-02 20:45:54 25 4
gpt4 key购买 nike

我正在编写一个 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 ()

等等...

那我就可以了

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

我的第一个问题是我无法使用<>不再因为SQLFragment a不是Monoid不再了。第二个是我如何实现我的新<>正确计算幻像类型?

我发现了一种我认为很难看的方法,我希望有更好的解决方案。我创建了一个typed versionSQLFragment并使用幻像属性,即 HList .

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

然后我创建一个新的typed运算符(operator):!<>!我不理解类型签名,所以我不写它

(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 并能够使用 <> 而不是 !<>! )有没有办法恢复类型?

最佳答案

考虑使用haskelldb解决了类型化数据库查询问题。 haskelldb 中的记录工作正常,但它们不提供很多操作,并且类型较长,因为它们不使用 -XDataKinds

我对您当前的代码有一些建议:

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

更好,因为 e 实际上是幻像类型。那么您的追加操作可能如下所示:

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

结果看起来更干净:

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

如果您想保留 HList+mysql-simple 和 haskelldb 的重复部分,实例 QueryResults (Record r) 可能比较合适。未发布的Read instance解决了一个非常类似的问题,可能值得一看。

关于haskell - 如何在 Haskell 中连接幻像类型中的元组?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24043650/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com