Повторное использование кода в 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

Другие вопросы по тегам