package macros_demo
import scala.language.experimental.macros
import org.slf4j._
import scala.reflect.macros.whitebox.Context
object Macros {
implicit class LoggerEx(val logger: Logger) {
def DEBUG(msg: String): Unit = macro LogMacros.DEBUG1 def DEBUG(msg: String, exception: Exception): Unit = macro LogMacros.DEBUG2 }
object LogMacros {
def DEBUG1(c: Context)(msg: c.Tree): c.Tree = {
import c.universe._
val pre = c.prefix q""" val x = $pre.logger if( x.isDebugEnabled ) x.debug($msg) """ }
def DEBUG2(c:Context)(msg: c.Tree, exception: c.Tree): c.Tree = {
import c.universe._
val pre = c.prefix q""" val x = $pre.logger if(x.isDebugEnabled) x.debug( $msg, $exception ) """ } } }
package macros_test
import org.slf4j._
import macros_demo.Macros._
class LogTest {
val logger = LoggerFactory.getLogger(getClass) logger.DEBUG(s"Hello, today is ${new java.util.Date}")
}
在這個例子中:html
咱們經過隱式轉換的方式,爲 org.slf4j.Logger
擴展了 DEBUG 方法,使用上與 原有的debug 一致,咱們指望新的 DEBUG 匹配以下的模式:java
// logger.DEBUG(message) will expand to at compile timeif(logger.isDebugEnabled) logger.debug(message)
可使用這個選項來看看 scala 編譯生成的代碼:(能夠直接在sbt中 set scalacOption := Seq(「-Ymacro-debug-lite」)
開啓選項)android
val x = macros_demo.Macros.LoggerEx(LogTest.this.logger).logger;
if (x.isDebugEnabled) x.debug(scala.StringContext.apply("Hello, today is ", "").s(new java.util.Date()))
else ()
//Block(List(ValDef(Modifiers(), TermName("x"), TypeTree(), Select(Apply(Select(Select(Ident(macros_demo), macros_demo.Macros), TermName("LoggerEx")), List(Select(This(TypeName("LogTest")), TermName("logger")))), TermName("logger")))), If(Select(Ident(TermName("x")), TermName("isDebugEnabled")), Apply(Select(Ident(TermName("x")), TermName("debug")), List(Apply(Select(Apply(Select(Select(Ident(scala), scala.StringContext), TermName("apply")), List(Literal(Constant("Hello, today is ")), Literal(Constant("")))), TermName("s")), List(Apply(Select(New(Select(Select(Ident(java), java.util), java.util.Date)), termNames.CONSTRUCTOR), List()))))), Literal(Constant(()))))
上面的第一段代碼,是 scalac 生成的等效代碼,能夠看到,已經符合了咱們的預期,盡在debug級別生效時,纔會對messgae進行求助計算,避免沒必要要的開銷,使得這段代碼,在debug級別關閉時,基本上沒有任何性能的損失。 cookie
而第二段代碼,有如天書,難以閱讀。其實,這就是scalac內部的對這一段代碼的表示格式,通常的,咱們稱之爲 Abstracted Syntax Tree(AST),有興趣的同窗,能夠經過這個網站 *AST explorer* 來幫助閱讀AST。 scala 2.10時代,寫macro,就必須本身來構建AST,至關於你要徒手寫出這麼複雜的一個表達式,這是一件近乎不可完成的任務,因此,macro書寫的難度時及其至高的,好在後續的版本中提供了 q」」 插值,咱們能夠直接使用q」val x = $pre.logger; if( x.isDebugEnabled ) x.debug($msg)」
來替代上面這麼一個複雜的AST,讓 macro 的編寫門檻極大幅度的下降下來。app
不過,即便這樣,要想很好的駕馭macro,你仍是要懂一些 AST 的知識,不然,仍是很難的。 因此,書寫Macro,其實就是一個和編譯器協同工做的過程,這就是macro的難度之所在。或許,將來,隨着 scalameta 和 dotty的成熟,macro的編寫能夠進一步的下降吧。post
參考: 性能