Повторное использование кода в Haxl - исключение конструктора GADT для каждого запроса
Haxl - удивительная библиотека, но одна из основных проблем, которую я нахожу, связана с тем, что для каждого вида запросов к источнику данных требуется собственный конструктор в Request GADT. Например, взяв пример из учебника:
data BlogRequest a where
FetchPosts :: BlogRequest [PostId]
FetchPostContent :: PostId -> BlogRequest PostContent
Затем каждый из этих конструкторов сопоставляется с шаблоном и обрабатывается отдельно в функции сопоставления экземпляра DataSource. Этот стиль приводит к большому количеству шаблонов для нетривиального применения. Возьмем, к примеру, приложение, использующее реляционную базу данных, где каждая таблица имеет первичный ключ. Может быть много сотен таблиц, поэтому я не хочу определять конструктор для каждой таблицы (не говоря уже о всех возможных объединениях между таблицами...). Что я действительно хочу, это что-то вроде:
data DBRequest a where
RequestById :: PersistEntity a => Key a -> DBRequest (Maybe a)
Я использую постоянный для создания типов из моих таблиц, но это не критично - я просто хочу использовать один конструктор для нескольких возможных типов возвращаемых данных.
Проблема возникает при попытке написать функцию выборки. Обычная процедура с Haxl - это сопоставление с образцом в конструкторе для разделения различных типов запросов BlockedFetch, которые в приведенном выше примере соответствуют чему-то вроде этого:
resVars :: [ResultVar (Maybe a)]
args :: [Key a]
(psArgs, psResVars) = unzip
[(key, r) | BlockedFetch (RequestById key) r <- blockedFetches]
... тогда я бы (как-то) сгруппировал аргументы по типу ключа и отправил SQL-запрос для каждой группы. Но такой подход не будет, потому что здесь могут быть запросы на несколько PersistentEntity
типы (т.е. таблицы базы данных), каждый из которых имеет свой тип a
, поэтому составление списка невозможно. Я думал об использовании экзистенциально квантифицированного типа, чтобы обойти эту проблему (что-то вроде SomeSing в библиотеке синглетонов), но потом я не вижу способа сгруппировать запросы, как требуется, без сопоставления с образцом для каждой возможной таблицы / типа.
Есть ли способ добиться такого повторного использования кода?
1 ответ
Я вижу два подхода:
Typeable
:
data DBRequest a where
RequestById :: (Typeable a, PersistEntity a) => Key a -> DBRequest (Maybe a)
или GADT типа "тег":
data Tag a where
TagValue1 :: Tag Value1
TagValue2 :: Tag Value2
TagValue3 :: Tag Value3
TagValue4 :: Tag Value4
TagValue5 :: Tag Value5
data DBRequest a where
RequestById :: PersistEntity a => Tag a => Key a -> DBRequest (Maybe a)
Это очень похожие шаблоны, особенно если вы используете GHC-8.2 с https://hackage.haskell.org/package/base-4.10.1.0/docs/Type-Reflection.html(замените Tag a
с TypeRep a
).
В любом случае, вы можете группировать Key a
используя тег. Я не пробовал, ноdependent-map
может быть удобно: http://hackage.haskell.org/package/dependent-map