Reflecting the Values of Class Members in Scala
In this article, I will explore how to get the value of vals, vars and methods with no arguments using reflection. I’ll present three solutions for this problem: using Java reflection, using Scala reflection and using Scala macros (compile-time reflection).
The function I want to implement takes two arguments: an object and the name of the member for which the value should be retrieved.
As I started exploring this problem in the context of a validation library, the most important use case is getting the value for the fields of a case class.
However, the implementation also works with regular classes and it can obtain the value for vals, vars and methods that are declared in the class itself or inherited.
Note: no error handling is implemented in order to keep the code simple. The implementations are behaving differently for invalid input.
Java Reflection
The implementation using Java reflection is pretty straight forward. It obtains a Method
object by name and invokes it on obj
.
What might come as a surprise is that we are searching for methods, even though the implementation should also work with vals and vars. This is because Scala is generating default accessors for any val or var, except the ones that are defined as private[this]
(for vars, mutators are also generated).
Scala Reflection
Using Scala reflection leads to an implementation which is similar to the one based on Java reflection API. One important difference is that it is relying on implicit type tags and class tags generated by the compiler in order to preserve additional type info for erased types and types which are specific to Scala.
The TypeTag
context bound is needed when executing typeOf[T]
and the ClassTag
one is used by m.reflect(obj)
.
Scala Macros
As macros are executed at compile time, the code doesn’t know what values are passed as arguments. The macro has to generate code that knows how to return the value for any member name. The code that is generated for the Person
case class looks like the following. I’m constructing a map of functions and not actual values because I want methods to be executed only when the value is needed (I want to avoid executing methods with side-effects or methods that are performing complex computations).
In order to achieve this result for a generic type, the code selects all the public methods with no parameters or type arguments that are not constructors. Once it has a list with these members, it constructs a map that binds the member name to a function that returns the value of the member. I’m using member.name.decodedName
in order to replace all occurrences of $op_names by corresponding operator symbols (foo_$plus$eq
decodes to foo_+=
)