神奇的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}")
}

在這個例子中:

  • 我們通過隱式轉換的方式,為 org.slf4j.Logger 擴展了 DEBUG 方法,使用上與 原有的debug 一致,我們期望新的 DEBUG 匹配如下的模式:

// logger.DEBUG(message) will expand to at compile timeif(logger.isDebugEnabled) logger.debug(message)
  • 可以使用這個選項來看看 scala 編譯生成的代碼:(可以直接在sbt中 set scalacOption := Seq(“-Ymacro-debug-lite”)開啟選項)

  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級別關閉時,基本上沒有任何性能的損失。 

 

而第二段代碼,有如天書,難以閱讀。其實,這就是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 的編寫門檻極大幅度的降低下來。

 

不過,即使這樣,要想很好的駕馭macro,你還是要懂一些 AST 的知識,否則,還是很難的。 所以,書寫Macro,其實就是一個和編譯器協同工作的過程,這就是macro的難度之所在。或許,未來,隨着 scalameta 和 dotty的成熟,macro的編寫可以進一步的降低吧。

 

參考: 

神奇的Scala Macro之旅(一)- 什么時候用宏

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM