【问题标题】:Caching the circe implicitly resolved Encoder/Decoder instances缓存 circe 隐式解析的 Encoder/Decoder 实例
【发布时间】:2019-06-21 23:49:47
【问题描述】:

我正在使用 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

【问题讨论】:

  • 我不确定您的StringExtensions class 是否有可能。但是,每当您调用decodeAs 方法 时,您可以先执行此操作:implicit val tDecoder: Decoder[T] = derieveDecoder (将T 更改为您自己的类型)。这样,所有对decodeAs[T] 的调用都将使用val,而不是派生一个新的解码器。 注意:如果您的模型包含许多嵌套类型,请以相反的顺序为每个类型创建解码器
  • 我不认为 Luis 的想法可以节省任何东西,因为编译器仍然需要为每个 implicit val 生成所有这些 Decoders。我能想到的唯一方法是将您的类型的所有这些解码器作为implicit vals 放在一些全球已知的静态位置,例如您的json 包对象本身。那么每次只能有一个这样的implicit val,并且编译器可以在每次需要这样的Decoder时使用这些曾经派生的值(假设您将import它们放入您的上下文中)。

标签: scala implicit shapeless circe


【解决方案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] 的值会破坏其“功能”。

下一个最好的事情

下一个最好的方法是手动缓存一堆类型的实例。为了避免样板,我推荐来自ShapelesscachedImplicit 宏。对于您的解码器示例,您最终得到:

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 宏所做的那样),但它可能不那么有趣。这使用了一个鲜为人知的技巧,可以通过隐藏它们的名字来“隐藏”implicits。

package json extends JsonCodec {

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

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-12-01
    • 2022-09-24
    • 2021-06-06
    • 2021-06-09
    • 2019-03-15
    • 2017-06-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多