我正在使用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
?
答案 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]]
}
// ...
}