Suche…


Einführung

Makros sind eine Form der Kompilierzeit-Metaprogrammierung. Bestimmte Elemente des Scala-Codes, z. B. Anmerkungen und Methoden, können so erstellt werden, dass sie anderen Code konvertieren, wenn sie kompiliert werden. Makros sind gewöhnlicher Scala-Code, der mit Datentypen arbeitet, die anderen Code darstellen. Das Plugin [Macro Paradise] [] erweitert die Fähigkeiten von Makros über die Basissprache hinaus. [Macro Paradise]: http://docs.scala-lang.org/overviews/macros/paradise.html

Syntax

  • def x () = Makro x_impl // x ist ein Makro, wobei x_impl zur Transformation von Code verwendet wird
  • def macroTransform (annottees: Any *): Any = macro impl // In Annotationen verwenden, um Makros zu erstellen

Bemerkungen

Makros sind eine scala.language.macros , die aktiviert werden muss, indem entweder scala.language.macros oder mit der Compiler-Option -language:macros importiert wird. Nur Makrodefinitionen erfordern dies. Code, der Makros verwendet, muss dies nicht tun.

Makro-Anmerkung

Diese einfache Makroannotation gibt das kommentierte Element unverändert aus.

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

Die Annotation @compileTimeOnly generiert einen Fehler mit einer Meldung, die darauf hinweist, dass das paradise Compiler-Plug-In für die Verwendung dieses Makros enthalten sein muss. Anweisungen zum Einbinden über SBT finden Sie hier .

Sie können das oben definierte Makro folgendermaßen verwenden:

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

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

@noop
def g(): Int = 10

Methodenmakros

Wenn eine Methode als Makro definiert ist, übernimmt der Compiler den übergebenen Code als Argument und wandelt ihn in ein AST um. Sie ruft dann die Makroimplementierung mit dieser AST auf und gibt eine neue AST zurück, die dann an ihre Aufrufstelle zurückgespleißt wird.

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

Es ist auch möglich, Makros zu verwenden, die Tree s als Argumente verwenden. Wie , wie reify dient zum Erstellen Expr s, die q (für quasiquote) string Interpolator lässt uns erstellen und dekonstruieren Tree s. Beachten Sie, dass wir q oben verwenden konnten ( expr.tree ist überraschend ein Tree selbst), dies jedoch nicht zu Demonstrationszwecken.

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

Fehler in Makros

Makros können Warnungen und Fehler des Compilers durch die Verwendung ihres Context auslösen.

Nehmen wir an, wir sind besonders übereifrig, wenn es um schlechten Code geht, und wir möchten jede technische Schuld mit einer Compiler-Infomeldung kennzeichnen. Wir können ein Makro verwenden, das nur eine solche Nachricht ausgibt.

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

Zusätzlich anstelle von ??? um nicht implementierten Code zu kennzeichnen, können wir zwei Makros erstellen, !!! und ?!? , die dem gleichen Zweck dienen, aber Compiler-Warnungen ausgeben. ?!? wird eine Warnung auslösen und !!! wird einen eindeutigen Fehler verursachen.

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
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow