Scala Reflection — Taming the Beast

Gonzalo Isaza
11 min readOct 4, 2017

--

Being fairly new to Scala, I would not be surprised if some of the information I provide in this blog won’t be the most efficient, but hopefully it will show how to make the ramp up effort of learning reflection less steep. When I started reading about reflection, my first thought was: “this is quite complicated”. The fact that the reflection story has changed in multiple released versions of the Scala language shows it has been a challenging issue in the language design.

I know my writing is not the best, so bear with me when you find paragraphs with writing deficiencies. The code is pasted as images throughout the blog, but you can find the full source code at the end, from where you will be able to copy and paste if you wish.

There are different aspects of reflection, depending on what you want to do. For me, there are three basic cases: One, where I have an object, and I want to access properties and methods via reflection. Another when I have a type (or a class if you want) and want to find the same information. Finally, when I have a type or an object and want to check what that object is (is it an option, a collection, a primitive type, etc.). I find the first easier. The last case is not covered in this blog. Of course there are many more cases due to the richness of the language (traits, types, etc.). These I won’t touch in this blog.

Java classes can be used in Scala code. Being Java reflection less daunting, my first reaction was to use Java reflection wherever I could, but this proved not to be the best choice. Type erasure bit me at some point, and I ended up running in circles trying to find the type of some generic argument, just to obtain “type A” where A could be anything. Not good. My advice: Use only Scala reflection, and build a set of methods to help make common operations easy. If you are not familiar with type erasure, there is plenty of information in the web. If you need some information on it, you may want to start with this link: What is type erasure? There are many more.

In this blog, I will show the following actions using reflection:

· From an object, find the primary constructor and find the types of the parameters I need to invoke that constructor.
· Find a property in an object and check the type.
· Find the generic types of generic classes (the T in List[T]) using a type with lots of nested generics.
· Invoke methods on an object
· Instantiate an object invoking its constructor

The code in my article is unacceptable for production. For example, if I know a method returns a list with elements, I will use the head without verifying if the list is empty, and other similar omissions. Some Scala statements could be condensed, but I did not take that route in order to make them easier to follow.

To start, I will create a copy of an object using reflection. The class: For the exercise, I created a simple class shown below. Member “deepThoughts” has nothing to do with thought, telepathy, or anything of the sort. It is just a member with lots of nested types to show how to get to all of them. phone is a member of address, which is completely unrealistic, but it is convenient for some other blogs I’m planning to write, so don’t pay much attention to that poor design here. phoneType is an Option[String]. In real life it will probably be an enumeration, but I want to intentionally stay away from those here. There are complete articles about enum’s in Scala. That is a whole topic of study and choices.

Scala reflection works mostly with Symbols. Normally the type navigation is closely tied to Symbols. A symbol can be a generic argument, parameters in a method, a property, a class, etc. The Type class is also important. Types lead to symbols and symbols include members of class Type. Usually you can move from one to the other easily. The first step is to obtain a symbol or type of the class you want to work with. The symbol of an object can be obtained as follows:

Calling getClass() will return the class of the object. This class object is useful with Java methods, but not in Scala. But it is the way to obtain the symbol. So what is a mirror? It’s an object that will assist with reflection (I don’t know if the name is intended to be humorous, but with such an arid topic, I did not find humor in it). A runtime mirror is just one of several types of mirrors. You will need other type of mirrors depending on the action you need to accomplish. In this blog you will see the use of three types of mirrors. In the code above, variable mirror is a runtime mirror.

The Symbol is the entry door to the information in the class. From it, I can find the declarations (properties and methods), constructors, generic arguments and other things. Symbol is the base of a set of other types: TypeSymbol, ClassSymbols, and others. A Symbol has a member called info (declared in SymbolApi), which has information about the parent classes, methods, and more. I will now find the primary constructor for the class. This can be done from the declarations, by filtering the information I need, like this:

The decls property contains all the methods and properties. If I call asMethod on a property, it will throw an exception. Therefore, I first need to check the declaration refers to a method before invoking asMethod on it. Properties are term symbols. All the methods in the class are included in decls, so only the primaryConstructor is filtered out.

The next step is to find what parameters the constructor takes, in order to prepare them and invoke the constructor. To find these, I will use the type signature of the method. Property typeSignature has very useful information. This property is used frequently when working with reflection, not only with methods, but also with types.

paramLists is a List[List[Symbol]]. Why multiple lists? Normally a method receives a list of parameters. In Scala there are curried methods which receive multiple parameter lists, and therefore a list of lists is appropriate. If my method is a “vanilla” method with parameters, these will be returned in the head of paramLists.

The code line above will return a list of lists, where the head has 4 symbols: name, age, address, and deepThoughts. These are parameter symbols. As you can see, the way to navigate reflection waters is through symbols: class symbols, term symbols, parameter symbols, and more. Since my exercise is to create a copy of my original object, I will get the value for each property in my original object and invoke the constructor using these values. Yes, I could invoke the copy method and do this in one shot, but the purpose of my article is to show different aspects of reflection.

To get the property from the object, I will need to find the “getter” for each property, and invoke it to get a value. When I check the list of declarations with the debugger I find two declarations with the same name (not quite) for each property. Two for name, 2 for address, and so on. One of the names of the declarations ends in a space and the other one does not. One is a method for the getter, and the other one (the one ending in a space — e.g “name “) is a property. The getter can be identified because it’s a method, and the method is a getter (_.isGetter is true). There are many other differences: For example the property is not public in my specific case.

In order to invoke a method in an object, an instanceMirror is required. The first mirror we obtained was a runtime mirror, and so, from it, we need to obtain an instance mirror for the object we are working with. This instance mirror will allow us to invoke the members. The way I can exercise reflection on an object is through instance mirrors. The following code gets the values for the properties required in the constructor call, from the original object. Variable params was declared above.

