Using Scala Reflection

by Stephane Micheloud, December 2011

[Home]
[Back]

In this article we present several use cases of the new Scala Reflection API introduced in version 2.10.0 of the Scala software distribution.

That new mirror-based reflection API (see [2]) for the Scala programming language gives access to meta-level informations about a Scala program.

Scala Manifests

A Scala manifest is an opaque type descriptor used by the Scala compiler to preserve run-time type information (RTTI). The class hierarchy looks as follows:

OptManifest <— ClassManifest <— Manifest <— AnyValManifest
             \
              \— NoManifest
  1. Manifests give access to the Java reflection API (eg. method getMethods) without the need for instanciating a value of the instrumented type:
    object methods extends App {
      def printMethods[T](t: T) { // requires instance
        val meths = t.getClass.getMethods
        println(meths take 5 mkString "\n")
      }
      def printMethods1(name: String) { // low-level
        val meths = Class.forName(name).getMethods
        println(meths take 5 mkString "\n")
      }
      def printMethods2[T: Manifest] { // no instance
        val meths = manifest[T].erasure.getMethods
        println(meths take 5 mkString "\n")
      }
      printMethods(Some(""))
      printMethods1("scala.Some")
      printMethods2[Some[_]]
    }
  2. Subtyping relations can be tested using Scala manifests:
    object subtypes extends App {
      def printSubtype[T, U](t: T, u: U) {
        val mt = Manifest.classType(t.getClass)
        val mu = Manifest.classType(u.getClass)
        println(mt <:< mu)
      }
      def printSubtype2[T: Manifest, U: Manifest] {
        println(manifest[T] <:< manifest[U])
      }
      // arrays are invariant
      printSubtype(Array(0), Array[AnyVal](1)) // false
      printSubtype(List(""), List[AnyRef](""))  // true
      printSubtype((Seq(0), 1), (Seq[AnyVal](), 2))  // true
    
      printSubtype2[Array[Int], Array[AnyVal]]
      printSubtype2[List[String], List[AnyRef]]
      printSubtype2[(Seq[Int], Int), (Seq[AnyVal], Int)]
    }
  3. Finally manifests are necessary to create native Arrays whose element's class is not known at compile time.
    object arrays extends App {
      class Side(n: Int) {
        override def toString = "Side "+n
      }
      class Shape[T: Manifest](n: Int) {
        val sides = new Array[T](n)
        try {
          val ctr = manifest[T].erasure.getConstructor(classOf[Int])
          for (i <- 0 until n)
            sides(i) = ctr.newInstance(i:java.lang.Integer).asInstanceOf[T]
        }
        catch { case _ => }
      }
      val square = new Shape[Side](4)
      println(square.sides mkString "\n")
    }

Symbols

Compiler internal symbols

  1. package examples.ex1
    class Foo(x: Int, val y: Int) {
      def this(x: Int) = this(x, 0)
      def <<(z: Int) {}
    }
    object Main extends App with SymbolPrinter {
      printSymbol(classOf[Foo])
      printSymbol(classOf[scala.Option[_]])
      printSymbol("scala.Symbol")
    }
    package examples.ex1
    
    import scala.reflect.api.Modifier
    import scala.reflect.runtime.Mirror
    
    trait SymbolPrinter {
      import Mirror._, Mirror.definitions._
      def printSymbol[T](clazz: Class[T]) {
        printSymbol(classToSymbol(clazz))
      }
      def printSymbol(name: String) {
        printSymbol(classWithName(name))
      }
      def printSymbol(sym: Symbol) {
        val buf = new StringBuilder()
        var indent = 0
        // ...
        def traverse(sym: Symbol) {
          sym.tpe match {
            case MethodType(pt, rt) =>
              addFlags(sym)
              buf append "def " append symbolName(sym)
              addParameters(pt)
              buf append ": " append rt
            // ...
          }
        }
        traverse(sym)
        println(buf.toString)
      }
    }
    Running the above program produces the following output:
    class Foo(x: Int, val y: Int) { 
      def Foo(x: Int): examples.ex1.Foo
      def <<(z: Int): Unit
    }
    class Option extends Product with Serializable { 
      abstract def isEmpty: Boolean
      def isDefined: Boolean
      abstract def get: A
      // ...
    }
    object Option extends Serializable { 
      implicit def option2Iterable[A](xo: Option[A]): Iterable[A]
      def apply[A](x: A): Option[A]
      private def readResolve(): Object
    }
    class Symbol private (val name: String) extends Serializable { 
      override def toString(): String
      @throws(classOf[java.io.ObjectStreamException]) private def readResolve(): Any
      override def hashCode(): Int
      override def equals(other: Any): Boolean
    }
    object Symbol extends UniquenessCache[String,Symbol] with Serializable { 
      protected def valueFromKey(name: String): Symbol
      protected def keyFromValue(sym: Symbol): Option[String]
      private def readResolve(): Object
    }

Trees

  1. package examples.ex2
    
    import scala.reflect.Code
    
    object Main extends App with SourcePrinter {
      printSource(Code.lift {
        5
      }.tree)
      var y = 3
      printSource(Code.lift {
       5+y
      }.tree)
      printSource(Code.lift {
        if (y < 0) -y else y
      }.tree)
      printSource(Code.lift {
        if (y < 0) -y else { val z = 5+y; y-z }
      }.tree)
      printSource(Code.lift {
        (x: Int) => x + y + 1
      }.tree)
      printSource(Code.lift {
        (x: Int) => {
          var z = 0
          while (z < y) z += 1 
          x + z + 1
        }
      }.tree)
    }
    package examples.ex2
    
    import scala.reflect.api.Modifier
    import scala.reflect.mirror._
    import scala.reflect.runtime.Mirror.ToolBox
    
    trait SourcePrinter {
      private val toolbox = new ToolBox()
    
      def printSource(tree: Tree) {
        val ttree = toolbox.typeCheck(tree)
        val buf = new StringBuilder
        // ...
        var indent = 0
        def traverse(tree: Tree) {
          tree match {
            case Apply(fun, args) =>
              traverse(fun)
              if (args.length == 1) {
                buf append " "
                traverse(args.head)
              }
              else {
                buf append "("
                args foreach traverse
                buf append ")"
              }
            case Assign(lhs, rhs) =>
              traverse(lhs)
              buf append " = "
              traverse(rhs)
            case Block(List(), expr) =>
              traverse(expr)
            // ...
          }
        }
        traverse(ttree)
        println(buf.toString)
      }
    
    }
    The output generated by the above code is the following:
    5
    5 + Main.y
    if (Main.y < 0) -Main.y else Main.y
    if (Main.y < 0) -Main.y else {
      val z: Int = 5 + Main.y
      Main.y - z
    }
    (x: Int) => x + Main.y + 1
    (x: Int) => {
      var z: Int = 0
      /*label*/def while$1(): Unit = if (z < Main.y) {
      z = z + 1
      while$1()
      } else ()
      while$1()
      x + z + 1
    }
  2. 
        

References

  1. The Scala Language Specification (SLS), Version 2.8
    Martin Odersky, November 2010
  2. Yohann Coppel, January 2008
  3. Using Java Reflection
    Glen McCluskey, January 1998

About the Author

Stephane's Picture
Stéphane Micheloud is a senior software engineer. He holds a Ph.D in computer science from EPFL and a M.Sc in computer science from ETHZ. At EPFL he worked on distributed programming and advanced compiler techniques and participated for over six years to the Scala project. Previously he was professor in computer science at HES-SO // Valais in Sierre, Switzerland.
[Top]

Other Articles