aeson中的FromJSON1和ToJSON1是干什么用的?

What are FromJSON1 and ToJSON1 used for in aeson?

Aeson 提供 FromJSON1 and ToJSON1 type classes. These are similar to the Eq1 and Show1 classes defined in the Data.Functor.Classes 模块。

我对 Eq1Show1 类 的理解是,它们需要能够在不使用 FlexibleContextsFlexibleContextsUndecidableInstances.

Data.Functor.Classes模块文档中的示例如下:

假设我们有一个充当转换器的数据类型:T。例如,让它同构于 IdentityT:

data T f a = T (f a)

种类T如下:

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

如果 f 有一个 Eq1 实例,可以在为 T f 编写 Eq1 实例时使用它:

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

如果我们有 fEq1 实例,aEq 实例,以及 T fEq1 实例以上在范围内,我们可以轻松地为 T f a:

编写 Eq 实例
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

现在,Eq1Show1 等 类 变得有意义了。似乎可以更轻松地为转换器编写 EqShow 等实例。

不过,我想知道Aeson中使用的是什么类型的FromJSON1ToJSON1?我很少有我想转来转去的变形金刚JSON。

我最终更改为 JSON 的大多数数据类型都是普通类型(不是类型构造函数)。也就是说,类型为*。我还使用 Maybe 之类的类型和 * -> *.

但是,我不认为我经常为转换器创建 ToJSONFromJSON 实例,例如上面的 T。经常用来往返JSON的变压器是什么?我是否错过了一些有用的变形金刚?

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 adata Triple a = Triple a a adata 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 抱怨它不知道如何比较 aa' 是否相等。所以在上下文中添加Eq a

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

现在 GHC 抱怨它不知道如何在 Branch 情况下比较 m as 是否相等。有道理。

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

还是不行!现在 (==) :: Tree m a -> Tree m a -> Bool 的实现在其 Branch 情况下对 (==) :: Tree m (m a) -> Tree m (m a) -> Bool 进行了递归调用,因此必须提供上下文 (Eq (m a), Eq (m (m a))) 以进行该递归调用。好的,让我们将其添加到实例上下文中...

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

还是不行。现在递归调用必须证明更多的东西!我们真正想说的是,如果我们有 Eq b,那么对于所有 b,我们就有 Eq (m 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)), ...)都是可能的。

ToJSON1FromJSON1 类 起着相似的作用:它们给你一个单一的约束,你可以给它相当于一个潜在的无限集合 ToJSONFromJSON 约束,这样您就可以以 data-driven 的方式选择您需要的 ToJSONFromJSON 约束,并确保它可用。