Notice how getterMethod is a symbol, and I need to call asMethod to “cast” it to a MethodSymbol.

I assign the result of invoking the mirror to a val, and the next line has just “propValue” to return that value as the result expression. This is just convenient during debugging since I can inspect the value before some method returns. It’s my personal style.

The above code creates the instance mirror, and then invokes the getter. Notice the empty parameter list at the end of the line. The getter does not take parameters. When we invoke the constructor, the list will not be empty. For now, we have the 4 values required to invoke the constructor. So let’s finish the task by invoking it.

To obtain the properties we used an instance mirror, created with an object. But to invoke the constructor there is no object. We want to instantiate one. Therefore a new type of mirror is used. A ClassMirror. The class mirror is created with the class symbol (symbols again). So the final lines for creating a copy of the original object are:

paramValues is a list, but we need to send the individual elements as arguments when invoking the constructor. This is accomplished with the use of :_* which sends the parameters individually. Different methods work with different type of symbols, and the right type must be sent. This is done by invoking asXXX method. These invocations have to be done carefully. If you invoke asMethod on something which is not a method, it will throw. The same thing will happen with all asXXX invocations.

In the code above, aqCopy is a replica of aq, just as if I had done aq.copy().

I just scratched the surface so far, and complexity increases with more complex scenarios. Now, I will find the type for deepThoughts. This helped me find the types of generic classes at deep levels of nesting.

PART 2: What is the type of deepThoughts

deepThoughts is a property I created intentionally as a sample of a type with lots of nested generics. I will find the type of this property and all its nested generics using reflection. There are 2 ways to search for the type: From the property in the class, or from the parameter in the constructor. Had my choice been to use Java reflection, I would have hit lots of places where the types could not be found due to type erasure.

Using the parameter in the constructor

The types of the generic arguments can be found using the class symbol info shown before. Specifically using classSymbol.info.typeArgs. The method to navigate the generic types recursively is the following:

And finally, this method is applied on the deepThoughts parameter, like this:

The image below shows the value for dtTypeSymbolDescription. As you can see, the code was able to navigate through all the generic types nested into deepThoughts without being affected by type erasure issues.

From the property in the class

I will now repeat the exercise using a different source: the property in the class. In order to get the symbol I will be working with, I will need to extract it from the information in info.decls as before, and then list the type of the return value of the getter.

This code stores in dtSymbol the information for the getter. Method listTypes requires a type as a parameter. This type should be the type of the return value of the getter. dtSymbol.typeSignature is a method type. It is the type of the getter itself. But what I need is the type of the return value. That is obtained from the resultType of a method type. The value of dtDesc is the same type obtained before.

Conclusion

In this article I showed some basic actions normally done with reflection, but I barely scratched the surface. I believe that one of the big challenges in some cases is finding a way to obtain the symbol of an object or type, in order to start working with it. The fact that Java and Scala classes coexist will also present challenges in real life situations. In my next blog I will show how to get annotation information using reflection.

Reflection is not easy, and it has a lot of different challenges. I hope this blog will help you solve some of the common problems. Find the types and members of a class, instantiate an object when you know the class type, and invoke methods on objects using reflection.

And to close, I provide the complete code used in this blog:

package BlogReflection  import scala.reflect.runtime.{universe => ru}  import ru._  case class Aquaintance (                         name: String,                         age: Int,                         address: Address,                         deepThoughts: Option[List[List[Option[(String, Double)]]]])  case class Address(street1: String, sreet2: Option[String], city: String, state: String, phone: Option[Phone])  case class Phone(areaCode: String, number: String, phoneType: Option[String])  object demo extends App {  def listTypes(elemSymbol: Type) : String = {    val name = elemSymbol.typeSymbol.name.toString()    val generics = elemSymbol.typeArgs match {      case Nil => ""      case aList => {        val genericDescriptions: String = aList.map(listTypes).mkString(", ")        s"[$genericDescriptions]"      }    }    s"$name$generics"  }  val aq = Aquaintance("Joe Doe", 25,    Address("345 Happiness Lane", None, "Pleasantville", "SW",      Some(Phone("555", "555-1212", Some("smartphone")))),    Some(List(List(Some("hello", 123.456)))))  val mirror = runtimeMirror(aq.getClass.getClassLoader())  val instMirror = mirror.reflect(aq)  val aqSymbol = mirror.classSymbol(aq.getClass())  val primCtor = aqSymbol.info.decls.filter(m => m.isMethod && m.asMethod.isPrimaryConstructor).head  val params = primCtor.typeSignature.paramLists.head  val paramValues = params.map(p => {    val getterMethod = aqSymbol.info.decls.filter(m => m.isMethod && m.name == p.name && m.asMethod.isGetter).head    val propValue = instMirror.reflectMethod(getterMethod.asMethod)()    propValue  })  val classMirror = mirror.reflectClass(aqSymbol.asClass)  val aqCopy = classMirror.reflectConstructor(primCtor.asMethod)(paramValues :_*)  val dtParam = params.find(p => "deepThoughts".equals(p.name.toString())).get  val dtTypeSymbolDescription = listTypes(dtParam.typeSignature)  println(dtTypeSymbolDescription)  // get the type information using the property (through the getter) in the class  val dtSymbol = aqSymbol.info.decls.find(m => m.isMethod && "deepThoughts".equals(m.name.toString())).get  val dtDesc = listTypes(dtSymbol.typeSignature.resultType)  println(dtDesc)  println("Done!")}

Enjoy!

Gonzalo Isaza
github alias: giposse
StackOverflow alias: DDRider62

��h�[de�

--

--