不幸的是,这里的术语被过度使用了,因为我们在两种意义上使用“派生”:
- 提供一个实例,例如
List[A] 给出了 A 的实例。
- 为所有成员类型的给定实例提供案例类或密封特征层次结构的实例。
这个问题并不特定于 Circe,甚至 Scala。在写关于 Circe 的文章时,我通常尽量避免将第一种实例生成称为“派生”,而将第二种称为“泛型派生”以强调我们是通过对代数数据类型。
我们有时使用同一个词来指代这两种类型类实例生成这一事实是一个问题,因为它们在 Scala 中通常是非常不同的机制。在 Circe 中,为List[A] 提供编码器或解码器实例的事物是为 A 提供的,是类型类伴随对象中的一种方法。例如,在 circe-core 中的 object Decoder 中,我们有这样一个方法:
implicit def decodeList[A](implicit decodeA: Decoder[A]): Decoder[List[A]] = ...
因为此方法定义在 Decoder 伴随对象中,如果您在具有隐式 Decoder[A] 的上下文中请求隐式 Decoder[List[A]],编译器将找到并使用 decodeList。您不需要任何导入或额外定义。例如:
scala> case class Foo(i: Int)
class Foo
scala> import io.circe.Decoder, io.circe.parser
import io.circe.Decoder
import io.circe.parser
scala> implicit val decodeFoo: Decoder[Foo] = Decoder[Int].map(Foo(_))
val decodeFoo: io.circe.Decoder[Foo] = io.circe.Decoder$$anon$1@6e992c05
scala> parser.decode[List[Foo]]("[1, 2, 3]")
val res0: Either[io.circe.Error,List[Foo]] = Right(List(Foo(1), Foo(2), Foo(3)))
如果我们在这里对隐式机制进行脱糖,它会如下所示:
scala> parser.decode[List[Foo]]("[1, 2, 3]")(Decoder.decodeList(decodeFoo))
val res1: Either[io.circe.Error,List[Foo]] = Right(List(Foo(1), Foo(2), Foo(3)))
请注意,我们可以将第一种推导替换为第二种推导,它仍然可以编译:
scala> import io.circe.generic.semiauto.deriveDecoder
import io.circe.generic.semiauto.deriveDecoder
scala> parser.decode[List[Foo]]("[1, 2, 3]")(deriveDecoder[List[Foo]])
val res2: Either[io.circe.Error,List[Foo]] = Left(DecodingFailure(CNil, List()))
这是可以编译的,因为 Scala 的 List 是一种代数数据类型,它具有 circe-generic 可以为其创建实例的泛型表示。但是,此输入的解码失败,因为此表示不会产生我们期望的编码。我们可以推导出对应的编码器,看看这个编码是什么样子的:
scala> import io.circe.Encoder, io.circe.generic.semiauto.deriveEncoder
import io.circe.Encoder
import io.circe.generic.semiauto.deriveEncoder
scala> implicit val encodeFoo: Encoder[Foo] = Encoder[Int].contramap(_.i)
val encodeFoo: io.circe.Encoder[Foo] = io.circe.Encoder$$anon$1@2717857a
scala> deriveEncoder[List[Foo]].apply(List(Foo(1), Foo(2)))
val res3: io.circe.Json =
{
"::" : [
1,
2
]
}
所以我们实际上看到了List 的:: 案例类,这基本上不是我们想要的。
如果您需要显式提供Decoder[List[Foo]],解决方案是使用Decoder.apply“summoner”方法,或者显式调用Decoder.decodeList:
scala> Decoder[List[Foo]]
val res4: io.circe.Decoder[List[Foo]] = io.circe.Decoder$$anon$44@5d40f590
scala> Decoder.decodeList[Foo]
val res5: io.circe.Decoder[List[Foo]] = io.circe.Decoder$$anon$44@2f936a01
scala> Decoder.decodeList(decodeFoo)
val res6: io.circe.Decoder[List[Foo]] = io.circe.Decoder$$anon$44@7f525e05
这些都提供完全相同的实例,您应该选择哪个是个人喜好问题。
作为脚注,我考虑过在 circe-generic 中使用特殊大小写 List 以使 deriveDecoder[List[X]] 无法编译,因为它几乎从来都不是您想要的(但看起来可能是这样,尤其是因为我们谈论实例推导的令人困惑的方式)。我通常不喜欢有这样的特殊情况的想法,但我认为在这种情况下这可能是正确的做法,因为这个问题经常出现。