Статьи

Отладка макросов Scala

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

macdeb