缓存循环隐式解析的Encoder / Decoder实例

时间:2019-01-28 22:17:42

标签: scala implicit shapeless circe

我正在使用circe序列化/反序列化某些合理的大型模型,其中每个叶子字段都是强类型(例如case class FirstName(value: String) extends AnyVal)。

EncoderDecoder的隐式解析/推导很慢。

我有自己的编解码器,为此我添加了一些额外的EncoderDecoder实例:

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 个答案:

答案 0 :(得分:2)

为什么不能一般地这样做

这是不可能的,因为您要查询的内容归结为缓存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]的值会破坏其“功能”。

第二件事

下一个最好的事情是手动缓存一堆类型的实例。为了避免重复样板,我建议使用Shapeless中的cachedImplicit宏。对于您的解码器示例,您最终得到:

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]]
  }
  // ...

}