Post on 10-May-2015
description
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 1 of 22
Reflection in Scala 2.10+
Whats, Whys and Hows
Walter Cazzola
Dipartimento di Informatica
Università degli Studi di Milano
e-mail: cazzola@di.unimi.it
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 2 of 22
Computational Reflection.General Definitions.
Computational reflection can be intuitively defined as:
}the activity done by a SW system to represent and
manipulate its own structure and behavior~. [1]
The reflective activity is done analogously to the usual system
activity.
[1] D. Bobrow, R. G. Gabriel and J. L. White. CLOS in Context.
In OOP: the CLOS Perspective. MIT Press, 1993.
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 3 of 22
Computational Reflection.Characterization of a Reflective System.
Structural and Behavioral Reflection
– the behavioral reflection allows the program of monitoring and
manipulating its own computation;
– the structural reflection allows the program of inspecting and al-
tering its own structure
Introspection and Intercession.
– introspection permits to observe the structure and behavior of
the application, whereas
– intercession permits to alter its structure and behavior
When the meta-level entities exist:
– compile-time: the causal connection is implicit, base-level and meta-
levels are merged together during a preprocessing phase;
– load-time: in this case the causal connection behaves as in the case,
reflection takes place at compile-time.
– run-time: the causal connection is explicit and must be maintained
by an entities super-parties, e.g., by the virtual machine or by the
run-time environment;
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 3 of 22
Computational Reflection.Characterization of a Reflective System.
Structural and Behavioral Reflection
– the behavioral reflection allows the program of monitoring and
manipulating its own computation;
– the structural reflection allows the program of inspecting and al-
tering its own structure
Introspection and Intercession.
– introspection permits to observe the structure and behavior of
the application, whereas
– intercession permits to alter its structure and behavior
When the meta-level entities exist:
– compile-time: the causal connection is implicit, base-level and meta-
levels are merged together during a preprocessing phase;
– load-time: in this case the causal connection behaves as in the case,
reflection takes place at compile-time.
– run-time: the causal connection is explicit and must be maintained
by an entities super-parties, e.g., by the virtual machine or by the
run-time environment;
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 4 of 22
Computational Reflection.Es. To Enrich the Behavior of a Method Call.
Each method call is logged into a file.
Hello logMetadoLog()doInvoke()
logMeta
hello
sayHello() invoke(sayHello)
meta
meta
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 4 of 22
Computational Reflection.Es. To Enrich the Behavior of a Method Call.
Each method call is logged into a file.
Hello logMetadoLog()doInvoke()
logMeta
hello
sayHello() invoke(sayHello)
meta
meta
sayHello()
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 4 of 22
Computational Reflection.Es. To Enrich the Behavior of a Method Call.
Each method call is logged into a file.
Hello logMetadoLog()doInvoke()
logMeta
hello
sayHello() invoke(sayHello)
meta
metainvoke()
sayHello()
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 4 of 22
Computational Reflection.Es. To Enrich the Behavior of a Method Call.
Each method call is logged into a file.
å
logFile
Hello logMetadoLog()doInvoke()
logMeta
hello
sayHello() invoke(sayHello)
meta
doLog()
metainvoke()
sayHello()
Method sayHello on Hello object
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 4 of 22
Computational Reflection.Es. To Enrich the Behavior of a Method Call.
Each method call is logged into a file.
å
logFile
console
Hello logMetadoLog()doInvoke()
logMeta
hello
sayHello() invoke(sayHello)
meta
doLog()
metainvoke()
sayHello()
doInvoke()
Method sayHello on Hello object
Hello World!!!
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 5 of 22
Scala Reflection.The Java Case.
Java provides reflection via the java.lang.reflection library
– pretty good for introspection activities but
– quite limited on intercession (only behavioral manipulation)
Scala <2.10 inherits reflection from Java.
So why should we need an ad hoc implementation?
– some types are wrongly reified due to type erasure
scala> case class A[T]scala> println(A[String].isInstanceOf[A[String]])true
scala> println(A[String].isInstanceOf[A[Int]])true
– the whole spectrum of types, modifiers and constructs is not cov-
ered (e.g., implicit, path dependent and abstract types)
– limited intercession, no structural reflection at all.
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 5 of 22
Scala Reflection.The Java Case.
Java provides reflection via the java.lang.reflection library
– pretty good for introspection activities but
– quite limited on intercession (only behavioral manipulation)
Scala <2.10 inherits reflection from Java.
So why should we need an ad hoc implementation?
– some types are wrongly reified due to type erasure
scala> case class A[T]scala> println(A[String].isInstanceOf[A[String]])true
scala> println(A[String].isInstanceOf[A[Int]])true
– the whole spectrum of types, modifiers and constructs is not cov-
ered (e.g., implicit, path dependent and abstract types)
– limited intercession, no structural reflection at all.
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 5 of 22
Scala Reflection.The Java Case.
Java provides reflection via the java.lang.reflection library
– pretty good for introspection activities but
– quite limited on intercession (only behavioral manipulation)
Scala <2.10 inherits reflection from Java.
So why should we need an ad hoc implementation?
– some types are wrongly reified due to type erasure
scala> case class A[T]scala> println(A[String].isInstanceOf[A[String]])true
scala> println(A[String].isInstanceOf[A[Int]])true
– the whole spectrum of types, modifiers and constructs is not cov-
ered (e.g., implicit, path dependent and abstract types)
– limited intercession, no structural reflection at all.
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 6 of 22
Reflection in ScalaCore Data Structures
Who can access to the necessary data?
The compiler.
Trees, Symbols and Types
[17:52]cazzola@surtur:~>scalac -Xshow-phasesphase name id description---------- -- -----------
parser 1 parse source into ASTs, perform simple desugaringnamer 2 resolve names, attach symbols to named treestyper 4 the meat and potatoes: type the trees
pickler 8 serialize symbol tables
Reflection in Scala regards
– ASTs and types
– all the information about them can be provided by the compiler
These are just few out the 30 phases the compiler performs.
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 6 of 22
Reflection in ScalaCore Data Structures
Who can access to the necessary data? The compiler.
Trees, Symbols and Types
[17:52]cazzola@surtur:~>scalac -Xshow-phasesphase name id description---------- -- -----------
parser 1 parse source into ASTs, perform simple desugaringnamer 2 resolve names, attach symbols to named treestyper 4 the meat and potatoes: type the trees
pickler 8 serialize symbol tables
Reflection in Scala regards
– ASTs and types
– all the information about them can be provided by the compiler
These are just few out the 30 phases the compiler performs.
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 7 of 22
Scala Reflection-Yshow-trees
object Test {println("Hello World!")}
[18:05]cazzola@surtur:~>scalac -Xprint:parser -Yshow-trees helloworld.scala[[syntax trees at end of parser]] // Scala source: helloworld.scalaPackageDef("<empty>"ModuleDef(0 "Test"Template("scala"."AnyRef" // parentsValDef(private "_" <tpt> <empty>)DefDef(0 "<init>" [] List(Nil) <tpt>Block(Apply(super."<init>"Nil
)())
)Apply("println""Hello World!"
))
))
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 8 of 22
Scala ReflectionshowRaw
scala> import scala.reflect.runtime.{universe => ru}import scala.reflect.runtime.{universe=>ru}
scala> ru.reify{ object Test { println("Hello World!") } }res0: reflect.runtime.universe.Expr[Unit] =Expr[Unit]({object Test extends AnyRef {def <init>() = {super.<init>();()
};Predef.println("Hello World!")
};()
})
scala> ru.showRaw(res0.tree)res1: String = Block(List(ModuleDef(Modifiers(), newTermName("Test"),
Template(List(Ident(newTypeName("AnyRef"))), emptyValDef,List(DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(),
Block(List(Apply(
Select(Super(This(tpnme.EMPTY), tpnme.EMPTY),nme.CONSTRUCTOR), List())), Literal(Constant(()))
)), Apply(Select(Ident(scala.Predef),newTermName("println")), List(Literal(Constant("Hello World!"))))
)))), Literal(Constant(())))
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 9 of 22
Reflection in ScalaSummary of the Trees
Trees are created naked by Parser.
Both definitions and references (expressed as ASTs) get their
symbols filled in by Namer (tree.symbol).
When creating symbols, Namer also creates their completers,
lazy thunks that know how to populate symbol types (symbol.info).
Typer inspects trees, uses their symbols to transform trees and
assign types to them (tree.tpe).
Shortly afterwards Pickler kicks in and serializes reachable sym-
bols along with their types into ScalaSignature annotations.
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 10 of 22
Scala ReflectionUniverses & Mirrors
Universes are environments that pack together trees, symbolsand their types.
– Compiler (scala.tools.nsc.Global) is a universe.
– Reflection runtime (scala.reflect.runtime.universe) is a universe
too.
– Macro context (scala.reflect.macros.Context) holds a reference
to a universe.
Mirrors abstract population of symbol tables.
Each universe can have multiple mirrors, which can share symbolswith each other within their parent universe.
– Compiler loads symbols from pickles using its own *.class parser. It
has only one mirror, the rootMirror.
– Reflective mirror uses Java reflection to load and parse ScalaSigna-tures. Every classloader corresponds to its own mirror created
with ru.runtimeMirror(classloader).
– Macro context refers to the compiler’s symbol table.
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 10 of 22
Scala ReflectionUniverses & Mirrors
Universes are environments that pack together trees, symbolsand their types.
– Compiler (scala.tools.nsc.Global) is a universe.
– Reflection runtime (scala.reflect.runtime.universe) is a universe
too.
– Macro context (scala.reflect.macros.Context) holds a reference
to a universe.
Mirrors abstract population of symbol tables.
Each universe can have multiple mirrors, which can share symbolswith each other within their parent universe.
– Compiler loads symbols from pickles using its own *.class parser. It
has only one mirror, the rootMirror.
– Reflective mirror uses Java reflection to load and parse ScalaSigna-tures. Every classloader corresponds to its own mirror created
with ru.runtimeMirror(classloader).
– Macro context refers to the compiler’s symbol table.
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 11 of 22
Scala ReflectionEntry Points
Using a universe depends on your scenario.
– You can play with compiler’s universe (aka global) in REPL’s :powermode.
– With runtime reflection you typically go through the Mirror in-
terface, e.g. scala.reflect.runtime.currentMirror, then cm.reflectand then you can get/set fields, invoke methods, etc.
– In a macro context, you import c.universe._ and can use imported
factories to create trees and types (avoid to create symbols).
All universe artifacts are path-dependent on their universe.
scala> import scala.reflect.runtime.{universe => ru}import scala.reflect.runtime.{universe=>ru}
scala> ru.reify(2.toString)res1: reflect.runtime.universe.Expr[String] = Expr[String](2.toString())
With runtime reflection, there is only one universe.
With macros it is more complicated. To pass artifacts around is
necessary to piggyback the universe as well.
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 11 of 22
Scala ReflectionEntry Points
Using a universe depends on your scenario.
– You can play with compiler’s universe (aka global) in REPL’s :powermode.
– With runtime reflection you typically go through the Mirror in-
terface, e.g. scala.reflect.runtime.currentMirror, then cm.reflectand then you can get/set fields, invoke methods, etc.
– In a macro context, you import c.universe._ and can use imported
factories to create trees and types (avoid to create symbols).
All universe artifacts are path-dependent on their universe.
scala> import scala.reflect.runtime.{universe => ru}import scala.reflect.runtime.{universe=>ru}
scala> ru.reify(2.toString)res1: reflect.runtime.universe.Expr[String] = Expr[String](2.toString())
With runtime reflection, there is only one universe.
With macros it is more complicated. To pass artifacts around is
necessary to piggyback the universe as well.
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 12 of 22
Scala ReflectionInspect Members
scala> import scala.reflect.runtime.{universe => ru}import scala.reflect.runtime.{universe=>ru}
scala> trait X { def foo: String }defined trait X
scala> ru.typeOf[X]res0: reflect.runtime.universe.Type = X
scala> res0.membersres1: reflect.runtime.universe.MemberScope =
Scopes(method $asInstanceOf, method $isInstanceOf, method synchronized,method ##, method !=, method ==, method ne, method eq, constructor Object,method notifyAll, method notify, method clone, method getClass,method hashCode, method toString, method equals, method wait,method finalize, method asInstanceOf, method isInstanceOf, method !=,method ==, method foo)
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 12 of 22
Scala ReflectionInspect Members
scala> import scala.reflect.runtime.{universe => ru}import scala.reflect.runtime.{universe=>ru}
scala> trait X { def foo: String }defined trait X
scala> ru.typeOf[X]res0: reflect.runtime.universe.Type = X
scala> res0.membersres1: reflect.runtime.universe.MemberScope =
Scopes(method $asInstanceOf, method $isInstanceOf, method synchronized,method ##, method !=, method ==, method ne, method eq, constructor Object,method notifyAll, method notify, method clone, method getClass,method hashCode, method toString, method equals, method wait,method finalize, method asInstanceOf, method isInstanceOf, method !=,method ==, method foo)
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 12 of 22
Scala ReflectionInspect Members
scala> import scala.reflect.runtime.{universe => ru}import scala.reflect.runtime.{universe=>ru}
scala> trait X { def foo: String }defined trait X
scala> ru.typeOf[X]res0: reflect.runtime.universe.Type = X
scala> res0.membersres1: reflect.runtime.universe.MemberScope =
Scopes(method $asInstanceOf, method $isInstanceOf, method synchronized,method ##, method !=, method ==, method ne, method eq, constructor Object,method notifyAll, method notify, method clone, method getClass,method hashCode, method toString, method equals, method wait,method finalize, method asInstanceOf, method isInstanceOf, method !=,method ==, method foo)
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 13 of 22
Scala ReflectionAnalyze and Invoke Members
Get the type, the constructor and its parameters
scala> case class Person(name: String, age: Int)defined class Person
scala> val personType = typeOf[Person]personType: reflect.runtime.universe.Type = Person
scala> val ctor = personType.member(nme.CONSTRUCTOR)ctor: reflect.runtime.universe.Symbol = constructor Person
scala> val args = ctor.asMethod.paramss.head map {p=>(p.name.decoded, p.typeSignature)}args:List[(String, reflect.runtime.universe.Type)]=List((name,String), (age,scala.Int))
Create a new instance (via the mirror)
scala> val classMirror = currentMirror.reflectClass(personType.typeSymbol.asClass)classMirror: reflect.runtime.universe.ClassMirror = class mirror for Person
scala> val ctor = personType.declaration(nme.CONSTRUCTOR).asMethodctor: reflect.runtime.universe.MethodSymbol = constructor Person
scala> classMirror.reflectConstructor(ctor).apply("Joe", 73)res1: Any = Person(Joe,73)
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 13 of 22
Scala ReflectionAnalyze and Invoke Members
Get the type, the constructor and its parameters
scala> case class Person(name: String, age: Int)defined class Person
scala> val personType = typeOf[Person]personType: reflect.runtime.universe.Type = Person
scala> val ctor = personType.member(nme.CONSTRUCTOR)ctor: reflect.runtime.universe.Symbol = constructor Person
scala> val args = ctor.asMethod.paramss.head map {p=>(p.name.decoded, p.typeSignature)}args:List[(String, reflect.runtime.universe.Type)]=List((name,String), (age,scala.Int))
Create a new instance (via the mirror)
scala> val classMirror = currentMirror.reflectClass(personType.typeSymbol.asClass)classMirror: reflect.runtime.universe.ClassMirror = class mirror for Person
scala> val ctor = personType.declaration(nme.CONSTRUCTOR).asMethodctor: reflect.runtime.universe.MethodSymbol = constructor Person
scala> classMirror.reflectConstructor(ctor).apply("Joe", 73)res1: Any = Person(Joe,73)
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 13 of 22
Scala ReflectionAnalyze and Invoke Members
Get the type, the constructor and its parameters
scala> case class Person(name: String, age: Int)defined class Person
scala> val personType = typeOf[Person]personType: reflect.runtime.universe.Type = Person
scala> val ctor = personType.member(nme.CONSTRUCTOR)ctor: reflect.runtime.universe.Symbol = constructor Person
scala> val args = ctor.asMethod.paramss.head map {p=>(p.name.decoded, p.typeSignature)}args:List[(String, reflect.runtime.universe.Type)]=List((name,String), (age,scala.Int))
Create a new instance (via the mirror)
scala> val classMirror = currentMirror.reflectClass(personType.typeSymbol.asClass)classMirror: reflect.runtime.universe.ClassMirror = class mirror for Person
scala> val ctor = personType.declaration(nme.CONSTRUCTOR).asMethodctor: reflect.runtime.universe.MethodSymbol = constructor Person
scala> classMirror.reflectConstructor(ctor).apply("Joe", 73)res1: Any = Person(Joe,73)
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 14 of 22
Scala ReflectionDefeat Type Erasure
The compiler implements the type erasure
scala> case class A[T]scala> println(A[String].isInstanceOf[A[String]])true
scala> println(A[String].isInstanceOf[A[Int]])true
Solution: to pass through the reflective TypeTag
scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._
scala> case class B[T: TypeTag] { val tpe = typeOf[T] }defined class B
scala> println(B[String].tpe == typeOf[String])truescala> println(B[String].tpe == typeOf[Int])false
scala> println(B[List[String]].tpe == typeOf[List[String]])truescala> println(B[List[String]].tpe == typeOf[List[Int]])false
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 14 of 22
Scala ReflectionDefeat Type Erasure
The compiler implements the type erasure
scala> case class A[T]scala> println(A[String].isInstanceOf[A[String]])true
scala> println(A[String].isInstanceOf[A[Int]])true
Solution: to pass through the reflective TypeTag
scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._
scala> case class B[T: TypeTag] { val tpe = typeOf[T] }defined class B
scala> println(B[String].tpe == typeOf[String])truescala> println(B[String].tpe == typeOf[Int])false
scala> println(B[List[String]].tpe == typeOf[List[String]])truescala> println(B[List[String]].tpe == typeOf[List[Int]])false
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 14 of 22
Scala ReflectionDefeat Type Erasure
The compiler implements the type erasure
scala> case class A[T]scala> println(A[String].isInstanceOf[A[String]])true
scala> println(A[String].isInstanceOf[A[Int]])true
Solution: to pass through the reflective TypeTag
scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._
scala> case class B[T: TypeTag] { val tpe = typeOf[T] }defined class B
scala> println(B[String].tpe == typeOf[String])truescala> println(B[String].tpe == typeOf[Int])false
scala> println(B[List[String]].tpe == typeOf[List[String]])truescala> println(B[List[String]].tpe == typeOf[List[Int]])false
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 14 of 22
Scala ReflectionDefeat Type Erasure
The compiler implements the type erasure
scala> case class A[T]scala> println(A[String].isInstanceOf[A[String]])true
scala> println(A[String].isInstanceOf[A[Int]])true
Solution: to pass through the reflective TypeTag
scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._
scala> case class B[T: TypeTag] { val tpe = typeOf[T] }defined class B
scala> println(B[String].tpe == typeOf[String])truescala> println(B[String].tpe == typeOf[Int])false
scala> println(B[List[String]].tpe == typeOf[List[String]])truescala> println(B[List[String]].tpe == typeOf[List[Int]])false
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 15 of 22
Scala ReflectionCompilation at Run-Time
scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._
scala> import scala.tools.reflect.ToolBoximport scala.tools.reflect.ToolBox
scala> val tree = Apply(Select(Literal(Constant(40)), newTermName("$plus")), List(Literal(Constant(2))))
tree: reflect.runtime.universe.Apply = 40.$plus(2)
scala> val cm = scala.reflect.runtime.universe.runtimeMirror(getClass.getClassLoader)cm: reflect.runtime.universe.Mirror = JavaMirror with ...scala> println(cm.mkToolBox().eval(tree))42
Toolbox is a full-fledged compiler.
– Unlike the regular compiler, it uses Java reflection encapsulated in
the provided mirror to populate its symbol table.
– Toolbox wraps the input AST, sets its phase to Namer (skipping
Parser) and performs the compilation into an in-memory directory.
– After the compilation is finished, toolbox fires up a classloader that
loads and lauches the code.
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 16 of 22
Scala ReflectionCompile-Time Reflection or Structural Reflection
MacroIt is a pattern or a rule that specifies how a given input se-
quence should be mapped into another.
. . . but what is a macro, really?A regular scala function that transforms an AST into another
AST and it is called by the compiler.
E.g.,
if (1 ==1) { true } else { false }
if (1.$eq$eq(1)) { true } else { false }
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 16 of 22
Scala ReflectionCompile-Time Reflection or Structural Reflection
MacroIt is a pattern or a rule that specifies how a given input se-
quence should be mapped into another.
. . . but what is a macro, really?A regular scala function that transforms an AST into another
AST and it is called by the compiler.
E.g.,
if (1 ==1) { true } else { false }
if (1.$eq$eq(1)) { true } else { false }
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 16 of 22
Scala ReflectionCompile-Time Reflection or Structural Reflection
MacroIt is a pattern or a rule that specifies how a given input se-
quence should be mapped into another.
. . . but what is a macro, really?A regular scala function that transforms an AST into another
AST and it is called by the compiler.
E.g.,
if (1 ==1) { true } else { false }
if (1.$eq$eq(1)) { true } else { false }
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 16 of 22
Scala ReflectionCompile-Time Reflection or Structural Reflection
MacroIt is a pattern or a rule that specifies how a given input se-
quence should be mapped into another.
. . . but what is a macro, really?A regular scala function that transforms an AST into another
AST and it is called by the compiler.
E.g.,
if (1 ==1) { true } else { false }
if (1.$eq$eq(1)) { true } else { false }
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 17 of 22
Scala ReflectionAbstract Syntax Tree (AST)
if (1.$eq$eq(1)) {true} else {false}
If
Apply
Select
Literal
1
"$eq$eq"
List
Literal
1
Literal
true
Literal
false
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 18 of 22
Scala ReflectionApplications of the Macros
Macros permits to transform an AST into a new AST at com-
pile time.
. . . but what we can do wth that?E.g.,
– zero-overhead assertions, logging or bounds checking;
– static loops unroll or other kind of optimization; or
– static DSL transformation
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 19 of 22
Scala ReflectionMacros in Action
import scala.language.experimental.macrosimport scala.reflect.macros.Contextimport scala.collection.mutable.{ListBuffer, Stack}
object Macros {def printf(format: String, params: Any*): Unit = macro printf_impldef printf_impl(c:Context)(format:c.Expr[String], params:c.Expr[Any]*):c.Expr[Unit]={import c.universe._
val Literal(Constant(s_format: String)) = format.treeval evals = ListBuffer[ValDef]()
def precompute(value: Tree, tpe: Type): Ident = {val freshName = newTermName(c.fresh("eval$"))evals += ValDef(Modifiers(), freshName, TypeTree(tpe), value)Ident(freshName)
}
val paramsStack = Stack[Tree]((params map (_.tree)): _*)val refs = s_format.split("(?<=%[\\w%])|(?=%[\\w%])") map {case "%d" => precompute(paramsStack.pop, typeOf[Int])case "%s" => precompute(paramsStack.pop, typeOf[String])case "%%" => Literal(Constant("%"))case part => Literal(Constant(part))
}
val stats = evals ++ refs.map(ref => reify(print(c.Expr[Any](ref).splice)).tree)c.Expr[Unit](Block(stats.toList, Literal(Constant(()))))
}}
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 20 of 22
Scala ReflectionMacros in Action
object Test extends App {import Macros._
printf("hello %s!", "world")}
[22:18]cazzola@surtur:~>scalac Macros.scala[22:19]cazzola@surtur:~>scalac Test.scala[22:20]cazzola@surtur:~>scala Testhello world!
Note that,
– macros must be compiled separately from where they should used,
i.e., no recursive macros are possible;
– macros work on compile time information, i.e., they reify, manipulate
and return an AST.
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 21 of 22
Scala ReflectionConclusions
In Scala 2.10+ you can have all the information about your pro-
gram that the compiler has (well, almost).
– this includes trees, symbols and types and more.
– you can reflect at run-time (scala.reflect.runtime.universe) or at
compile-time (macros).
But (Devil’s Advocate) . . .
– we are still far from C++ Template Programming, e.g., no factorial
at compile time.
– Java reflection, even if limited, is easier to use.
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 21 of 22
Scala ReflectionConclusions
In Scala 2.10+ you can have all the information about your pro-
gram that the compiler has (well, almost).
– this includes trees, symbols and types and more.
– you can reflect at run-time (scala.reflect.runtime.universe) or at
compile-time (macros).
But (Devil’s Advocate) . . .
– we are still far from C++ Template Programming, e.g., no factorial
at compile time.
– Java reflection, even if limited, is easier to use.
Reflection in
Scala 2.10+
Walter Cazzola
Reflection
introduction
properties
example
ScalaReflection
trees
universes
examples
Macros
References
Slide 22 of 22
References
I Martin Odersky.
Reflection and Compilers.
Keynote at Lang.NEXT, April 2012.
I Eugene Burmako.
Macros.
Scala 2.10 documentation, École Polytechnique Fédérale de Lausanne,
2013.
I Heather Miller, Eugene Burmako, and Philipp Haller.
Scala Reflection 2.10.
Scala 2.10 documentation, École Polytechnique Fédérale de Lausanne,
2013.