【问题标题】:Why Scala runtime reflection no longer works on lambda?为什么 Scala 运行时反射不再适用于 lambda?
【发布时间】:2019-12-16 01:18:25
【问题描述】:

以下简单代码:

import org.scalatest.FunSpec

class RuntimeMirrorSpike extends FunSpec {

  import org.apache.spark.sql.catalyst.ScalaReflection.universe._

  it("can reflect lambda") {

    val ll = { v: String =>
      v.toInt
    }

    val clazz = ll.getClass
    val mirror = runtimeMirror(clazz.getClassLoader)

    val sym = mirror.classSymbol(clazz)

    print(sym)
  }
}

曾经在 Scala 2.11 上完美运行。但现在它在 Scala 2.12 上中断了:

assertion failed: no symbol could be loaded from class <...>.spike.RuntimeMirrorSpike$$Lambda$124/78204644 in package spike with name RuntimeMirrorSpike$$Lambda$124/78204644 and classloader sun.misc.Launcher$AppClassLoader@18b4aac2
java.lang.AssertionError: assertion failed: no symbol could be loaded from class <...>.spike.RuntimeMirrorSpike$$Lambda$124/78204644 in package spike with name RuntimeMirrorSpike$$Lambda$124/78204644 and classloader sun.misc.Launcher$AppClassLoader@18b4aac2
    at scala.reflect.internal.SymbolTable.throwAssertionError(SymbolTable.scala:184)
    at scala.reflect.runtime.JavaMirrors$JavaMirror.classToScala1(JavaMirrors.scala:1061)
    at scala.reflect.runtime.JavaMirrors$JavaMirror.$anonfun$classToScala$1(JavaMirrors.scala:1019)
    at scala.reflect.runtime.JavaMirrors$JavaMirror.$anonfun$toScala$1(JavaMirrors.scala:130)
    at scala.reflect.runtime.TwoWayCaches$TwoWayCache.$anonfun$toScala$1(TwoWayCaches.scala:50)
    at scala.reflect.runtime.TwoWayCaches$TwoWayCache.toScala(TwoWayCaches.scala:46)
    at scala.reflect.runtime.JavaMirrors$JavaMirror.toScala(JavaMirrors.scala:128)
    at scala.reflect.runtime.JavaMirrors$JavaMirror.classToScala(JavaMirrors.scala:1019)
    at scala.reflect.runtime.JavaMirrors$JavaMirror.classSymbol(JavaMirrors.scala:231)
    at scala.reflect.runtime.JavaMirrors$JavaMirror.classSymbol(JavaMirrors.scala:68)

这里发生了什么?什么样的对象没有运行时类?

【问题讨论】:

  • val ll = new (String =&gt; Int) { override def apply(v1: String): Int = v1.toInt } 有效。 SO 不是报告错误的合适场所。应在此处报告错误:github.com/scala/bug/issues
  • 非常感谢@DmytroMitin,我不确定它是否故意缺少符号(我怀疑在这种情况下它会抛出 ReflectionError 而不是 AssertionError)。很想报告并观看回复:)

标签: scala scala-reflect scala-2.12


【解决方案1】:

相对于 2.11,lambda 的编码在 2.12 中发生了变化。见the 2.12.0 release notes

以前,lambda 的编码方式基本上等同于 Dmytro Mitin 的答案,即扩展 FunctionN 并具有 apply 方法的对象。

在 Java 8 中引入了 lambda,但与 Scala 的方式略有不同。调用java.lang.invoke.LambdaMetaFactory.metafactory bootstrap 方法,该方法将在运行时创建一个类,其中一个方法调用另一个类中的方法。

在 Scala 2.12 中,Scala lambda 的编码已移动以匹配 Java 实现(可能是因为 JVM(尤其是 HotSpot)识别出LambdaMetaFactory.metafactory 结果的结果是一个 lambda,并进行了比其他方式更多的优化)。但是,运行时生成的类似乎无法映射到 Scala 类,导致 Scala 的反射爆炸。

编辑:在 Dmytro Mitin 的评论之后,对我来说唯一明显的解决方法是将 lambdas 手动编码为 FunctionN 的实例:

def delambdafy[Result](ll: () => Result): Function0[Result] = new Function0[Result] { def apply(): Result = ll() }

def delambdafy[In, Result](ll: In => Result): Function1[In, Result] =
  new Function1[In, Result] { def apply(in: In): Result = ll(in) }

对于必要的arities等等......

为 REPL 调整您的代码,这很有效:

// Entering paste mode (ctrl-D to finish)

import scala.reflect.runtime.universe._         // Effectively what is being imported with your Spark import

def delambdafy[In, Result](ll: In => Result): Function1[In, Result] = new Function1[In, Result] { def apply(in: In): Result = ll(in) }

val ll: String => Int = { v: String => v.toInt }

val clazz = delambdafy(ll).getClass

val symbol = runtimeMirror(clazz.getClassLoader).classSymbol(clazz)

// Exiting paste mode, now interpreting.

import scala.reflect.runtime.universe._
delambdafy: [In, Result](ll: In => Result)In => Result
ll: String => Int = $$Lambda$5162/1405722527@114a083b
clazz: Class[_ <: String => Int] = class $anon$1
symbol: reflect.runtime.universe.ClassSymbol = $anon

$anon 是否对您有用取决于您实际尝试做什么,这导致您提出这个问题。

【讨论】:

    猜你喜欢
    • 2016-10-01
    • 2015-04-21
    • 2014-07-29
    • 2015-12-10
    • 2012-08-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多