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 настройки отладки для нашего примера: