Терминология здесь, к сожалению, перегружена, так как мы используем «получение» в двух смыслах:
- Предоставление экземпляра, например,
List[A]
, заданного экземпляра для A
. - Предоставление экземпляра для класса дел или иерархии запечатанных признаков, заданных экземпляров для всех типов элементов.
Эта проблема не указана c для Цирцеи или даже Scala. При написании статьи о Circe я обычно стараюсь вообще не ссылаться на первый тип генерации экземпляров как на «деривацию», а на второй тип - на «generi c дифференцирование», чтобы подчеркнуть, что мы генерируем экземпляры через generi. c представление типа данных algebrai c.
Тот факт, что мы иногда используем одно и то же слово для ссылки на оба типа генерации экземпляров класса типов, является проблемой, поскольку они обычно очень разные механизмы в Scala. В Circe то, что предоставляет экземпляр кодера или декодера для List[A]
, заданное для A
, - это метод в объекте-компаньоне класса типа. Например, в object Decoder
в circe-core у нас есть метод, подобный этому:
implicit def decodeList[A](implicit decodeA: Decoder[A]): Decoder[List[A]] = ...
Поскольку это определение метода содержится в объекте-компаньоне Decoder
, если вы запрашиваете неявный Decoder[List[A]]
в контексте, где у вас есть неявное Decoder[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 s List
является типом данных algebrai c, который имеет представление generi c, которое circe-generi c может создать экземпляр для. Однако декодирование не выполняется для этого ввода, поскольку это представление не приводит к ожидаемой кодировке. Мы можем получить соответствующий кодировщик, чтобы увидеть, как выглядит эта кодировка:
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
. точно такой же экземпляр, и который вы должны выбрать - дело вкуса.
В качестве сноски я подумал о специальном корпусе List
в circe-generi c, чтобы deriveDecoder[List[X]]
не компилируется, так как это примерно никогда не то, что вы хотите (но, похоже, это может быть, особенно из-за запутанного способа, которым мы говорим о деривации экземпляра). Мне обычно не нравится идея иметь такие особые случаи, но я думаю, что в этом случае это может быть правильным решением, так как этот вопрос часто возникает.