为什么 Scala 运行时反射不再适用于 lambda?

tri*_*oid 0 scala scala-reflect scala-2.12

下面简单的代码:

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)
  }
}

Run Code Online (Sandbox Code Playgroud)

曾经在 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)
Run Code Online (Sandbox Code Playgroud)

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

Lev*_*sey 5

相对于 2.11,2.12 中 lambda 的编码发生了变化。请参阅2.12.0 发行说明

之前,lambda 的编码方式基本上相当于 Dmytro Mitin 的答案,即扩展 aFunctionN并具有apply方法的对象。

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

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

编辑:对我来说唯一明显的修复是FunctionN在 Dmytro Mitin 发表评论之后手动将 lambda 编码为 的实例:

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) }
Run Code Online (Sandbox Code Playgroud)

等等必要的参数......

调整您的代码以适应 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
Run Code Online (Sandbox Code Playgroud)

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