(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
).
Text replacement macros are awesome at this kind of thing. Scala doesn't come with them, but consider writing your own! Transforming e.g.
to
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.
goes to
(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.)