Почему отражение среды выполнения Scala больше не работает на лямбда-выражении?

Следующий простой код:

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)

Что здесь происходит? Какой объект не имеет класса времени выполнения?


person tribbloid    schedule 16.12.2019    source источник
comment
val ll = new (String => Int) { override def apply(v1: String): Int = v1.toInt } работает. SO - неподходящее место для сообщений об ошибках. Об ошибках следует сообщать здесь: github.com/scala/bug/issues   -  person Dmytro Mitin    schedule 16.12.2019
comment
Большое спасибо @DmytroMitin, я не уверен, что в нем намеренно отсутствует символ (я подозреваю, что нет, поскольку в этом случае он выдаст ReflectionError вместо AssertionError). Будет любопытно отчитаться и посмотреть ответ :)   -  person tribbloid    schedule 16.12.2019


Ответы (1)


Кодировка лямбда изменилась в 2.12 по сравнению с 2.11. См. примечания к выпуску 2.12.0.

Раньше лямбда-выражения были закодированы способом, который в основном эквивалентен ответу Дмитрия Митина, как объекты, которые расширяют FunctionN и имеют метод apply.

В Java 8 появились лямбда-выражения, но немного иначе, чем в Scala. Вызывается метод начальной загрузки java.lang.invoke.LambdaMetaFactory.metafactory, который будет создавать класс во время выполнения с одним методом, который вызывает метод в другом классе.

В Scala 2.12 кодировка лямбда-выражений Scala была изменена, чтобы соответствовать реализации Java (вероятно, потому, что JVM (особенно HotSpot) распознает, что результатом LambdaMetaFactory.metafactory является лямбда, и выполняет большую оптимизацию, чем в противном случае). Однако результирующий класс во время выполнения, по-видимому, не может быть сопоставлен с классом Scala, что приводит к тому, что отражение Scala взрывается.

РЕДАКТИРОВАТЬ: единственным очевидным для меня исправлением будет ручное кодирование лямбда-выражений как экземпляров 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) }

И так далее по необходимым ариям...

Адаптируя ваш код для 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, зависит от того, что вы на самом деле пытались сделать, что привело вас к этому вопросу.

person Levi Ramsey    schedule 16.12.2019