zoukankan      html  css  js  c++  java
  • 神奇的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)

  • 相关阅读:
    数据结构笔记
    并查集
    计算两个数的最大公约数和最小公倍数
    行盒
    浏览器兼容性和布局
    Java中HashMap等的实现要点浅析
    关于js异步上传文件
    填充路径时所使用的 “非零环绕规则”
    XP极限编程
    假如森林里有一棵树倒下了
  • 原文地址:https://www.cnblogs.com/barrywxx/p/10779401.html
Copyright © 2011-2022 走看看