Buscar..


Introducción

Las macros son una forma de metaprogramación en tiempo de compilación. Ciertos elementos del código Scala, como las anotaciones y los métodos, pueden transformarse en otro código cuando se compilan. Las macros son códigos Scala normales que operan en tipos de datos que representan otros códigos. El complemento [Macro Paradise] [] extiende las capacidades de las macros más allá del lenguaje base. [Paraíso macro]: http://docs.scala-lang.org/overviews/macros/paradise.html

Sintaxis

  • def x () = macro x_impl // x es una macro, donde x_impl se usa para transformar el código
  • def macroTransform (annottees: Any *): Any = macro impl // Usar en las anotaciones para hacerlas macros

Observaciones

Las macros son una función de idioma que se debe habilitar, ya sea importando scala.language.macros o con la opción del compilador -language:macros . Solo las definiciones de macros requieren esto; El código que utiliza macros no necesita hacerlo.

Anotación de macro

Esta simple anotación de macro genera el elemento anotado tal como está.

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}")
  }
}

El @compileTimeOnly anotación genera un error con un mensaje que indica que el paradise complemento compilador debe ser incluido para utilizar esta macro. Las instrucciones para incluir esto a través de SBT están aquí .

Puedes usar la macro definida arriba como esta:

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

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

@noop
def g(): Int = 10

Método de macros

Cuando se define un método para que sea una macro, el compilador toma el código que se pasa como su argumento y lo convierte en un AST. A continuación, invoca la implementación de la macro con ese AST y devuelve un nuevo AST que luego se empalma de nuevo a su sitio de llamada.

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
    }
  }
}

También es posible tener macros que tomen los Tree como argumentos. Por ejemplo, cómo reify se utiliza para crear Expr s, el q (por quasiquote) interpolador cadena nos permite crear y deconstruir Tree s. Tenga en cuenta que podríamos haber usado q arriba ( expr.tree es, sorpresa, un Tree sí) también, pero no con propósitos demostrativos.

// 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"
  }
}

Errores en macros

Las macros pueden activar advertencias y errores del compilador mediante el uso de su Context .

Digamos que somos particularmente celosos cuando se trata de un código incorrecto, y queremos marcar cada instancia de deuda técnica con un mensaje de información del compilador (no pensemos en lo mala que es esta idea). Podemos usar una macro que no haga nada excepto emitir un mensaje de este tipo.

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 ()
}

Además, en lugar de usar ??? Para marcar código no implementado, podemos crear dos macros, !!! y ?!? , que sirven al mismo propósito, pero emiten avisos de compilación. ?!? hará que se emita una advertencia, y !!! causará un error absoluto

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
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow