Using def-macros to capture source code

272 views Asked by At

(for TL;DR, go to the bold face part)

I am having a clean closed type class system with serialization (detached from POJO serialization woes). For example:

trait Expr
case class Const(i: Int) extends Expr
case class BinOp(a: Expr, b: Expr, op: Int) extends Expr

But in situations I need to capture a closure. For example:

case class Map(a: Expr, fun: Expr => Expr) extends Expr

Now, I had solved this once with POJO serialization (ObjectOutputStream etc.) for the fun. I got badly bitten in the feet, because I couldn't read in Scala 2.10 what I had serialised in 2.9. And in this case, I really need to make sure I can get my stuff back independent of the Scala version.

So... I have been thinking that I could use a macro to make a "backup" of the source code, so that if POJO deserialisation fails, I can regenerate the function from source (using an in-place compiler/interpreter).

My idea would be

object Map {
  def apply(a: Expr, fun: Expr => Expr): Map = macro applyImpl
  private def applyImpl = ???

  def unapply(m: Map): Option[(Expr, Expr => Expr)] = Some(m.a -> m.fun)
}
trait Map extends Expr {
  def a: Expr
  def fun: Expr => Expr
}

implicit class ExprOps(val ex: Expr) extends AnyVal {
  def map(fun: Expr => Expr) = Map(ex, fun)
}

Is it possibly to easily capture the source of a call like

//           |------------- source of this -------------|
someExpr.map { case Const(i) => Const(i*i); case x => x }

(My guess is the def-macro needs to be already in the map function of ExprOps).

1

There are 1 answers

1
Rex Kerr On BEST ANSWER

Text replacement macros are awesome at this kind of thing. Scala doesn't come with them, but consider writing your own! Transforming e.g.

{# case Const(i) => Const(i*i); case x => x #}

to

({ case Const(i) => Const(i*i); case x => x }, """case Const(i) => Const(i*i); case x => x""")

should be pretty easy; then you just have to preprocess before you compile. If you want your IDE to not get confused by different line lengths, you can store the strings in a separate object, e.g.

{# _+7 #}/*STORE*/

goes to

({_+7}, Store.aW4)

...

object Store {
   val aW4 = """_+7"""
}
//(EOF)

(For best results, base-64 encode the captured text. Nesting will work fine as long as you work recursively and are aware that nesting might happen.)