【问题标题】:Implicit resolution fail in reflection with ToolBox使用 ToolBox 进行隐式解析失败
【发布时间】:2021-03-02 07:35:39
【问题描述】:

我正在尝试根据类路径在反射中生成 Avro4s 的 RecordFormat。以下代码会引发错误。

case class MyCaseClass(a: Int)
println(toolBox.compile {
  toolBox.parse(
    s"""
       |import com.sksamuel.avro4s._
       |import mypackage.MyCaseClass
       |RecordFormat[MyCaseClass]
       |""".stripMargin
  )
}())

找不到 com.sksamuel.avro4s.Decoder[mypackage.MyCaseClass] 类型的证据参数的隐式值

RecordFormat 就像

object RecordFormat {

  def apply[T <: Product : Encoder : Decoder : SchemaFor]: RecordFormat[T] = apply(AvroSchema[T])

  def apply[T <: Product : Encoder : Decoder](schema: Schema): RecordFormat[T] = new RecordFormat[T] {
    private val fromRecord = FromRecord[T](schema)
    private val toRecord = ToRecord[T](schema)
    override def from(record: GenericRecord): T = fromRecord.from(record)
    override def to(t: T): Record = toRecord.to(t)
  }
}

参考:https://github.com/sksamuel/avro4s/blob/release/2.0.x/avro4s-core/src/main/scala/com/sksamuel/avro4s/RecordFormat.scala

我可以看到,它可以解析 Encoder[MyCaseClass]SchemaFor[MyCaseClass] 但对于 Decoder[MyCaseClass] 失败。

同样的代码无需反射就可以解析RecordFormat[MyCaseClass]

我可以看到Decoder 是用类似于Encoder 的宏实现的。

implicit def applyMacro[T <: Product]: Decoder[T] = macro applyMacroImpl[T]

为什么反思不能解决隐含的证据?

【问题讨论】:

  • 运行时反射很少是合适的解决方案
  • 我的类路径中有 +1000 案例类定义在不同的包中,并且不是由我维护的。我的逻辑基本上接受类的完整路径进行数据处理。我正在尝试使用 avro 而不是 json,并且需要从 avro4s 创建RecordFormat。您是否推荐更合适的解决方案,因为 RecordFormat 高度依赖于宏。
  • @lalala 无法复制。 Decoder 似乎和 SchemaForEncoder scastie.scala-lang.org/HZR8Kf1WQkGtqOsc5efWuQ 我在 Scastie 制作了 mypackage 一个对象,但如果它是一个包,本地行为对我来说是相同的。斯卡拉 2.13.3,avro4s 4.0.1。对象RecordFormat 内线右侧的apply 是什么?现在似乎是从左侧递归调用apply,这不可能是正确的。
  • @DmytroMitin 我的 Avro4s 版本是 2.x 和 Scala 2.11,不知道是不是这个原因。
  • @DmytroMitin,关于递归调用,我已经用完整的库实现和参考链接更新了这个问题

标签: scala reflection scala-macros scala-reflect avro4s


【解决方案1】:

avro4s 4.x 使用Magnoliaavro4s 2.x 使用原始implicit macros + Shapeless

通常there shouldn't be significant problems 在运行时使用反射工具箱具体化类型类,即使类型类是用宏定义的。

现在的问题是定义com.sksamuel.avro4s.Decoder 的宏有一个错误。行Decoder.scala#L404

c.Expr[Decoder[T]](
  q"""
  new _root_.com.sksamuel.avro4s.Decoder[$tpe] {
    private[this] val decoders = Array(..$decoders)

    override def decode(value: Any, schema: _root_.org.apache.avro.Schema): $tpe = {
      val fullName = $fullName
      value match {
        case record: _root_.org.apache.avro.generic.GenericRecord => $companion.apply(..$fields)
        case _ => sys.error("This decoder decodes GenericRecord => " + fullName + " but has been invoked with " + value)
      }
    }
  }
  """
)

指的是sys.error 而不是hygienic _root_.scala.sys.error

如果你修复这条线,Decoder[MyCaseClass]RecordFormat[MyCaseClass] 将在工具箱中工作

println(toolBox.compile {
  toolBox.parse(
    s"""
       |import com.sksamuel.avro4s._
       |import mypackage.MyCaseClass
       |RecordFormat[MyCaseClass]
       |""".stripMargin
  )
}()) //com.sksamuel.avro4s.RecordFormat$$anon$1@25109d84

所以一个快速的解决方法是删除该行

libraryDependencies += "com.sksamuel.avro4s" %% "avro4s-core" % "2........."

build.sbt,添加

libraryDependencies += "com.chuusai" %% "shapeless" % "2.3.3"
libraryDependencies += "org.apache.avro" % "avro" % "1.8.2"

(否则您将拥有NoClassDefFoundError)并将以下修补的jar放入lib

https://github.com/DmytroMitin/avro4s-2.0.5-2.11-patched

avro4s-core_2.11-2.0.5-SNAPSHOT.jar
avro4s-macros_2.11-2.0.5-SNAPSHOT.jar

如果您像这样创建工具箱,则始终可以调试使用工具箱生成的基于隐式或基于宏的代码

val toolBox = runtimeMirror.mkToolBox(
  frontEnd = new FrontEnd {
    override def display(info: Info): Unit = println(info)
    override def interactive(): Unit = ???
  },
  options = "-Xlog-implicits" // or "-Xlog-implicits -Ymacro-debug-lite"
)

如果你这样做

println(reify{
  Decoder[MyCaseClass]
}.tree)

打印出来

Decoder.apply[MyCaseClass](Decoder.applyMacro)

所以隐含的Decoder[MyCaseClass] 被解析为Decoder.applyMacro[MyCaseClass]

使用原始未打补丁的 jars

toolBox.compile {
  toolBox.parse(
    s"""
       |import com.sksamuel.avro4s._
       |import mypackage.MyCaseClass
       |Decoder.applyMacro[MyCaseClass]
       |""".stripMargin
  )
}()

抛出

scala.tools.reflect.ToolBoxError: reflective compilation has failed:
object error is not a member of package sys

【讨论】:

  • 很好的解释!它与我的问题没有直接关系,但除了反思之外,您还有其他解决方案吗?我确实使用了其他包中的案例类。在运行时,我得到一个类路径作为构建逻辑的配置。 RecordFormat 需要编译时的类型。你认为有可能从类路径而不是基于反射的编译器中获取 RecordFormat。
  • @lalala 很难说。我不确定我是否完全理解是什么阻止您直接调用RecordFormat[MyCaseClass](编译时技术将是类型类、隐式、宏、编译时反射)。但是,如果您真的只在运行时知道类名MyCaseClass,那么使用工具箱(或在运行时运行编译器的另一种方式)解析隐式似乎是正确的方法。否则,您必须手动填写 Map[String, RecordFormat[_]](其中字符串是类名)。
  • @lalala 或调用RecordFormat[MyCaseClass],如果不是直接调用,那么至少在编译时。
  • @lalala 关于将类路径作为构建逻辑的配置。有些类在编译时是已知的。该项目应组织为三个子项目:commonmacros(取决于common)、core(取决于macroscommon)。见12。来自common 的类在core 的编译时是已知的,并且可以在从macros 扩展宏时使用。
猜你喜欢
  • 2020-09-09
  • 1970-01-01
  • 2021-06-03
  • 1970-01-01
  • 2015-10-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-19
相关资源
最近更新 更多