1. Introduction

As of Scala 2.10, the reflection feature of the Scala language allows us to inspect our programs both at compile and run time. In this tutorial, we’re going to study the runtime reflection API.

Before going further, it’s worth mentioning that the reflection API has many specialized terms. So, in this article, we aren’t going to explore the whole API, but we’ll try to run some common use cases of reflection API.

2. What Is Reflection?

The ability of a programming language to be its own metalanguage is called reflection. Reflection is a valuable language feature that facilitates meta-programming. In other words, reflective programming or reflection is the ability of a process to examine, introspect, and modify its own structure and behavior.

There are two types of reflection, which differ depending on when they execute:

  1. Runtime reflection
  2. Compile-time reflection

Another name for compile-time reflection is macro programming. In this article, we’ll focus on runtime reflection.

We can also use reflection to create an instance of a class. This feature is often used by dependency injection frameworks such as Spring or Guice, which are quite common in Java but almost nonexistent in Scala.

3. Runtime Reflection

When we’re in a runtime environment, reflection is the ability of a program to inspect and possibly even modify itself at runtime. This means that we have access to lots of metadata about all the objects we manipulate.

Using runtime reflection has its own costs. For example, reflection can be a dangerous feature. Not only can it easily throw an exception, but even worse, reflection lets us bypass all the abstractions and safeguards set by the language.

So, if we have an instance of some object at runtime, we can inspect the type of that object, including generic types, and access its members – even private members – and manipulate them, or we can initialize a new object. Also, we can invoke members of that object.

4. Java Reflection API

One might ask: While we have the Java runtime reflection API, what is the need for using the Scala reflection library? The answer is that Scala has its own capabilities on top of Java, so the Java reflection API isn’t sufficient to work with Scala types, especially when we have type erasure. Sometimes, it leads to incorrect information. So, we suggest using Scala reflection by default:

personObject.getClass.getMethods.map(_.getName)

We can also use reflection to call methods on an object. Let’s look up a method by name and then invoke it:

classOf[Person].getDeclaredMethod("prettyPrint").invoke(personObject)

5. Scala Reflection API Dependency

First, we need to import the dependency in our build.sbt file:

libraryDependencies += "org.scala-lang" % "scala-reflect" % "2.13.8"

6. Importing Runtime Universe

The Scala reflection API has two universes:

  1. scala.reflect.runtime.universe for runtime reflection
  2. scala.reflect.macros.Universe for compile-time reflection

As we’re only going to study the runtime universe, we’ll import the runtime universe as ru:

import scala.reflect.runtime.{universe => ru}

Now, we can access the Scala reflection API from now on by referring to ru.

7. Obtaining Type Information at Runtime

Let’s assume we’ve defined a trait:

trait PersonInterface {
  val name: String
  val age: Int

  def prettyPrint: String
}

Also, let’s say the Person class implements this trait:

case class Person(name: String, age: Int) extends 
  PersonInterface {
    def prettyPrint =
      s"""
      |Person {
      | name: "$name",
      | age: $age
      |}
      |""".stripMargin

  private val password = "123"
}

Now, we want to get type information of these two types using Scala reflection API.

8. Getting Type of Scala Types

To get the universe type (ru.Type) of a Scala type, we can use the ru.typeOf API method:

val intType = ru.typeOf[Int]

val personType = ru.typeOf[Person]

val listType = ru.typeOf[List[_]]

9. Getting Type of Scala Instances

What if we have an instance of a type and want to get a universe.Type for that? It looks like there’s no built-in method to do that. The recommended way is to write our own method for it:

def getInstanceType[T: TypeTag](obj: T) = ru.typeOf[T]

The Scala compiler will supply our getInstanceType method with an implicit for TypeTag[T] so we can then obtain the universe type of that object by calling ru.typeOf[T].

Scala generic types that are present at compile-time are erased at runtime due to type erasure. TypeTags are a way of having access to erased type information at runtime. A TypeTag[T] encapsulates the runtime type representation of some type T. So, they give us access to erased types at runtime.

With getInstanceType, we can now extract the type of the instance:

val intInstance = 1
val personInstance = Person("John", 20)
val listInstance = List(1, 2, 3)

val intType = getInstanceType(intInstance)
val personType = getInstanceType(personInstance)
val listType = getInstanceType(listInstance)

10. Getting Type of Scala Types by Their Names

First, we should create a runtime mirror by providing the current class loader:

val mirror: ru.Mirror = ru.runtimeMirror(getClass.getClassLoader)

Mirrors are windows to all information provided by reflections. We have two types of mirrors:

  1. Classloader Mirrors — used to translate names to symbols
  2. Invoker Mirrors — used to invoke our invocations

In this example, we’ll use this type of mirror to translate the name of the Person class to its corresponding class symbol using the staticClass method.

Let’s obtain the ClassSymbol of the Person class by calling the staticClass method:

val classSymbol: ru.ClassSymbol = mirror.staticClass("Person")

Now, we can access all type information corresponding to the Person class by calling ClassSymbol#info method:

val personType = classSymbol.info

11. Getting Methods Declared Inside a Type

Now that we have a universe.Type for our type, we can get the methods defined on it by using the decls method:

val personInterfaceType: ru.MemberScope = ru.typeOf[PersonInterface].decls
val personClassType = ru.typeOf[Person].decls

