Проблема в том, что ваш код смешивает концепции времени компиляции и времени выполнения.
Используемая вами переменная «список» является значением времени компиляции (т. е. предполагается, что оно повторяется во время компиляции), и вы просите reify сохранить его до времени выполнения (путем объединения производных значений). Эта межстадийная головоломка приводит к созданию так называемого свободного термина.
Короче говоря, свободные термины — это заглушки, которые ссылаются на значения из более ранних этапов. Например, следующий фрагмент:
val x = 2
reify(x)
Будет компилироваться следующим образом:
val free$x1 = newFreeTerm("x", staticClass("scala.Int").asTypeConstructor, x);
Ident(free$x1)
Умно, да? Результат сохраняет тот факт, что x является Ident, сохраняет его тип (характеристика времени компиляции), но, тем не менее, ссылается и на свое значение (характеристика времени выполнения). Это стало возможным благодаря лексическому охвату.
Но если вы попытаетесь вернуть это дерево из расширения макроса (которое встроено в сайт вызова макроса), все взорвется. Сайт вызова макроса, скорее всего, не будет иметь x в своей лексической области, поэтому он не сможет ссылаться на значение x.
Что еще хуже. Если приведенный выше фрагмент написан внутри макроса, то x существует только во время компиляции, то есть в JVM, которая запускает компилятор. Но когда компилятор завершает работу, его нет.
Однако предполагается, что результаты расширения макроса, содержащие ссылку на x, должны выполняться во время выполнения (скорее всего, в другой JVM). Чтобы понять это, вам потребуется постоянство между этапами, то есть возможность каким-то образом сериализовать произвольные значения времени компиляции и десериализовать их во время выполнения. Я не знаю, как это сделать на скомпилированном языке, таком как Scala.
Обратите внимание, что в некоторых случаях возможна межэтапная персистентность. Например, если x был полем статического объекта:
object Foo { val x = 2 }
import Foo._
reify(x)
Тогда это не станет свободным термином, а будет прямо овеществлено:
Select(Ident(staticModule("Foo")), newTermName("x"))
Это интересная концепция, которая также обсуждалась в докладе SPJ на Scala Days 2012: http://skillsmatter.com/podcast/scala/haskell-cloud.
Чтобы убедиться, что какое-то выражение не содержит свободных терминов, в Haskell в компилятор добавляют новый встроенный примитив — конструктор типа Static
. С макросами мы можем сделать это естественным образом, используя reify (который сам по себе является просто макросом). См. обсуждение здесь: https://groups.google.com/forum/#!topic/scala-internals/-42PWNkQJNA.
Хорошо, теперь мы увидели, в чем именно заключается проблема с исходным кодом, так как же заставить его работать?
К сожалению, нам придется вернуться к ручному построению AST, потому что reify испытывает трудности с выражением динамических деревьев. Идеальным вариантом использования reify в макрологии является наличие статического шаблона с типами дыр, известными во время компиляции макроса. Сделайте шаг в сторону - и вам придется прибегнуть к строительству деревьев вручную.
Суть в том, что вам нужно использовать следующее (работает с недавно выпущенной версией 2.10.0-M4, см. руководство по миграции на scala-language, чтобы узнать, что именно изменилось: http://groups.google.com/group/scala-language/browse_thread/thread/bf079865ad42249c):
import scala.reflect.makro.Context
object Macros {
def join_impl(c: Context)(a: c.Expr[Int]): c.Expr[List[Int]] = {
import c.universe._
import definitions._
a.tree match {
case Block(list, ret) =>
c.Expr((list :+ ret).foldRight(Ident(NilModule): Tree)((el, acc) =>
Apply(Select(acc, newTermName("$colon$colon")), List(el))))
}
}
def join(a: Int): List[Int] = macro join_impl
}
person
Eugene Burmako
schedule
14.06.2012