问题描述
我正在使用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实例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!