As the ru.MemberScope is a subtype of Iterable[Symbol], we can traverse all symbols declared inside that type:

personInterfaceType.foreach{ symbol =>
  println(s"declared member: ${symbol.name}, type signature: ${symbol.typeSignature.toString}")
}
// declared member: name, type signature: String
// declared member: age, type signature: Int
// declared member: salary, type signature: (role: String): Double
// declared member: prettyPrint, type signature: String

The ru.Type#decls method returns the list of declared members of a type that are defined directly. We can access all methods defined for a type either directly or indirectly by using the ru.Type#member method.

12. How to Instantiate a Scala Class at Runtime

In this section, we want to instantiate Person at runtime by providing the name of the class.

First of all, let’s try to obtain the ClassSymbol of our Person class:

val mirror: ru.Mirror = ru.runtimeMirror(getClass.getClassLoader)
val classSymbol: ru.ClassSymbol = mirror.staticClass("Person")

Now, we can access the primary constructor of the Person class. Let’s convert it to a MethodSymbol:

val consMethodSymbol = classSymbol.primaryConstructor.asMethod

As a rule of thumb, whenever we need to invoke something or we want to do something on an object at runtime, we should do that via a mirror. So, as we want to call the primary constructor, we should have a corresponding mirror for the MethodSymbol of the constructor. In order to do that, we obtain the ClassMirror and then, via that mirror, access the MethodMirror for the constructor:

val classMirror = mirror.reflectClass(classSymbol)
val consMethodMirror = classMirror.reflectConstructor(consMethodSymbol)

Now that we have a MethodMirror for the primary constructor, we can execute the constructor by calling its apply method:

val result = consMethodMirror.apply("John", 20)
assert(result == Person("John", 20))

13. Run a Method at Runtime

So, now that we have an instance of Person, we want to run one of its methods, called prettyPrint, at runtime. First of all, in order to do that, we should convert the prettyPrint to a MethodSymbol:

val methodSymbol = classSymbol.info.decl(ru.TermName("prettyPrint")).asMethod

Next, we should obtain the mirror of the result instance (remember that to perform invocation, we need a mirror):

val instanceMirror = mirror.reflect(result)

Now, we can reflect the prettyPrint method using the reflectMethod operation on the InstanceMirror:

val method = instanceMirror.reflectMethod(methodSymbol)

Finally, we can run the apply method to invoke the prettyPrint:

assert(
  method.apply() ==
    """
    |Person {
    | name: "John",
    | age: 20
    |}
    |""".stripMargin
)

14. Accessing Private Fields

In this section, we’ll access the private password field of the Person class and change its value:

val passwordTermSymbol =
  classSymbol.info
    .decl(ru.TermName("password"))
    .asTerm
val passwordFiledMirror = instanceMirror.reflectField(passwordTermSymbol)
 
assert(passwordFiledMirror.get == "123")
passwordFiledMirror.set("321")
assert(passwordFiledMirror.get == "321")

15. Reification

Reification is the process of taking an abstract concept and making it concrete. For example, suppose we have an idea for an application. When we write that application, we’re reifying our idea. So, in metaprogramming, the transformation of a computer program into data using an explicit data model is reification. After reification of a computer program, we can easily manipulate that program. So, we can say that reflection is the ability to reify Scala expressions into abstract syntax trees.

When we reify a concept, we can pass it as data to other functions to transform it. For example, in procedural programming, a procedure is not data. We can’t pass a procedure to another procedure. In functional programming, like Scala, we can treat functions as data, and we can pass functions to other functions. So, we can say that Scala reified the concept of a function and made the concept of function as data that we can pass around and manipulate.

Now, let’s try to reify some simple expressions:

ru.reify(1 to 10 foreach println)
// Expr[Unit](Predef.intWrapper(1).to(10).foreach(((x) => Predef.println(x))))

ru.reify(5)
// Expr[Int(5)](5)

val expr = ru.reify { val a = 5; val b = 2; (a * 2 + 1) ^ b }
// Expr[Int]({
// val a = 5;
// val b = 2;
// a.$times(2).$plus(1).$up(b)
// })

Then, we can convert any expression to the abstract syntax tree by calling the Expr#tree method:

val tree: Tree = expr.tree

By converting an expression into a tree, we can transform that into another tree, which is beyond the scope of this article. But at least, let’s inspect the raw tree using ru.showRaw operator:

println(ru.showRaw(tree))

Let’s see what this code prints (we’ve pretty-printed it manually here for readability):

Expr(
  Block(
    List(
      ValDef(Modifiers(), TermName("a"), TypeTree(), Literal(Constant(5))),
      ValDef(Modifiers(), TermName("b"), TypeTree(), Literal(Constant(2)))
    ),
    Apply(
      Select(
        Apply(
          Select(
            Apply(
              Select(Ident(TermName("a")), TermName("$times")),
              List(Literal(Constant(2)))
            ),
            TermName("$plus")
          ),
          List(Literal(Constant(1)))
        ),
        TermName("$up")
      ),
      List(Ident(TermName("b")))
    )
  )
)

16. Conclusion

In this article, we tried to explore some of the most common use cases of Scala’s reflection API for runtime reflection. After reading this article, we’re ready to read its documentation to get ready to provide solutions to more sophisticated problems.

As always, the full source code for the examples is available over on GitHub.