Sök…


Introduktion

Makroer är en form för kompilering av tidsmetaprogrammering. Vissa element i Scala-kod, till exempel anteckningar och metoder, kan göras för att omvandla annan kod när de sammanställs. Makron är vanlig Scala-kod som fungerar på datatyper som representerar annan kod. [Macro Paradise] [] -pluginet utvidgar makronens förmågor utöver basspråket. [Macro Paradise]: http://docs.scala-lang.org/overviews/macros/paradise.html

Syntax

  • def x () = makro x_impl // x är ett makro, där x_impl används för att transformera kod
  • def macroTransform (annottees: Any *): Any = macro impl // Använd i kommentarer för att göra dem till makroer

Anmärkningar

Makroer är en språkfunktion som måste aktiveras, antingen genom att importera scala.language.macros eller med kompilatoralternativet - -language:macros . Endast makrodefinitioner kräver detta; kod som använder makron behöver inte göra det.

Makroanteckning

Den här enkla makroanteckningen matar ut det annoterade objektet som det är.

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

Den @compileTimeOnly anteckning genererar ett fel med ett meddelande om att paradise kompilatorn plugin måste inkluderas för att använda makrot. Instruktioner för att inkludera detta via SBT finns här .

Du kan använda det ovan definierade makroet så här:

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

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

@noop
def g(): Int = 10

Metod makroer

När en metod definieras som ett makro tar kompilatorn koden som skickas som dess argument och förvandlar den till en AST. Den åberopar sedan makroimplementeringen med den AST, och den returnerar en ny AST som sedan splitsas tillbaka till sin samtalssida.

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

Det är också möjligt att ha makron som tar Tree som argument. Liksom hur reify används för att skapa Expr , låter stränginterpolatorn q (för kvasi-kvot) oss skapa och dekonstruera Tree . Observera att vi kunde ha använt q ovan ( expr.tree är, förvånande, ett Tree själv) också, men inte för demonstrativa syften.

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

Fel i makron

Makron kan utlösa kompilatorvarningar och fel genom användning av deras Context .

Säg att vi är en särskilt övervärldig när det gäller dålig kod, och vi vill markera alla instanser av teknisk skuld med ett kompilatorinfo-meddelande (låt oss inte tänka på hur dålig denna idé är). Vi kan använda ett makro som inte gör något annat än att avge ett sådant meddelande.

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

Istället för att använda ??? För att markera oimplementerad kod kan vi skapa två makron, !!! och ?!? , som tjänar samma syfte, men avger kompilatorvarningar. ?!? kommer att leda till en varning och !!! kommer att orsaka ett direkt fel.

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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow