不思議なScala Macroの旅(二)-一例

5746 ワード

最適化されたログ方式
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(()))))

     
    上記の第1セグメントのコードはscalacが生成した等価コードであり、debugレベルが有効になったときにmessgaeに対して助けを求める計算を行い、不要なオーバーヘッドを回避し、このコードはdebugレベルがオフになったとき、ほとんど性能の損失がないことがわかります. 
     
    2段目のコードは、天書のように読みにくい.実は、これはscalac内部のこのコードの表示フォーマットで、一般的に、私たちはAbstracted Syntax Tree(AST)と呼ばれています.興味のある学生は、このサイト*AST explorer*を通じてASTを読むのを助けることができます.scala 2.10時代、macroを書くには、自分でASTを構築しなければなりません.これはあなたがこのような複雑な表現を素手で書くことに相当します.これはほぼ完成できない任務です.だから、macroの書く難易度は非常に高く、後続のバージョンではqを提供しています.」挿入値は、上記のような複雑なASTの代わりにq”val x = $pre.logger; if( x.isDebugEnabled ) x.debug($msg)”を直接使用することができます.macroの作成の敷居を大幅に下げた.
     
    しかし、それでもmacroをうまくコントロールするには、ASTの知識を理解しなければなりません.そうしないと、難しいです.だから、Macroを書くのは、コンパイラと協力して仕事をする過程であり、これがmacroの難しさです.もしかすると、将来、scalametaやdottyが成熟するにつれて、macroの作成はさらに低下するだろう.
     
    参照先:
    不思議なScala Macroの旅(一)-いつマクロを使うか
    転自:不思議なScala Macroの旅(2)