Для чего используются FromJSON1 и ToJSON1 в aeson?

Aeson предоставляет FromJSON1 и Классы типов ToJSON1. Они аналогичны Eq1 и Show1 классы, определенные в Data.Functor.Classes< /а> модуль.

Мое понимание классов Eq1 и Show1 заключается в том, что они необходимы, чтобы иметь возможность выражать ограничения на аргументы преобразователей без использования расширений, таких как FlexibleContexts и UndecidableInstances.

Пример из документации модуля Data.Functor.Classes выглядит следующим образом:

Предположим, у нас есть тип данных, который действует как преобразователь: T. Например, пусть он будет изоморфен IdentityT:

data T f a = T (f a)

Вид T следующий:

T :: (* -> *) -> * -> *

Если есть экземпляр Eq1 для f, его можно использовать при записи экземпляра Eq1 для T f:

instance Eq1 f => Eq1 (T f) where
  liftEq :: (a -> b -> Bool) -> T f a -> T f b -> Bool
  liftEq eq (T fa1) (T fa2) = liftEq eq fa1 fa2

Если у нас есть экземпляр Eq1 для f, экземпляр Eq для a и экземпляр Eq1 для T f выше находится в области видимости, мы можем легко написать экземпляр Eq для T f a:

instance (Eq1 f, Eq a) => Eq (T f a) where
  (==) :: T f a -> T f a -> Bool
  (==) = eq1

Тип eq1 определяется следующим образом:

eq1 :: (Eq1 h, Eq a) => h a -> h a -> Bool

В приведенном выше примере h становится T f, поэтому тип eq1 можно представить следующим образом:

eq1 :: Eq a => T f a -> T f a -> Bool

Теперь классы Eq1, Show1 и т. д. имеют смысл. Кажется, что проще писать экземпляры Eq, Show и т. д. для трансформаторов.

Однако мне интересно, какие типы FromJSON1 и ToJSON1 используются в Aeson? У меня редко бывают преобразователи, которые я хочу использовать в JSON и обратно.

Большинство типов данных, которые я в конечном итоге заменяю на JSON, являются обычными типами (а не конструкторами типов). То есть типы с видом *. Я также использую типы вроде Maybe с типом * -> *.

Однако я не думаю, что часто создаю экземпляры ToJSON или FromJSON для трансформеров, как T выше. Что такое преобразователь, который часто используется для перехода к JSON и обратно? Я пропускаю некоторые полезные трансформеры?


person illabout    schedule 09.03.2018    source источник
comment
Эти классы были введены через aeson выпуск № 352. Там может быть хороший ответ, который можно извлечь из обсуждения (и в связанной issue #341< /а>).   -  person duplode    schedule 09.03.2018


Ответы (1)


Eq1 предлагает еще одну возможность, которую вы не обсуждали в своем изложении: она позволяет вам написать функцию, которая вызывает (==) для многих различных типов, не обязательно заранее зная, для каких типов вы будете ее использовать.

Я приведу игрушечный пример; надеюсь, вы сможете увидеть сквозь кажущуюся бесполезность этого примера причину, по которой Eq1 дает вам некоторые интересные возможности.

Представьте, что вы хотите создать дерево, которое параметризовано по коэффициенту ветвления, поэтому вы параметризуете его дочерним контейнером. Таким образом, значения могут выглядеть так:

{-# LANGUAGE GADTs #-}
data Tree m a where
    Branch :: Tree m (m a) -> Tree m a
    Leaf :: a -> Tree m a

Например, я могу получить бинарные деревья с Tree Pair, тройные деревья с Tree Triple, пальчатые деревья с Tree TwoThree и розовые деревья с Tree [], где data Pair a = Pair a a, data Triple a = Triple a a a и data TwoThree a = Two a a | Three a a a. Теперь я хотел бы написать для этого экземпляр Eq. Если мы полагаемся только на Eq ограничений, мы не сможем достичь того, чего хотим. Давай попробуем:

instance Eq (Tree m a) where
    Leaf a == Leaf a' = a == a'
    Branch t == Branch t' = t == t'
    _ == _ = False

Естественно, GHC жалуется, что не знает, как сравнить a и a' на равенство. Поэтому добавьте Eq a в контекст:

instance Eq a => Eq (Tree m a) where ...

Теперь GHC жалуется, что не знает, как сравнить m as на равенство в случае Branch. Имеет смысл.

instance (Eq a, Eq (m a)) => Eq (Tree m a) where ...

Все равно не пройти! Теперь реализация (==) :: Tree m a -> Tree m a -> Bool имеет рекурсивный вызов (==) :: Tree m (m a) -> Tree m (m a) -> Bool в случае Branch, следовательно, должна предоставить контекст (Eq (m a), Eq (m (m a))) для выполнения этого рекурсивного вызова. Хорошо, давайте добавим это в контекст экземпляра...

instance (Eq a, Eq (m a), Eq (m (m a))) => Eq (Tree m a) where ...

Все равно ничего хорошего. Теперь рекурсивный вызов должен доказывать еще больше! На самом деле мы хотели бы сказать, что если у нас есть Eq b, то у нас есть Eq (m b) для всех b, а не только для конкретного a, используемого в качестве второго параметра Tree.

instance (Eq a, (forall b. Eq b => Eq (m b))) => Eq (Tree m a) where ...

Конечно, в Haskell это совершенно не так. Но Eq1 дает нам это:

instance Eq1 m => Eq1 (Tree m) where
    liftEq (==) (Leaf a) (Leaf a') = a == a'
    liftEq (==) (Branch t) (Branch t') = liftEq (liftEq (==)) t t'
    liftEq (==) _ _ = False

instance (Eq1 m, Eq a) => Eq (Tree m a) where
    (==) = eq1

Здесь ограничение Eq1 m выполняет ту роль, о которой мы просили ранее, а именно, что все (Eq a, Eq (m a), Eq (m (m a)), ...) возможны.

Классы ToJSON1 и FromJSON1 выполняют аналогичную роль: они дают вам одно ограничение, которое вы можете задать, что составляет потенциально бесконечный набор ограничений ToJSON и FromJSON, так что вы можете выбрать, какое ограничение ToJSON или FromJSON вам нужно в управляемом данными путь и быть гарантированным, что он доступен.

person Daniel Wagner    schedule 09.03.2018
comment
Спасибо! Это действительно хорошее объяснение. Я собираюсь пройти через это медленно, чтобы действительно позволить ему погрузиться. В моем собственном коде я не думаю, что склонен использовать рекурсивные типы, которые упаковывают все больше и больше конструкторов типов (например, data Tree m a = Branch (Tree m (m a))). Мне придется держать глаза открытыми для мест, где это будет полезно. Кроме того, можно ли теперь Eq (Tree m a) писать с количественными ограничениями? - person illabout; 09.03.2018
comment
@illabout Этот тип Tree и подобные ему вещи не очень распространены, но экзистенциально-квантифицированные блоки необходимы и нуждаются в подобном трюке. Ваша ссылка выглядит тесно связанной, но Eq1 имеет явное преимущество в том, что она существует сегодня. - person Daniel Wagner; 10.03.2018
comment
Tbf, квантифицированные контексты существуют и сегодня. Они просто существуют очень экспериментально. - person HTNW; 10.03.2018