Inconsistency with scala reflection library

2019-07-19 11:58发布

I'm having trouble understanding why using scala's runtime reflection in 2.11.1 gives me seemingly inconsistent results.

I am trying to inspect the type of a field contained in a java object, like this one:

import java.util.List;
import java.util.ArrayList;

public class Example {
  private List<Integer> listOfInts;

  public Example () {
    listOfInts = new ArrayList<Integer>();
  }  
}

Now suppose I have a scala program that tries to reason about the type of the field inside "Example:"

import java.lang.Class
import java.lang.reflect.Field
import java.util.List
import scala.reflect.runtime.{ universe => ru }

object Inspect extends scala.App {
  val example = new Example 
  val cls = example.getClass
  val listfield = cls.getDeclaredField("listOfInts")

  println(isListType(listfield)) // prints false 
  println(isListType(listfield)) // prints true, as do all subsequent calls

  def isListType (field: Field): Boolean = {
    /*
      A function that returns whether the type of the field is a list.
      Based on examples at http://docs.scala-lang.org/overviews/reflection/environment-universes-mirrors.html
    */
    val fieldcls = field.getType

    val mirror: ru.Mirror = ru.runtimeMirror(getClass.getClassLoader)
    val fieldsym: ru.ClassSymbol = mirror.classSymbol(fieldcls)
    val fieldtype: ru.Type = fieldsym.toType 

    (fieldtype <:< ru.typeOf[List[_]])
  }  
}

In this particular code snippet, the first call to isListType returns false, and the second returns true. If I switch the type operator from <:< to =:=, the first call returns true, and the second false.

I have a similar function in a larger code body, and have found that even when the function is part of a static object, this behavior occurs. This does not happen when using unparameterized classes. While I intended for the function to be pure, this is obviously not the case. Further experimentation has shown that there is some persistent state held somewhere. If I replace the isListType function with straightline code, I get this:

...
val example = new Example   
val cls = example.getClass
val listfield = cls.getDeclaredField("listOfInts")
val fieldcls = listfield.getType

val mirror: ru.Mirror = ru.runtimeMirror(getClass.getClassLoader)
val fieldsym: ru.ClassSymbol = mirror.classSymbol(fieldcls)
val fieldtype: ru.Type = fieldsym.toType 

println(fieldtype <:< ru.typeOf[List[_]]) // prints false
println(fieldtype <:< ru.typeOf[List[_]]) // prints false

but if I reassign to field type after the <:< operator, I get this:

// replace as under the fieldsym assignment
var fieldtype: ru.Type = fieldsym.toType 
println(fieldtype <:< ru.typeOf[List[_]]) // prints false

fieldtype = fieldsym.toType 
println(fieldtype <:< ru.typeOf[List[_]]) // prints true

while reassigning to field type before the <:< operator gives this:

// replace as under the fieldsym assignment
var fieldtype: ru.Type = fieldsym.toType 
fieldtype = fieldsym.toType 

println(fieldtype <:< ru.typeOf[List[_]]) // prints false
println(fieldtype <:< ru.typeOf[List[_]]) // prints false

Does anyone understand what I'm doing wrong here, or at least have a way around this?

2条回答
一纸荒年 Trace。
2楼-- · 2019-07-19 12:00

The reflection library is based on the compiler, which is a crying shame. People should demand better. Anyway, this is just how it is.

Here's a sample ticket from almost two years ago. https://issues.scala-lang.org/browse/SI-6826

there is some persistent state held somewhere

There's almost nothing but.

Addendum: for a truly dizzying experience, page through a selection of the 355 open reflection tickets.

查看更多
再贱就再见
3楼-- · 2019-07-19 12:23

I don't know about mixing Java reflection and Scala reflection, but symbols do need initialization, as I remember past issues around lack thereof. Surely the re-assignment is not relevant, but maybe after the first <:< the state of the Symbol changes.

usage:

scala> import reflect.runtime._ ; import universe._
import reflect.runtime._
import universe._

scala> typeOf[jex.Example]
res0: reflect.runtime.universe.Type = jex.Example

