神奇的Scala Macro之旅(二)- 一個實例 神奇的Scala Macro之旅(一)- 何時用宏

優化的日誌方式

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

 

參考: 性能

神奇的Scala Macro之旅(一)- 何時用宏

轉自:神奇的Scala Macro之旅(2)

相關文章
相關標籤/搜索