Scala 2中如何通过Macro减少样板代码 自动生成Tuple1到Tuple22隐式方法
Scala 2宏实现Tuple1~Tuple22隐式方法自动生成方案
前置配置(SBT)
首先需要开启Scala 2宏支持,在build.sbt中加入如下配置:
scalaVersion := "2.13.12" // 也支持2.12.x系列版本 libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided // 2.13.x版本添加该编译选项 scalacOptions += "-Ymacro-annotations" // 如果是2.12及更早版本,用下面的插件替代上面的编译选项 // addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full)
注意:宏实现代码需要在业务代码之前编译,建议将宏定义放到单独的子模块中,避免单模块下的编译顺序问题。
宏注解实现
自定义宏注解,负责在编译期自动生成1~22的Tuple对应的隐式方法:
import scala.annotation.{StaticAnnotation, compileTimeOnly} import scala.language.experimental.macros import scala.reflect.macros.blackbox @compileTimeOnly("enable macro annotations to expand @GenerateTupleCases") class GenerateTupleCases extends StaticAnnotation { def macroTransform(annottees: Any*): Any = macro GenerateTupleCasesImpl.impl } object GenerateTupleCasesImpl { def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = { import c.universe._ // 批量生成1~22对应的caseTuple方法 val caseMethods = (1 to 22).map { n => val wildcardParams = List.fill(n)(tq"_") val tupleType = tq"Tuple${n}[..$wildcardParams]" val methodName = TermName(s"caseTuple${n}") // Tuple1单独保留原代码的返回类型细化逻辑 if (n == 1) { q""" implicit def $methodName[P <: $tupleType](implicit fm: FlatMapper[P, Flat.type]): Flat.Case[P] { type Result = FlatMapper[P, Flat.type]#Out } = at[P](_.flatMap(Flat)) """ } else { q""" implicit def $methodName[P <: $tupleType](implicit fm: FlatMapper[P, Flat.type]) = at[P](_.flatMap(Flat)) """ } } // 将生成的方法注入到被注解的对象中 annottees.head match { case q"object $name extends $parent { ..$body }" => q"object $name extends $parent { ..$body ; ..$caseMethods }" case _ => c.abort(c.enclosingPosition, "@GenerateTupleCases 只能应用在object上") } } }
原业务代码改造
所有重复的样板代码都可以删除,仅需要给Flat对象加上上面定义的宏注解即可:
object TupleFlatten { import shapeless._ import ops.tuple.FlatMapper import syntax.std.tuple._ trait LowPriorityFlat extends Poly1 { implicit def default[T] = at[T](Tuple1(_)) } @GenerateTupleCases // 编译时自动生成Tuple1~Tuple22的所有隐式方法 object Flat extends LowPriorityFlat }
效果说明
宏生成的代码和手写结构完全一致,无任何运行时开销,编译后字节码和手写版本没有区别,可直接正常使用嵌套Tuple压平功能:
val testTuple = (1, ("2", 3.0), (4L, (true, 5))) val flattened = testTuple.flatMap(Flat) // 输出:(1, "2", 3.0, 4L, true, 5)
内容的提问来源于stack exchange,提问作者counter2015
相关产品推荐
相关产品推荐

