优化的日志方式
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的编写可以进一步的降低吧。
参考: