神奇的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)
神奇的Scala Macro之旅(二)- 一个实例的更多相关文章
- 神奇的Scala Macro之旅(三)- 实际应用
在上一篇中,我们示范了使用macro来重写 Log 的 debug/info 方法,并大致的介绍了 macro 的基本语法.基本使用方法.以及macro背后的一些概念, 如AST等.那么,本篇中,我们 ...
- 神奇的Scala Macro之旅(一)- 什么时候用宏
在Lisp语言中,macro是一个神器,可以“动态的生成代码”,然后被执行,这种方式给到Lisp无限的表达能力.除Lisp之外,很少有语言支持Macro这个特性,我记得 GWT之中曾经有一个类似的Ge ...
- 神奇的Scala Macro之旅(四)- BeanBuilder
在Java开发中,经常会有一个需求,将一个 Bean 复制到另外一个 Bean,尤其是在后台分层的场景下,在不同的层之间传递信息,经常需要进行 这样的一个对象复制工作,类似于: val source: ...
- WCF学习之旅——第一个WCF示例(二)
第四步:通过自我寄宿的方式寄宿服务 WCF服务需要依存一个运行着的进程(宿主),服务寄宿就是为服务指定一个宿主的过程.WCF是一个基于消息的通信框架,采用基于终结点(Endpoint)的通信手段. 终 ...
- WCF学习之旅——第一个WCF示例(一)
最近需要用到WCF,所以对WCF进行了解.在实践中学习新知识是最快的,接下来先做了一个简单的WCF服用应用示例. 本文的WCF服务应用功能很简单,却涵盖了一个完整WCF应用的基本结构.希望本文能对那些 ...
- WCF学习之旅——第一个WCF示例(三)
第五步:创建客户端 WCF应用服务被成功寄宿后,WCF服务应用便开始了服务调用请求的监听工作.此外,服务寄宿将服务描述通过元数据的形式发布出来,相应的客户端就可以获取这些元数据.接下来我们来创建客户端 ...
- scala函数式编程(二) scala基础语法介绍
上次我们介绍了函数式编程的好处,并使用scala写了一个小小的例子帮助大家理解,从这里开始我将真正开始介绍scala编程的一些内容. 这里会先重点介绍scala的一些语法.当然,这里是假设你有一些ja ...
- Scala学习笔记(二)表达式和函数
笔记的整理主要针对Scala对比Java的新特性: 1.if表达式 if表达式是有结果返回的. val a= if (5>2) "你好" else 1 a的值为if表达式 ...
- Akka.NET是Java/Scala 流行框架Akka的一个 .NET 开源移植
Akka.NET v1.0 已发布,支持Mono Akka.NET 是Java/Scala 流行框架Akka的一个 .NET 开源移植.可用于构建高并发,分布式和容错事件驱动的应用在 .NET 和 M ...
随机推荐
- 安装Redis 编译make gcc: error trying to exec 'cc1': execvp: 没有该文件或目录的错误
Linux(Redhat) make: gcc: error trying to exec 'cc1': execvp: 没有该文件或目录的错误 排查错误: 1.检查gcc.gcc-c++是否安装rp ...
- 浅谈编程语言中的新宠Python,你叫它如何不火?
论述 凡是对编程有所关注的朋友都已经知道,Python公布于1991年,即使出现的时间不是很遥远,但是在众多爱好者的贡献下已经发展到全民Python的地步. Python最近火起来的笼统原因:面向企业 ...
- 万网主机使用wordpress发送邮件的方法
今天弄了一下午总算明白了,这里写一下具体过程. 首先是邮箱,万网主机是不支持mail()函数的,所以默认的不可用,如果你想发送邮件的话,只能使用fsockopen()函数.首先进入万网主机管理平台,启 ...
- 用C++实现一个不能被继承的类
#include<iostream> using namespace std; class Gradpa { friend class Parent;//声明public或者private ...
- SSM博客登录注册
我的博客采用的是 spring+springmvc+mybatis框架,用maven和git管理项目,之后的其他功能还有待进一步的学习. 首先新建一个maven项目,我的项目组成大概就这样, 建立好项 ...
- Python_数据整理与写入
''' 假设文件data.txt中有若干整数,整数之间使用英文逗号分隔.编写程序读取所有整数,将其按升序后再写入文本文件datta_asc.txt中 ''' import random countNu ...
- PAT1059:Prime Factors
1059. Prime Factors (25) 时间限制 100 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 HE, Qinming Given ...
- 设置ActiveMQ的访问密码
1.设置ActiveMQ的访问密码,以提高ActiveMQ的安全性 2.在ActiveMQ的conf目录的activemq.xml中添加账号密码 2.1 添加的代码如下 <!-- 添加访问Ac ...
- c++ 输出精度
1 保留小数点后**位 cout.flags(ios::fixed); cout.precision(4); //设置输出精度,
- D. Kuro and GCD and XOR and SUM
Kuro is currently playing an educational game about numbers. The game focuses on the greatest common ...