Scala 中的宏
- Scala 中的宏要复杂的多,但它对生成代码的方式,提供了更大的灵活性。
- Scala 宏仍然是用 Scala 写的,一定程序上保证了开发体验的一致。
- Scala 宏总是要返回一个AST,这需要你对 Scala AST 有一定的了解。
- 但 Quasiquotes 可以帮你轻松生成 AST。
如:
import scala.reflect.macros.blackbox
import scala.language.experimental.macros
object Hello {
def hello(msg:String): Unit = macro helloImpl
def helloImpl(c: blackbox.Context)(msg: c.Expr[String]): c.Expr[Unit] = {
import c.universe._
c.Expr(q"""println("hello!")""")
}
}
Scala 宏现状
- 2.10.x
- 需要引入外部编译器插件 macro paradise compiler plugin。
- 需要引入org.scalamacros quasiquotes。
- 2.11.x+
- 引入scala-reflect即可。
- 开始引入黑盒和白盒概念。
- 宏实现直到2.13.7仍是实验性、不稳定的API。
- Def macros 实现工具方法,类型转换,减少冗余代码等
- Implicit macros 与隐式参数结合,在开源库中用的很多
- Macro annotations 编译期反射
- 2.13.x 需要scalac参数
-Ymacro-annotations
。 - 2.11.x,2.12.x需要 macro paradise compiler plugin。
- 2.13.x 需要scalac参数
- Scala3(dotty是Scala3编译器的名称)重新设计了宏API,不兼容Scala2。
注解宏
- 同样是编译期改变代码行为,仅支持使用白盒宏。
- 具有与Java Annotation Processor相似功能。
- 与Scala注解也类似,继承StaticAnnotation特质。
- 注解宏执行时依赖macro paradise compiler plugin。
- 基本与使用Java注解一样。
- 类上 @SerialVersionUID
- 方法上 @tailrec @inline
- 字段上 @transient @volatile @BeanProperty
- 泛型上 @specialized
- 其他 @unchecked
Scala中Lombok的可行性
字节码生成+Intellij支持
在这里custom-scala-plugin指的是自己编写的Scala-Macro-Tools插件。
编写自己的宏注解一 builder
步骤
- 定义注解 限定使用场景:替普通类或样例类的主构造函数生成builder模式
- 定义宏实现
- 1.获取主构造函数中的必要内容
- 类的类型名称
- 是否样例类
- 构造函数是否柯里化
- 参数列表的语法树
- 每个参数的名称
- 每个参数的类型
- 每个参数的默认值
- 每个参数的修饰符
- 类上的类型参数(泛型)和泛型边界限定符
- 2.使用1获取的信息创建一个Builder类的语法树
- 每个参数对应一个内部私有成员变量
- 每个参数对应一个公共set方法
- 3.使用1获取的信息创建一个builder方法的语法树,如有必要顺便创建一个该类对应的单例对象的语法树
- 4.将builder方法和Builder类放入单例对象的定义中,返回
- 1.获取主构造函数中的必要内容
具体实现
/*
* Copyright (c) 2021 jxnu-liguobin && contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.dreamylost.macros
import scala.reflect.macros.whitebox
/**
*
* @author 梦境迷离
* @since 2021/7/7
* @version 1.0
*/
object builderMacro {
class BuilderProcessor(override val c: whitebox.Context) extends AbstractMacroProcessor(c) {
import c.universe._
private def getBuilderClassName(classTree: TypeName): TypeName = {
TypeName(classTree.toTermName.decodedName.toString + "Builder")
}
private def getFieldDefinition(field: Tree): Tree = {
val ValDef(_, name, tpt, rhs) = field
q"private var $name: $tpt = $rhs"
}
private def getFieldSetMethod(typeName: TypeName, field: Tree, classTypeParams: List[Tree]