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)

  • 相关阅读:
    年末反思
    Flink运行时架构
    Phoenix 启动报错:Error: ERROR 726 (43M10): Inconsistent namespace mapping properties. Cannot initiate connection as SYSTEM:CATALOG is found but client does not have phoenix.schema.
    Clickhouse学习
    Flink简单认识
    IDEA无法pull代码到本地,Can't Update No tracked branch configured for branch master or the branch doesn't exist.
    第1章 计算机系统漫游
    简单的 Shell 脚本入门教程
    开源≠免费 常见开源协议介绍
    MySQL 视图
  • 原文地址:https://www.cnblogs.com/barrywxx/p/10779401.html
Copyright © 2011-2022 走看看