scala> .declarations
warning: there was one deprecation warning; re-run with -deprecation for details
res1: reflect.runtime.universe.MemberScope = SynchronizedOps(variable listOfInts, constructor Example)

scala> typeOf[jex.Example] member (TermName("listOfInts"))
res2: reflect.runtime.universe.Symbol = variable listOfInts

scala> .typeSignature
res3: reflect.runtime.universe.Type = java.util.List[Integer]

Sorry if I'm too distracted to read your question correctly.

Here's my attempt to reproduce:

scala> import reflect.runtime._ ; import universe._
import reflect.runtime._
import universe._

scala> classOf[jex.Example].getDeclaredField("listOfInts").getType
res0: Class[_] = interface java.util.List

scala> currentMirror classSymbol res0 toType
warning: there was one feature warning; re-run with -feature for details
res1: reflect.runtime.universe.Type = java.util.List

scala> .<:<(typeOf[List[_]])
res2: Boolean = false

scala> currentMirror classSymbol res0 toType
warning: there was one feature warning; re-run with -feature for details
res3: reflect.runtime.universe.Type = java.util.List[E]

scala> .<:<(typeOf[List[_]])
res4: Boolean = false

Another attempt:

scala> import reflect.runtime._ ; import universe._
import reflect.runtime._
import universe._

scala> val x = new jex.Example
x: jex.Example = jex.Example@1efed156

scala> x.getClass getDeclaredField "listOfInts" getType
warning: there was one feature warning; re-run with -feature for details
res0: Class[_] = interface java.util.List

scala> val m = runtimeMirror(getClass.getClassLoader)
m: reflect.runtime.universe.Mirror = JavaMirror with scala.tools.nsc.interpreter.IMain$TranslatingClassLoader@1ffd0e4b of type class scala.tools.nsc.interpreter.IMain$TranslatingClassLoader with classpath [(memory)] and parent being scala.reflect.internal.util.ScalaClassLoader$URLClassLoader@3b084709 of type class scala.reflect.internal.util.ScalaClassLoader$URLClassLoader with classpath [file:/home/apm/jdk-8/jdk1.8.0_11/jre/lib/resources.jar,file:/home/apm/jdk-8/jdk1.8.0_11/jre/lib/rt.jar,file:/home/apm/jdk-8/jdk1.8.0_11/jre/lib/jsse.jar,file:/home/apm/jdk-8/jdk1.8.0_11/jre/lib/jce.jar,file:/home/apm/jdk-8/jdk1.8.0_11/jre/lib/charsets.jar,file:/home/apm/jdk-8/jdk1.8.0_11/jre/lib/jfr.jar,file:/home/apm/scala-2.11.2/lib/akka-actor_2.11-2.3.4.jar,file:/home/apm/scala-2.11.2/lib/config-1.2...
scala> val s = m classSymbol res0
s: reflect.runtime.universe.ClassSymbol = trait List

scala> var t = s.toType
t: reflect.runtime.universe.Type = java.util.List[E]

scala> t <:< typeOf[List[_]]
res1: Boolean = false

scala> t = s.toType
t: reflect.runtime.universe.Type = java.util.List[E]

scala> t <:< typeOf[List[_]]
res2: Boolean = false

OK, so I verified your test class. If the REPL behaves differently, it's probably due to side effects because of printing results.

So adding this to your test

val fieldsym: ru.ClassSymbol = mirror.classSymbol(fieldcls)
println(fieldsym)
val fieldtype: ru.Type = fieldsym.toType
println(fieldtype)

fixes the issue.

apm@mara:~/tmp$ vi jex/inspect.scala
apm@mara:~/tmp$ scalac jex/inspect.scala && scala jex.Inspect
false
true
apm@mara:~/tmp$ vi jex/inspect.scala
apm@mara:~/tmp$ scalac jex/inspect.scala && scala jex.Inspect
trait List
java.util.List[E]
true
trait List
java.util.List[E]
true

I don't know if there's a lesson about "initialize your Syms!"

"At Syms, an educated consumer is our best customer."

查看更多
登录 后发表回答