Scala微服务架构 二
三. Scala的Macro(宏)
Scala Macros对scala函数库编程人员来说是一项不可或缺的编程工具,可以通过它来解决一些用普通编程或者类层次编程(type level programming)都无法解决的问题,这是因为Scala Macros可以直接对程序进行修改。
说到对程序进行修改,几个概念一定要先理解,"编译期"和"运行期",Java也有一个可以修改程序的功能,大家一定用过,就是反射.不是在运行期,编译器也不知道接下来会发生什么,会执行哪些代码(这就是==动态性==).
而scala是java的衍生语言,自然也有反射,而且它还有一种更高级的反射,就是编译时反射,它就是宏.
3.1 什么是宏?
一般说来,宏是一种规则或模式,或称语法替换 ,用于说明某一特定输入(通常是字符串)如何根据预定义的规则转换成对应的输出(通常是字符串,或者是类,方法等)。这种替换在预编译时进行,称作宏展开。
通过上面的定义,感觉和C的宏概念差不多.但C的宏只不过是一段语法的替换,然而Scala的宏却可以通过表达式树控制一节代码(类,或者方法)的生成。获得了控制代码的执行顺序(见惰性计算和非限制函数)的能力,使得新创建的语法结构与语言内建的语法结构不可区分。
宏,从程序抽象的角度来看,可能不太容易调试和维护,但是能够很强大的固定我们的设计. 同时使用宏能够==大量==的减少样板代码.比如Scala的assert
和require
就是使用宏实现的.
3.2 宏出现的意义?
- 编译期元编程
- 更完善的错误检查
3.2.1 编译期元编程
什么是元编程?
百度词条的一句话:
元编程(Metaprogramming)是指某类计算机程序的编写,这类计算机程序编写或者操纵其他程序(或者自身)作为它们的数据,==或者在运行时完成部分本应在编译时完成的工作==。很多情况下与手工编写全部代码相比工作效率更高。编写元程序的语言称之为元语言,被操作的语言称之为目标语言。==一门语言同时也是自身的元语言的能力称之为反射==。
元编程是用来产生代码的程序,操纵代码的程序,在运行时创建和修改代码而非编程时,这种程序叫做元程序。而编写这种程序就叫做元编程。
所以,元编程技术在多种编程语言中都可以使用,但更多的还是被应用于动态语言中,因为动态语言提供了更多的在运行时将代码视为数据进行操纵的能力。
虽然静态语言也支持元编程(反射机制),但是仍然没有诸如Ruby这样的更趋动态性的语言那么透明,这是因为静态语言在运行时其代码和数据是分布在两个层次上的。
最后可以理解为,元编程就是程序可以操作更小的粒度和动作.
3.2.2 更完善的错误检查
引自知乎https://www.zhihu.com/question/27685977/answer/38014170
首先思考一个问题:如果你的应用程序有bug,那么你希望在什么情况下发现呢?
- 编译时:这是最理想的状态,如果一个bug可以通过编译器检查出来,那么程序员可以在第一时间发现问题,基本上就是一边写一边fix。这也正是静态编译型语言的强大优势。
- 单元测试:没有那么理想但是也不差。每写完一段code跑一下测试,看看有没有新的bug出来。对于scala来说,现在的工具链已经不错了,左屏sbt > ~test,右屏写代码惬意得很。
- 运行时:这个就比较糟糕了。运行时才报错意味着你得首先打包部署,这个时间开销通常就比较大,而且在许多公司,你还要时不时的解决环境问题,很是让人抓狂。
而Scala的宏,就是可以将一些运行期才会出现的错误,在编译器暴露出来.
3.3 宏的编译过程?
Scala是如何编译宏的呢?
引用自https://www.cnblogs.com/tiger-xc/p/6112143.html
明白了上面的流程之后,我们出个栗子:
object modules { greeting("john") } object mmacros { def greeting(person: String): Unit = macro greetingMacro def greetingMacro(c: Context)(person: c.Expr[String]): c.Expr[Unit] = { import c.universe._ println("compiling greeting ...") val now = reify {new Date().toString} reify { println("Hello " + person.splice + ", the time is: " + new Date().toString) } } }
以上代码的执行逻辑如下:
注意编译器在运算greetingMacro时会以AST方式将参数person传入。由于在编译modules对象时需要运算greetingMacro函数,所以greetingMacro函数乃至整个mmacros对象必须是已编译状态,这就意味着modules和mmacros必须分别在不同的源代码文件里,而且还要确保在编译modules前先完成对mmacros的编译.
3.4 编写宏实现
其实宏的使编写并不难,api已经帮我们做好了一切,我们只要关注如何使用获取宏参数和宏的返回值即可.
上面栗子中的代码,greetingMacro
方法就是一个最简单的宏实现,代码如下:
def greetingMacro(c: Context)(person: c.Expr[String]): c.Expr[Unit] = { import c.universe._ println("compiling greeting ...") val now = reify {new Date().toString} reify { println("Hello " + person.splice + ", the time is: " + new Date().toString) } }
但是想要实现更多的功能,还需要更加深入的学习Scala的宏和表达式树.
※. 本期语法糖
※.1 implicit macor (隐式宏)
开局出个栗子
trait Showable[T] { def show(x: T): String } def show[T](x: T)(implicit s: Showable[T]) = s.show(x) implicit object IntShowable extends Showable[Int] { def show(x: Int) = x.toString } show(42) // return "42" show("42") // compilation error
可以调用成功show()
,主要因为名称空间存在Showable
的子类IntShowable
,并且是implicit object
,这个implicit object
的作用上一篇已经讲过了,就不说了.
上面代码,乍一看还可以,但是如果扩展起来就不是很舒服了,如果要让show("42")
也可用,我们就需要添加如下代码:
implicit object StringShowable extends Showable[String] { def show(x: String) = x }
※.2 宏注解 Macro Annotations ==> @compileTimeOnly("")
开局处个栗子,可以自动为case class
或class
在编译时生成一个名为TempLog
的方法.
import scala.reflect.macros.Context import scala.language.experimental.macros import scala.annotation.StaticAnnotation import scala.annotation.compileTimeOnly @compileTimeOnly("temp log print") class AnPrint(msg: Any*) extends StaticAnnotation { def macroTransform(annottees : Any*) : Any = macro AnPrintMacroImpl.impl }
官网栗子,我们的代码也比较常见,继承了StaticAnnotation
,表示这是一个注解类,有兴趣的朋友可以看看上一期文章.
主要说的是上面
@compileTimeOnly("temp log print")
官网解释
First of all, note the @compileTimeOnly annotation. It is not mandatory, but is recommended to avoid confusion
首先,这不是强制性的,即便不写,也会被编译器自动扩展上.但还是建议加上避免混乱.
然后是宏的具体实现,如下:
object AnPrintMacroImpl { def impl(c : whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = { import c.universe._ val tree = annottees.map(_.tree).toList.head val (className, fields, parents, body) = tree match{ case q"case class $className(..$fields) extends ..$parents { ..$body }" => (className, fields, parents, body) case q"class $className(..$fields) extends ..$parents { ..$body }" => (className, fields, parents, body) } //TempLog val LogDefName = TermName("TempLog") val LogDefImpl = q"""def $LogDefName(sss: Any):Unit=println(" ===> " + sss)""" val out = q""" case class $className(..$fields) extends ..$parents { ..$LogDefImpl ..$body } """ println(showRaw(tree)) c.Expr(out) } }
里面的具体细节,主要是宏将类变成AST,然后利用模式匹配,来解析类信息,之后可以加入自己定义的任何操作,最后用Block封装起来,这样子,一个简单的宏就实现了.
我们测试一下:
package myTest @AnPrint("clock") class ccc(val a: String = "aaa", val b: String = "bbb"){ TempLog("init b") } object annotationPrintTest extends App { println("start") val a = new b("aiyou", "wolegequ") a.TempLog("打印我了") println("end") }
注意,这里需要先编译
AnPrintMacroImpl
和AnPrint
文件,才可以测试通过
打印结果如下:
start ===> init b ===> 打印我了 end
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
linux进程内存布局
一个程序本质上都是由 BSS 段、data段、text段三个组成的。这样的概念在当前的计算机程序设计中是很重要的一个基本概念,而且在嵌入式系统的设计中也非常重要,牵涉到嵌入式系统运行时的内存大小分配,存储单元占用空间大小的问题。 BSS段:在采用段式内存管理的架构中,BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。 数据段:在采用段式内存管理的架构中,数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。 代码段:在采用段式内存管理的架构中,代码段(text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域属于只读。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。 程序编译后生成的目标文件至少含有这三个段,这三个段的大致结构图如下所示: 其中.text即为代码段,为只读。.bss段包含程序中未初始化的全局变量和stat...
- 下一篇
探索多媒体开发最新最佳实践,我们在深圳等你
2018年初始,音视频技术生态并不平静,Codec争夺愈加激烈,新一代标准的挑战一浪高过一浪;WebRTC的定版也为打通浏览器、移动端乃至IoT带来了机会;此外AI、区块链技术的兴起,催化着与多媒体领域的化学反应。新技术正在对安防、视频会议、社交、教育、金融等行业产生影响,这无疑对多媒体开发者带来了新的挑战。 7月14日·深圳| LiveVideoStack携手腾讯、北京大学、魅族、即构科技、又拍云、华为等技术大咖一同探索多媒体开发新趋势,探讨开发难点、技术转型,展现新技术在音视频领域的最新、最佳实践。 讲师与话题 王振宇 北京大学信息科技技术学院博士 Speaker:北京大学信息工程学院工程师,北京大学信息科技技术学院博士,北大深圳研究生院在站博士后。2007年、2010年、2018年于北京大学信息科学技术学院分别获计算机专业学士、硕士和博士学位。主要研究方向为视频编解码质量优化,编解码并行加速优化,3D视频内容生成等。在相关领域发表国际会议两篇,国际期刊一篇,申请5个国家专利,2个已被授权。自2007年起,参与并实施了多个大型工程项目,包括多视及自由视点传输系统、基于众核的并行视频...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- CentOS7安装Docker,走上虚拟化容器引擎之路
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS8编译安装MySQL8.0.19
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS关闭SELinux安全模块