Professional Documents
Culture Documents
Reflection in Scala
Íñigo Mediavilla
Abstract This article will explain the two main reflection APIs available for
Scala: runtime and compile-time reflection. It will describe the limitations of the
previously existent solutions and the goals that motivated them, their main fea-
tures as well as some of the challenges faced during their implementation. It will
end up with a review of their limitations.
Keywords Scala · Reflection · Macro
1 Introduction
Due to the more powerful nature of runtime reflection this article will focus
on the runtime reflection API provided by Scala. However in the end, this article
provides an introduction to Scala’s macro system, a facility for doing compile-time
reflection that although less powerful than its runtime counterpart, allows to solve
some of the problems that have been traditionally solved with runtime reflection,
but preserving type-safety.
2 Runtime reflection
Initially Scala didn’t have an specialized reflection API, however as a language that
was executed inside the JVM, Scala provided the possibility to use Java’s reflection
API. This possibility came with some serious deficiences. Java’s reflection API
had no understanding of Scala’s model what meant that Java’s reflection didn’t
provide means to manipulate scala-specific metaobjects. On top of that Java’s API
has limitations such as erasure of generic types at runtime or a design that lacks
encapsulation making it impossible to redefine its default implementation.
2.1.1 Limitations
Fig. 1 Example of trying to instantiate a type alias with Java Reflection API.
1 package com.test
2 /* Example on trying to do a lookup for a type alias */
3 class Person(name:String)
4 type Human = Person
5 // The following throws ClassNotFoundException
6 val human = Class.forName("com.test.Human");
Fig. 2 Example of trying to verify if two clases extend the same superclass.
1 class E {
2 type T
3 val x: Option[T] = None
4 }
5 class C extends E
6 class D extends C
7 val c = new C { type T = String }
8 val d = new D { type T = String }
9 val shouldBeTrue = c.getClass.isAssignableFrom(d.getClass)
10 assert(shouldBeTrue) // Throws AssertionError
2.2 Principles
The principles required for a good reflection library have been defined by [2] and
can be summarized in three: encapsulation, ontology correspondance and strat-
ification. This section will describe these principles, relating them to the initial
situation of Scala.
2.2.1 Encapsulation
locally. However there might be other scenarios where reflection can be useful that
don’t fit exactly with the same requirements as the basic scenario. As an example,
reflection can be used for debugging or for doing instrospection in code executed
in a remote environment.
According to this principle the API that allows reflection should be defined in
a way that its implementation can be replaced transparently for its clients.
Java violates this principle by providing the meta-class Class as a class in-
stead of an interface making it difficult to redefine the implementation of the core
reflection package.
2.2.2 Stratification
The designers of Scala’s API having these principles in mind designed an API with
the following properties:
– All the concepts available in the language have a representation on the meta-
language that can be analyzed through reflection.
– Access to the metaobjects is done through mirrors and not through the objects
themselves, thus preserving the principle of stratification.
– The metaobjects are represented by interfaces and not by classes what allows
for code that works for different implementations of reflection as long as they
respect these interfaces (encapsulation).
Along the section we will see examples of how to use the API and how the
principles described above are respected. We will also see how some of the limita-
tions like erasure of generic types are solved. However, it will start by describing
quickly the concepts of universe and mirror to then see how to use them to perform
introspection on the code.
Reflection in Scala 5
Universe is the entry point to the reflection API. It allows the developer to access
any element of the API. The concept of universe serves mainly to differentiate
between runtime and compile-time reflection. According to the required temporal
correspondance [2], reflection on computation and reflection on code needs to be
done separately. Universes enforce this restriction that is further emphasized by
the different operations available through their corresponding mirrors.
Mirrors are the objects that provide any information or operation that is defined
in the API. Access to mirrors is facilitated either by their universe in the case
of high level mirrors or by their parent mirror in the case of specialized mirrors.
When looking at the runtime reflection API the root mirror is the runtime mir-
ror, the ClassLoader mirror is accessed through the root and Invoker mirrors are
respectively accessed through the ClassLoader.
Scala’s reflection API uses mirrors as the principle for performing introspec-
tion. As we’ve seen previously an API based on mirrors provides like any other
reflection API access to representations of the entities of a programming language,
this representation allows to manipulate and extract information about the objects
represented. What makes a mirror API special is that the access to the representa-
tions is not done through the object themselves but through an external function
that given the object returns its representation. This kind of API has the ben-
efits that the meta-level operations are encapsulated from their implementations
and that base-level functionality has no knowledge of the reflection infraestructure.
In the case of Java objects, Scala has no mean to extract any additional in-
formation to the one provided by the JVM and Java’s reflection API. In this case
Scala just provides a wrapper over Java’s reflection API in the form of a function
that takes a Java object and returns its mirror. This means that when a Scala
developer introspects an object which class belongs to a Java library, it will ob-
tain a mirror that is just a wrapper over the methods provided by java.lang.reflect.
In the case of Scala objects, the mechanism is a bit more complex. Since the
information provided by JVM’s bytecode is not complete enough, Scala’s manages
to insert additional information about the objects in the .class file by using the
fact that according to the Java bytecode specification [5] the compiler can generate
additional attribute in class files, that are ignored by the JVM. The Scala compiler
uses this right to insert an attribute ”ScalaSig” into the .class files generated
that contains many of the information needed by the reflection API, including
information about generic types.
Accessing a type A basic reflection operation is accessing the metaclass for a given
class. In most reflective languages metaclasses contain the methods that allow
6 Íñigo Mediavilla
1 import scala.reflect.runtime.universe._
2 val mytype : reflect.runtime.universe.Type = typeOf[Set[Boolean]]
The second case is a bit different, let’s say that we want to define a method that
given an instance of any instance object it returns the representation of its type.
We’ve already seen that the method typeOf obtains the representation for a type
defined explicitly. However, how do we pass to typeOf the type of our instance?
Well, doing this in Scala requires just the definition of a polymorphic method
whose polymorphic parameter is the type of the object passed. The problem is
that this doesn’t work since in the JVM the information about generics is erased
at runtime. Fortunately Scala has found a way to overcome this limitation by
a technique that includes additional metainformation about generic types in the
.class file when requested by the code. The way to request that is to add the
implicit parameter TypeTag for the parametric type that we want to inspect at
runtime. When the compiler sees this implicit parameter in the signature of a
method it adds the needed information so it’s accessible at runtime.
1 import scala.reflect.runtime.universe._
2
3 object ReflectionUtils {
4 def getType[T: TypeTag](obj: T) = typeOf[T]
5 }
6
7 val mytype = ReflectionUtils.getType(List(1,2,3)) // contains type List[Int]
The third is case when we want to get the representation for a polymorphic
type that has a non-defined parameter (e.g List[A]) is a bit more complex, we will
not explain it here but it requires a different kind of tag called WeakTypeTag.
Create an instance of the type Once the representation for a type has been recov-
ered, we can do things like instantiating an object of a type as we can see in figure
5
Reflection in Scala 7
Listing the members of the type The members of a type are accessed simply
through its members field. The additional benefit is that it is a field of type Scala
collection what means that they can be manipulated with the common functions
for collections like map, filter, .. . For example the code in figure 6 allows to list
the fields of the type list that are not private.
1 ReflectionUtils.getType(List(1,2,3)).members.filter(!_.isPrivate)
Modifying the value of a field Fields are a type of member. To extract the specific
field from the list of members and then we modify it calling ”set”. It’s important
to note that in the same way that final fields are not modifiable on Java, fields
declared with val are not mutable in Scala so calling set is only allowed for var
fields.
Advantages The resulting API has interesting properties. As described in the be-
ginning of the chapter the main principles of reflective APIs [2] are respected,
8 Íñigo Mediavilla
meaning that the API provides meta-level representations of all the concepts avail-
able in the language, that the implementation is encapsulated making it replace-
able and that it is stratified in a way that it can be disabled easily.
On top of these properties the new API is capable of overcoming Java’s limita-
tion to access generic types at runtime. Due to Scala’s functional nature, paramet-
ric polymorphism is widely used in Scala: to give an example collections, functions
and asynchrounous primitives like Futures are all implemented as polymorphic
types. The solution to the previous limitation gives an API on which developers
know the generic types at runtime and can do specific manipulations based on
them. This achievement have been made possible thanks to the addition of some
specific metainformation to the .class files generated by the Scala compiler that
includes the parametric types.
Another additional benefit of the new reflection in Scala is that it has forced
the designers of Scala to focus on exposing some of the internals of the compiler.
The main classes in the “scala.reflect” package provide a safe way to do intro-
spection for non expert developers. However this effor has also given advanced
developers the possibility to do more complex manipulations thanks to classes
like scala.tools.reflect.Toolbox that allow to compile AST trees, typecheck them
and evaluate expressions at runtime. For example figure 8 shows how to build an
expression and execute at runtime.
1 import scala.reflect.runtime.universe._
2 import scala.tools.reflect.ToolBox
3 val cm = runtimeMirror(getClass.getClassLoader)
4 val toolbox = cm.mkToolBox()
5 val expr = reify { println ("Hello world") }
6 toolbox.eval(expr.tree) // This prints Hello world on stdout
Finally, even though Scala allows to do runtime code manipulation, this kind
of manipulation is done in a really low level and it implies loosing the warranties
provided by the type system a really high price to pay. Macros provide a type safe
way to do code manipulations, but they can only be applied at compile time and
Scala doesn’t provide any other mecanism to do type safe runtime manipulations
in a way similar to languages like MetaOcaml. The low level of the API for code
manipulations contrasts with the syntax provided by nemerle [9] and MetaOcaml,
this problem will be mitigated with the arrival of quasiquotes [3] but for the
moment they are not available in the language.
3 Macros
Even though Scala compile time metaprogramming capabilities’ come under the
name of macros, due to the completely difference nature of Scala and Scheme both
macro systems have many things that separate them. In fact, Scala macros are
inspired by the macro system available at [9]. The following section will explain
the basic concepts behind Scheme’s macros to then compare them to Scala’s,
explaining the reasons that justify their differences and how Scala has taken a
different approach to solve some of problems in the implementation of macros.
3.2 Hygiene
”Macro’s are reflective tools that operate on the structure of a program” [8] they
were born in the context of the languages of the lisp family and they represent a
powerful technique to modify the semantics of a language. However its implemen-
tation in a language conveys some serious challenges. Probably one of the most
important challenges is the preservation of what is called ”hygiene”. A macro is
considered hygienic if its expansion doesn’t generate the capture of any identifier.
In languages that don’t provide macro hygiene, developers need to use tricks such
as obfuscation or temporary symbol creation to avoid variable capture.
Languages like Scheme already come with mechanisms that ensure the hygiene
of macros in the language. An example of an interesting algorithm for enforcing
hygiene that has been implemented in Scheme is described in [8].
10 Íñigo Mediavilla
On the implementation of macros Scala also had to face the problem on how to
ensure hygiene. However, in the case of Scala the number of levels of the reflective
tower is limited to two: the execution level and the macro expansion level that is
executed as a part of the phase of compilation. Due to Scala’s statically typed and
non-homoiconic nature a solution with multiple level wasn’t appropiate. Under
these circumstances the solution proposed by [8] wasn’t appropiate so Scala opted
for another solution to the problem of hygiene, a macro called reify.
Reify is just a macro that given an expression, converts it into a reified ex-
pression that can be manipulated. At runtime this makes a more involved kind
of reflection but also a more powerful one that allows the generation of arbitrary
expression. When reify is executed at macroexpansion time, it not only gives a
convenient way to build expressions but it also ensures the hygiene of the macros
it generates. The reason why reify ensures hygiene is that the variables used in
the expression passed to reify are bound at definition site, so since all identifiers
and types are resolved at runtime (for the macros context macroexpansion time),
nothing that can be done at runtime can affect their binding.
3.5 An example
The compile time reflection API will in the future provide multiple types of macros
but in Scala 2.10 it comes with only one form, Def macros. Def macros are normal
Scala functions with the particularity that they are called by the compiler at
compile time. The responsability of a Def macro is to take an abstract syntax tree
for every argument passed to the macro and generate another abstract syntax tree
as an output or signal an error. Notice that this possibility to signal an error with
an appropiate message serves as a really useful way to verify incorrect programs
that extends the type system verification.
This article only intended to give an introduction into the principles behind
Scala’s macros so it won’t try to explain all the details on how to implement macros
(for those interested [4] is a good reference). The following example will only help
show the basic definition of a macro.
In 9 the macro definition hello is a function without implementation that only
has the keywords macro followed by the name of its implementation method impl.
The macro function already defines the signature of the macro seen from the
perspective of the developer who will call the macro. The implementation treats
the input arguments as Scala expressions (AST representations of the inputs) and
Reflection in Scala 11
returns another expression that contains the rewriting that happens as a result of
the execution of the macro. As we can see the input and output expressions of the
implementation are parameterized by the types of the input arguments and the
result of the macro definition. The context object that appears in the signature
of impl provides information about the environment of the call to the macro and
gives the utility methods needed to build the resulting expression.
1 object Macros {
2 def hello(input:String) : Unit = macro impl
3 def impl(c: Context)(input: c.Expr[String]) : c.Expr[Unit] =
4 c.universe.reify(println("Hello " +
5 c.Expr[String](input.tree).splice + " world!" ) )
6 }
7 Macros.hello(" reflective ") // prints Hello reflective world!
Macros are a fairly new addition to the set of tools provided by Scala. They
are a really powerful technique that has already make its way into some really
popular Scala frameworks and has demonstrated its many applications. However
as a feature it has many rough edges.
In terms of code manipulation it uses the same low level API than runtime
reflection, what means that writing macros is difficult, implies a lot of boiler plate
and requires a great deal of knowledge of the compiler internals. Quasiquotes [3]
will alleviate the problem with boilerplate code but knowledge of the compiler will
still be required. To add more difficulty debugging macros is a daunting task.
On top of that, Def macros only have access to the local context of the method,
therefore they cannot have into account information related to the class from where
they are called, even less from the whole structure of the program. For example,
sometimes it would be useful to define transformations that affect all the methods
in a program according to certain criteria but due to the local nature of Def macros
they don’t allow this kind of transformation.
Finally, the current macro implementation in the compiler requires that macro
implementations be compiled before they are used. Practically this means that
when developing a project with macros using sbt, the standard build tool for
scala, developers need to go into the trouble of creating two subprojects one for
the macros and another for the code.
4 Conclusion
This article has introduced the initial situation for doing reflection on Scala as
well as the result of the development of the new reflection API. It has explained
some of the basic principles for creating reflection APIs and how the developers
12 Íñigo Mediavilla
of Scala used this principles embodied by the concept of mirrors to develope the
new reflection system of their language. The resulting API has been explained
with some examples of their use. Finally that last section has explained how the
fact of adding the reflection API, opened the door to a kind of compile time
metaprogramming known as Scala macros. The section has provided an overview
of some of the implications of adding macros to the language including the problem
of macro hygiene and how Scala macros provide a solution to this problem.
References