45

In Scala 2.10 how do I generate a class from string (probably, using the Toolbox api) later to be instantiated with Scala's reflection?

om-nom-nom
  • 62,329
  • 13
  • 183
  • 228
Nikita Volkov
  • 42,792
  • 11
  • 94
  • 169

1 Answers1

54

W.r.t compilation toolboxes can only run expressions = return values, but not resulting classes or files/byte arrays with compilation results.

However it's still possible to achieve what you want, since in Scala it's so easy to go from type level to value level using implicit values:

Edit. In 2.10.0-RC1 some methods of ToolBox have been renamed. parseExpr is now just parse, and runExpr is now called eval.

scala> import scala.reflect.runtime._ // requires scala-reflect.jar
                                      // in REPL it's implicitly added 
                                      // to the classpath
                                      // but in your programs
                                      // you need to do this on your own
import scala.reflect.runtime

scala> val cm = universe.runtimeMirror(getClass.getClassLoader)
cm @ 41d0fe80: reflect.runtime.universe.Mirror = JavaMirror with scala.tools.nsc.interpreter.IMain$TranslatingClassLoader...

scala> import scala.tools.reflect.ToolBox // requires scala-compiler.jar
                                          // in REPL it's implicitly added 
                                          // to the classpath
                                          // but in your programs
                                          // you need to do this on your own
import scala.tools.reflect.ToolBox

scala> val tb = cm.mkToolBox()
tb: scala.tools.reflect.ToolBox[reflect.runtime.universe.type] = scala.tools.reflect.ToolBoxFactory$ToolBoxImpl@3a962da5

scala> tb.runExpr(tb.parseExpr("class C; scala.reflect.classTag[C].runtimeClass"))
res2: Any = class __wrapper$1$f9d572ca0d884bca9333e251c64e980d$C$1

Update #1. If you don't need a java.lang.Class and just need to instantiate the compiled class, you can write new C directly in the string submitted to runExpr.

Update #2. It is also possible to have runExpr use custom mapping from variable names to runtime values. For example:

scala> val build = scala.reflect.runtime.universe.build
build: reflect.runtime.universe.BuildApi = scala.reflect.internal.BuildUtils$BuildImpl@50d5afff

scala> val x = build.setTypeSignature(build.newFreeTerm("x", 2), typeOf[Int])
x: reflect.runtime.universe.FreeTermSymbol = free term x

scala> tb.runExpr(Apply(Select(Ident(x), newTermName("$plus")), List(Literal(Constant(2)))))
res0: Any = 4

In this example I create a free term that has a value of 2 (the value doesn't have to be a primitive - it can be your custom object) and bind an identifier to it. This value is then used as-is in the code that is compiled and run by a toolbox.

The example uses manual AST assembly, but it's possible to write a function that parses a string, finds out unbound identifiers, looks up values for them in some mapping and then creates corresponding free terms. There's no such function in Scala 2.10.0 though.

Eugene Burmako
  • 13,028
  • 1
  • 46
  • 59
  • Thanks! One follow-up: is there a way for me to get a handle on this returned `java.lang.Class` with Scala's reflection or I'll just have to stick to plain old Java's one? – Nikita Volkov Aug 25 '12 at 16:39
  • 2
    Sure. Use `.classSymbol()`, where = `scala.reflect.runtime.universe.runtimeMirror(.getClassLoader)`. Then you get a Scala reflection symbol, which can be inspected with Scala reflection API. – Eugene Burmako Aug 25 '12 at 17:18
  • Why did you use `universe.runtimeMirror(getClass.getClassLoader)` instead of `reflect.runtime.currentMirror` and `scala.reflect.classTag[C].runtimeClass` instead of `classOf[C]`? It turned out to be working fine on my end. Thanks A LOT for the help, btw! – Nikita Volkov Aug 26 '12 at 06:45
  • 2
    reflect.runtime.currentMirror is essentially the same, but I wanted to spell out the full version. classTag[C].runtimeClass? No particular reason. I agree, classOf[C] is shorter. – Eugene Burmako Aug 26 '12 at 09:52
  • 2
    @EugeneBurmako, what's the best way to find unbound identifiers? – Sagie Davidovich Sep 16 '12 at 11:55
  • I can't immediately think about a way of doing that. To account for all the scoping rules, you need a typecheck. But even typecheck won't tell you much about unbound identifiers - at best, it will show some error messages, possibly even not an exhaustive list of error messages. Hmm... I'd probably traverse all `Ident` nodes and try to apply some domain-specific knowledge (e.g. that unbound variables can only come from a dictionary of well-known names). – Eugene Burmako Sep 16 '12 at 12:28
  • In what way did [41e498adee10c582528b5ee475d6286d365ae9b0](http://github.com/scala/scala/commit/41e498adee10c582528b5ee475d6286d365ae9b0) change this answer? – Daniel C. Sobral Nov 06 '12 at 16:19
  • Why should it? Could you please elaborate? – Eugene Burmako Nov 06 '12 at 20:14
  • The answer seems deprecated for 2.11. However as the function specifically mentions 2.10, I am not sure if it is worth updating? Note: Meanwhile I have asked [a similar question for 2.11](http://stackoverflow.com/questions/29373551/scala-script-in-2-11) – Suma Apr 01 '15 at 09:12
  • 1
    Please consult http://docs.scala-lang.org/overviews/macros/changelog211.html for instructions wrt migration to 2.11 / maintaining compatibility with both 2.10 and 2.11. – Eugene Burmako Apr 01 '15 at 19:28