printlnэто золотой молоток отладки, но когда дело доходит до отладки ваших макросов Scala, я считаю полезным иметь возможность установить точку останова в точке раскрытия макроса. В этой статье я покажу вам, как создать простой макрос и как его отладить в IntelliJ IDEA.
Screencast
Макросы от Cake Solutions Ltd. на Vimeo .
Макрос
Допустим, мы хотели бы написать функцию, которая возвращает имена членов определенного типа без использования отражения.
object TypesDemo extends App {
class A {
def a: Int = 42
def b: String = "b"
}
class B {
def beta: String = "beta"
}
println(methodNames[A])
println(methodNames[B])
}
Запуск этого кода должен печатать List(, a, b)и List(, beta). Нам нужно реализовать methodNamesфункцию. И это будет работа для макроса. (Макрос должен быть определен в модуле, отличном от модуля, который содержит TypeDemo.)
Давайте запустим Typesмодуль только с его каркасом.
import language.experimental.macros
import scala.reflect.macros.Universe
object Types {
def methodNames[A]: List[String] = macro methodNames_impl[A]
def methodNames_impl[A : c.WeakTypeTag](c: Context): c.Expr[List[String]] = {
???
}
Обратите внимание на импорт language.experimental.macros, что позволяет макросы, а затем определение methodNames[A]функции , и methodNames_implкоторые зеркала он. Давайте теперь добавим реализацию макроса:
object Types {
def methodNames[A]: List[String] = macro methodNames_impl[A]
def methodNames_impl[A : c.WeakTypeTag](c: Context): c.Expr[List[String]] = {
import c.universe._
val methods: List[String] = c.weakTypeOf[A].typeSymbol.typeSignature.
declarations.toList.filter(_.isMethod).map(_.name.toString)
val listApply = Select(reify(List).tree, newTermName("apply"))
c.Expr[List[String]](Apply(listApply,
List(methods.map(x => Literal(Constant(x))):_*)))
}
}
methodsПеременная выглядит сложным, но это хорошо известные коллекции Scala потанцевать. Интересная часть заключается в следующем. Мы определяем listApplyкак «указатель» на applyфункцию в List. Затем мы возвращаем выражение, содержащее List[String], применяя listApplyметод к его параметрам. Если вы изучите List.applyметод, вы увидите, что он принимает один параметр типа A*. Итак, вторым параметром Applyдолжен быть список, содержащий один элемент (у нас есть один параметр), и его значением должны быть varargs, представляющие имена методов. Следовательно, мы приходим к c.Expr[List[String]](Apply(listApply, List(methods.map(x => Literal(Constant(x))):_*))).
Запуск TypesDemoсейчас действительно печатает желаемый результат.
Отладка
Я решил написать немного более сложный макрос, чтобы сделать его более полезным для отладки. Конечно, вы не можете просто добавить точку останова в макрос и ожидать, что она достигнет точки останова при запуске вашей программы. Макросы запускаются во время компиляции. И поэтому, если мы хотим отладить макрос, то вместо отладки нашей программы мы должны отладить компилятор Scala.
Другими словами, класс, который мы запускаем, имеет scala.tools.nsc.Mainнекоторые настройки, а именно:
-Dscala.usejavacp=trueПараметр виртуальной машины-cp types.Types demo/src/main/scala/types/TypesDemo.scalaпараметры
В IntelliJ IDEA настройки отладки для нашего примера:
