Я пишу комбинатор 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"
Это работает очень хорошо, и я доволен этим. Теперь я использую 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 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
и я смогу использовать <>
вместо !<>!
), есть ли способ вернуть этот тип?
strQuery :: Connection -> Query -> IO [(String, String)]
;strQuery = SQL.query_
, очень похоже на написание функцииreadInt :: String -> Int
;readInt = read
? Если вы всегда получаете один и тот же возвращаемый тип или только один из нескольких типов, тогда этот подход должен быть довольно управляемым и не требует для работы какой-либо сложной сантехники. В противном случае я не вижу проблем с указанием встроенного типа. - person bheklilr   schedule 04.06.2014(TQuery q e) !<>! (TQuery q' e') = TQuery (q<>q) (e.*.e')
может ты имел ввидуq<>q'
? - person didierc   schedule 04.06.2014TQuery
, оператор!<>!
и оператор.*.
, который я не могу найти с помощью hoogle. Могли бы вы объяснить? - person didierc   schedule 05.06.2014.*.
происходит из HList, это гетерогенная версия:
. Добавьте что-нибудь в HList. - person mb14   schedule 05.06.2014