缓存循环隐式解析的Encoder

缓存循环隐式解析的Encoder

本文介绍了缓存循环隐式解析的Encoder/Decoder实例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用circe序列化/反序列化一些相当大的模型,其中每个叶子字段都是一个强类型(例如 case class FirstName(value:String)扩展AnyVal ).

I am using circe to serialize/deserialize some reasonably large models, where each leaf field is a strong type (e.g. case class FirstName(value: String) extends AnyVal).

Encoder Decoder 的隐式分辨率/派生速度很慢.

Implicit resolution/derivation of an Encoder or Decoder is slow.

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

I have my own codec for which I add some extra Encoder and Decoder instances:

trait JsonCodec extends AutoDerivation {
    // ...
}

使用以下方法来帮助解码:

With the following method to help with decoding:

package json extends JsonCodec {

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

}

问题在于,每次我调用 decodeAs 时,它都会隐式派生 Decoder ,这会导致编译时间大量增加.

The problem is that every time I call decodeAs, it implicitly derives a Decoder which causes the compilation times to increase massively.

有什么方法可以(通常)缓存隐式对象,使其仅生成一次 Decoder ?

Is there any way I can (generically) cache the implicits such that it will only generate a Decoder once?

推荐答案

为什么不能通用地这样做

这是不可能的,因为您要查询的内容归结为缓存 def .问题的一部分是产生隐式实例会(尽管很少这样做)会产生副作用.病理示例:

Why you can't do this generically

This is not possible, since what you are asking boils down to caching a def. Part of the problem is that producing an implicit instance can (although it rarely does) have side effects. Pathological example:

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] 的值将破坏其功能".

As you can see, caching the value of DummyTypeclass[Int] would break its "functionality".

下一个最好的事情是手动缓存一堆类型的实例.为了避免样板,我建议从无形.对于您的解码器示例,您最终得到:

The next best thing is to manually cache instances for a bunch of types. In order to avoid boilerplate, I recommend the cachedImplicit macro from Shapeless. For your decoder example, you end up with:

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宏所做的事情),但这可能会不太有趣.这使用了一个鲜为人知的技巧,即可以通过隐藏隐式名称的名称来隐藏"隐式.

If you don't like macros, you can do this manually (basically just what the Shapeless macro does), but it might be less fun. This uses a little known trick that implicits can be "hidden" by shadowing their name.

package json extends JsonCodec {

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

}

这篇关于缓存循环隐式解析的Encoder/Decoder实例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-18 12:36