Home > Mobile >  Why this map function does not give traits' simple names
Why this map function does not give traits' simple names

Time:01-20

I try to get names of all trait a class extends using getInterfaces which returns an array of trait's names. When I manually access each member of the array, the method getName returns simple names like this

trait A
trait B

class C() extends A, B

val c = C()
val arr = c.getClass.getInterfaces
arr(0).getName // : String = A
arr(1).getName // : String = B

However, when I use map function on arr. The resulting array contains a cryptic version of trait's names

arr.map(t => t.getName) // : Array[String] = Array(repl$.rs$line$1$A, repl$.rs$line$2$B)

The goal of this question is not about how to get the resulting array that contains simple names (for that purpose, I can just use arr.map(t => t.getSimpleName).) What I'm curious about is that why accessing array manually and using a map do not yield a compatible result. Am I wrong to think that both ways are equivalent?

CodePudding user response:

I believe you run things in Scala REPL or Ammonite.

When you define:

trait A
trait B

class C() extends A, B

classes A, B and C aren't defined in top level of root package. REPL creates some isolated environment, compiles the code and loads the results into some inner "anonymous" namespace.

Except this is not true. Where this bytecode was created is reflected in class name. So apparently there was something similar (not necessarily identical) to

// repl$ suggest object
object repl {

  // .rs sound like nested object(?)
  object rs {
   
    // $line sounds like nested class
    class line { /* ... */ }
  
    // $line$1 sounds like the first anonymous instance of line
    new line { trait A }
    // import from `above

    // $line$2 sounds like the second anonymous instance of line
    new line { trait B }
    // import from above
    //...
  }
}

which was made because of how scoping works in REPL: new line creates a new scope with previous definitions seen and new added (possibly overshadowing some old definition). This could be achieved by creating a new piece of code as code of new anonymous class, compiling it, reading into classpath, instantiating and importing its content. Byt putting each new line into separate class REPL is able to compile and run things in steps, without waiting for you to tell it that the script is completed and closed.

When you are accessing class names with runtime reflection you are seeing the artifacts of how things are being evaluated. One path might go trough REPLs prettifiers which hide such things, while the other bypass them so you see the raw value as JVM sees it.

CodePudding user response:

The problem is not with map rather with Array, especially its toString method (which is one among the many reasons for not using Array).

Actually, in this case it is even worse since the REPL does some weird things to try to pretty-print Arrays which in this case didn't work well (and, IMHO, just add to the confusion)

You can fix this problem calling mkString directly like:

val arr = c.getClass.getInterfaces
val result = arr.map(t => t.getName)
val text = result.mkString("[", ", ", "]")
println(text)

However, I would rather suggest just not using Array at all, instead convert it to a proper collection (e.g. List) as soon as possible like:

val interfaces = c.getClass.getInterfaces.toList
interfaces .map(t => t.getName)

Note: About the other reasons for not using Arrays

  • They are mutable.
  • Thet are invariant.
  • They are not part of the collections hierarchy thus you can't use them on generic methods (well, you actually can but that requires more tricks).
  • Their equals is by reference instead of by value.
  •  Tags:  
  • Related