Поиск…


Вступление

Макросы - это форма метапрограммирования времени компиляции. Некоторые элементы кода Scala, такие как аннотации и методы, могут быть сделаны для преобразования другого кода при компиляции. Макросы - это обычный код Scala, который работает с типами данных, которые представляют другой код. Плагин [Macro Paradise] [] расширяет возможности макросов за пределами базового языка. [Макро-рай]: http://docs.scala-lang.org/overviews/macros/paradise.html

Синтаксис

  • def x () = macro x_impl // x - макрос, где x_impl используется для преобразования кода
  • def macroTransform (annottees: Any *): Any = macro impl // Использовать в аннотации, чтобы сделать их макросами

замечания

Макросы - это языковая функция, которая должна быть активирована либо путем импорта scala.language.macros либо с помощью опции -language:macros . Для этого требуются только макроопределения; код, который использует макросы, не должен этого делать.

Макро-аннотация

Эта простая макрокоманда выводит аннотированный элемент как есть.

import scala.annotation.{compileTimeOnly, StaticAnnotation}
import scala.reflect.macros.whitebox.Context

@compileTimeOnly("enable macro paradise to expand macro annotations")
class noop extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro linkMacro.impl
}

object linkMacro {
  def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
    import c.universe._

    c.Expr[Any](q"{..$annottees}")
  }
}

Аннотации @compileTimeOnly генерируют ошибку с сообщением о том, что для использования этого макроса должен быть включен плагин компилятора paradise . Инструкции по включению этого через SBT приведены здесь .

Вы можете использовать указанный выше макрос следующим образом:

@noop
case class Foo(a: String, b: Int)

@noop
object Bar {
  def f(): String = "hello"
}

@noop
def g(): Int = 10

Макросы метода

Когда метод определяется как макрос, компилятор принимает код, который передается в качестве аргумента, и превращает его в АСТ. Затем он вызывает реализацию макроса с этим AST, и он возвращает новый AST, который затем сплавляется обратно на его сайт вызова.

import reflect.macros.blackbox.Context

object Macros {
  // This macro simply sees if the argument is the result of an addition expression.
  // E.g. isAddition(1+1) and isAddition("a"+1).
  // but !isAddition(1+1-1), as the addition is underneath a subtraction, and also
  // !isAddition(x.+), and !isAddition(x.+(a,b)) as there must be exactly one argument.
  def isAddition(x: Any): Boolean = macro isAddition_impl

  // The signature of the macro implementation is the same as the macro definition,
  // but with a new Context parameter, and everything else is wrapped in an Expr.
  def isAddition_impl(c: Context)(expr: c.Expr[Any]): c.Expr[Boolean] = {
    import c.universe._ // The universe contains all the useful methods and types
    val plusName = TermName("+").encodedName // Take the name + and encode it as $plus
    expr.tree match { // Turn expr into an AST representing the code in isAddition(...)
      case Apply(Select(_, `plusName`), List(_)) => reify(true)
      // Pattern match the AST to see whether we have an addition
      // Above we match this AST
      //             Apply (function application)
      //            /     \
      //         Select  List(_) (exactly one argument)
      // (selection ^ of entity, basically the . in x.y)
      //      /          \
      //    _              \
      //               `plusName` (method named +)
      case _                                     => reify(false)
      // reify is a macro you use when writing macros
      // It takes the code given as its argument and creates an Expr out of it
    }
  }
}

Также возможно иметь макросы, которые берут Tree качестве аргументов. Подобно тому, как reify используется для создания Expr s, строковый интерполятор q (для квазиквазот) позволяет нам создавать и деконструировать Tree s. Обратите внимание, что мы могли бы использовать q выше ( expr.tree , удивление, само Tree ) тоже, но не для демонстрационных целей.

// No Exprs, just Trees
def isAddition_impl(c: Context)(tree: c.Tree): c.Tree = {
  import c.universe._
  tree match {
    // q is a macro too, so it must be used with string literals.
    // It can destructure and create Trees.
    // Note how there was no need to encode + this time, as q is smart enough to do it itself.
    case q"${_} + ${_}" => q"true"
    case _              => q"false"
  }
}

Ошибки в макросах

Макросы могут запускать предупреждения и ошибки компилятора с помощью их Context .

Скажем, мы особенно переусердствовали, когда дело доходило до плохого кода, и мы хотим отметить каждый экземпляр технической задолженности информационным сообщением компилятора (давайте не будем думать о том, насколько плохая эта идея). Мы можем использовать макрос, который ничего не делает, кроме как излучать такое сообщение.

import reflect.macros.blackbox.Context

def debtMark(message: String): Unit = macro debtMark_impl
def debtMarkImpl(c: Context)(message: c.Tree): c.Tree = {
  message match {
    case Literal(Constant(msg: String)) => c.info(c.enclosingPosition, msg, false)
    // false above means "do not force this message to be shown unless -verbose"
    case _                              => c.abort(c.enclosingPosition, "Message must be a string literal.")
    // Abort causes the compilation to completely fail. It's not even a compile error, where
    // multiple can stack up; this just kills everything.
  }
  q"()" // At runtime this method does nothing, so we return ()
}

Кроме того, вместо использования ??? чтобы отметить невыполненный код, мы можем создать два макроса, !!! и ?!? , которые выполняют одну и ту же цель, но выдают предупреждения компилятора. ?!? выдает предупреждение, и !!! приведет к прямой ошибке.

import reflect.macros.blackbox.Context

def ?!? : Nothing = macro impl_?!?
def !!! : Nothing = macro impl_!!!

def impl_?!?(c: Context): c.Tree = {
  import c.universe._
  c.warning(c.enclosingPosition, "Unimplemented!")
  q"${termNames.ROOTPKG}.scala.Predef.???"
  // If someone were to shadow the scala package, scala.Predef.??? would not work, as it
  // would end up referring to the scala that shadows and not the actual scala.
  // ROOTPKG is the very root of the tree, and acts like it is imported anew in every
  // expression. It is actually named _root_, but if someone were to shadow it, every
  // reference to it would be an error. It allows us to safely access ??? and know that
  // it is the one we want.
}

def impl_!!!(c: Context): c.Tree = {
  import c.universe._
  c.error(c.enclosingPosition, "Unimplemented!")
  q"${termNames.ROOTPKG}.scala.Predef.???"
}


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow