Кэширование экземпляров 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 только один раз?


person Cheetah    schedule 28.01.2019    source источник
comment
Я не уверен, возможно ли это в вашем StringExtensions классе. Но всякий раз, когда вы вызываете свой decodeAs метод, вы можете сначала сделать это: implicit val tDecoder: Decoder[T] = derieveDecoder (изменить T на свой собственный тип). Таким образом, все вызовы decodeAs[T] будут использовать val вместо получения нового декодера. Примечание. Если ваша модель состоит из многих вложенных типов, создайте декодеры для каждого из них в обратном порядке.   -  person Luis Miguel Mejía Suárez    schedule 29.01.2019
comment
Я не думаю, что идея Луиса что-то спасает, потому что компилятору все равно нужно генерировать все эти Decoder для каждого implicit val. Единственный способ, который я могу придумать, - это разместить все эти декодеры для ваших типов как implicit vals в некоторых глобально известных статических местах, таких как сам объект пакета json. Тогда может быть только один такой implicit val для каждого раза, и компилятор может использовать эти однажды полученные значения каждый раз, когда такие Decoder необходимы (при условии, что вы import их в своем контексте).   -  person SergGr    schedule 29.01.2019


Ответы (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]]
  }
  // ...

}
person Alec    schedule 29.01.2019