Кэширование Circe неявно разрешено экземпляров Encoder/Decoder

Я использую Circe для сериализации / десериализации некоторых достаточно больших моделей, где каждое поле листа имеет строгий тип (например, case class FirstName(value: String) extends AnyVal).

Неявное разрешение / вывод Encoder или же Decoder медленный.

У меня есть свой собственный кодек, для которого я добавляю некоторые дополнительные Encoder а также Decoder экземпляры:

trait JsonCodec extends AutoDerivation {
    // ...
}

С помощью следующего метода, чтобы помочь с декодированием:

package json extends JsonCodec {

  implicit class StringExtensions(val jsonString: String) extends AnyVal {
    def decodeAs[T](implicit decoder: Decoder[T]): T =
      // ...
  }

}

Проблема в том, что каждый раз, когда я звоню decodeAsнеявно выводит Decoder что приводит к значительному увеличению времени компиляции.

Есть ли способ, которым я могу (в общем) кешировать последствия так, что он будет генерировать только Decoder один раз?

1 ответ

Почему вы не можете сделать это в общем

Это невозможно, поскольку то, что вы просите, сводится к кешированию def, Частично проблема заключается в том, что создание неявного экземпляра может (хотя и редко) имеет побочные эффекты. Патологический пример:

scala> var myVar: Int = 0
myVar: Int = 0

scala> :paste
// Entering paste mode (ctrl-D to finish)

trait DummyTypeclass[T] { val counter: Int }
implicit def dummyInstance[T]: DummyTypeclass[T] = {
  myVar += 1
  new DummyTypeclass[T] {
    val counter = myVar
  }
}

// Exiting paste mode, now interpreting.

defined trait DummyTypeclass
dummyInstance: [T]=> DummyTypeclass[T]

scala> implicitly[DummyTypeclass[Int]].count
res1: Int = 1

scala> implicitly[DummyTypeclass[Boolean]].counter
res2: Int = 2

scala> implicitly[DummyTypeclass[Int]].counter
res3: Int = 3

Как видите, кеширование значения DummyTypeclass[Int] сломал бы его "функциональность".

Следующая лучшая вещь

Следующая лучшая вещь - это ручное кэширование экземпляров для нескольких типов. Чтобы избежать шаблонов, я рекомендую cachedImplicit макрос от Shapeless. В качестве примера вашего декодера вы получите:

package json extends JsonCodec {

  import shapeless._

  implicit val strDecoder:  Decoder[String]    = cachedImplicit
  implicit val intDecoder:  Decoder[Int]       = cachedImplicit
  implicit val boolDecoder: Decoder[Boolean]   = cachedImplicit
  implicit val unitDecoder: Decoder[Unit]      = cachedImplicit
  implicit val nameDecoder: Decoder[FirstName] = cachedImplicit
  // ...

  implicit class StringExtensions(val jsonString: String) extends AnyVal {
    // ...
  }

}

Если вам не нравятся макросы, вы можете сделать это вручную (в основном то, что делает макрос Shapeless), но это может быть не так весело. Это использует малоизвестный трюк, который может скрывать последствия, скрывая их имя.

package json extends JsonCodec {

  implicit val strDecoder:  Decoder[String] = {
    def strDecoder = ???
    implicitly[Decoder[String]]
  }
  implicit val intDecoder:  Decoder[Int] = {
    def intDecoder = ???
    implicitly[Decoder[Int]]
  }
  // ...

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