Использование макросов def для захвата исходного кода

(для TL;DR перейдите к части, выделенной жирным шрифтом)

У меня есть чистая система классов закрытого типа с сериализацией (отдельно от проблем с сериализацией POJO). Например:

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

Но в ситуациях мне нужно зафиксировать замыкание. Например:

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

Теперь я решил это однажды с сериализацией POJO (ObjectOutputStream и т. д.) для fun. Меня сильно укусили за ноги, потому что я не мог прочитать в Scala 2.10 то, что сериализовал в 2.9. И в этом случае мне действительно нужно убедиться, что я могу вернуть свои вещи независимо от версии Scala.

Итак... Я подумал, что могу использовать макрос для создания «резервной копии» исходного кода, чтобы в случае сбоя десериализации POJO я мог регенерировать функцию из исходного кода (используя встроенный компилятор/интерпретатор).

Моя идея была бы

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)
}

Можно ли легко зафиксировать источник звонка, например

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

(Я предполагаю, что макрос def должен быть уже в функции map ExprOps).


person 0__    schedule 07.05.2013    source источник
comment
Я не думаю, что вы можете получить исходный код, но вы можете получить Tree: def applyImpl(c: Context)(a: c.Expr[Expr], fun:c.Expr[Expr => Expr]): c.Expr[Map] = { val source = c.universe.show(fun.tree); ... }   -  person senia    schedule 07.05.2013
comment
@senia спасибо за ссылку и комментарий. Мне действительно не нужен оригинальный исходный код, потому что он не будет показан (вероятно) пользователю, мне просто нужен исходный код, который я могу перекомпилировать в то же дерево, даже если сериализованная версия, скажем, Tree изменится ( поэтому я не хочу сериализовать дерево напрямую). Итак, я посмотрю на этот universe.show, этого действительно может быть достаточно.   -  person 0__    schedule 07.05.2013


Ответы (1)


Макросы замены текста отлично справляются с такими вещами. Scala не поставляется с ними, но подумайте о том, чтобы написать свой собственный! Преобразование, например.

{# 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""")

должно быть довольно легко; тогда вам просто нужно выполнить предварительную обработку перед компиляцией. Если вы хотите, чтобы ваша IDE не путалась из-за разной длины строк, вы можете хранить строки в отдельном объекте, например.

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

идет к

({_+7}, Store.aW4)

...

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

(Для достижения наилучших результатов кодируйте захваченный текст в base-64. Вложение будет работать нормально, если вы работаете рекурсивно и знаете, что может произойти вложение.)

person Rex Kerr    schedule 07.05.2013
comment
Мне нужно иметь возможность делать это во время выполнения с помощью встроенного интерпретатора. В моем компилируемом проекте/IDE у меня уже есть доступ к исходному коду :) Я мог бы, конечно, захватить текст из интерпретатора (и я делал это раньше), но тогда мне нужно возиться с поиском исходных местоположений (если только конечно, я добавляю escape-символы, как вы делаете с #). - person 0__; 07.05.2013