수색…


소개

매크로는 컴파일 타임 메타 프로그래밍의 한 형태입니다. 어노테이션 및 메소드와 같은 스칼라 코드의 특정 요소는 컴파일 될 때 다른 코드를 변형 할 수 있습니다. 매크로는 다른 코드를 나타내는 데이터 형식에서 작동하는 일반 스칼라 코드입니다. [매크로 파라다이스] [] 플러그인은 매크로의 기능을 기본 언어 이상으로 확장합니다. [매크로 파라다이스] : http://docs.scala-lang.org/overviews/macros/paradise.html

통사론

  • def x () = 매크로 x_impl // x는 매크로이며, 여기서 x_impl은 코드를 변환하는 데 사용됩니다.
  • def macroTransform (annottees : Any *) : Any = 매크로 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로 매크로 구현을 호출하고 새 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 의의 q (quasiquote를 들어) 문자열 보간은 우리가 만들고 해체 할 수 있습니다 Tree 들. 우리는 위의 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