如何通过Scala宏实现多基类型的递归Case Class树遍历
实现Scala宏生成多类型递归遍历代码
你的需求完全可以通过Scala宏实现——虽然Scala没有原生可变参数泛型,但我们可以利用宏的编译期类型检查能力,解析多个handler的类型作为遍历目标,自动生成递归遍历Case Class树的代码。下面是具体的实现方案和示例:
核心思路
我们的宏会做这几件事:
- 解析传入的每个handler的输入类型,得到需要处理的基类型集合(比如你的
A、B、C) - 对目标实例进行递归遍历:
- 如果当前实例匹配某个基类型,调用对应的handler
- 如果是Case Class/Product类型,遍历它的所有字段
- 如果是集合(
Iterable)或Option,遍历内部元素
- 编译期生成高效的遍历代码,和手写访问者模式性能一致
完整实现代码
import scala.language.experimental.macros import scala.reflect.macros.blackbox object Traverser { // 入口函数:接受可变数量的handler和要遍历的实例 def traverse(handlers: (Any => Unit)*)(instance: Any): Unit = macro traverseImpl def traverseImpl(c: blackbox.Context)(handlers: c.Tree*)(instance: c.Tree): c.Tree = { import c.universe._ // 第一步:提取每个handler对应的目标类型 val handlerTypePairs = handlers.map { handler => handler match { case q"($f: ($targetType) => Unit)" => targetType -> handler case _ => c.abort(c.enclosingPosition, "所有handler必须是 T => Unit 类型的函数") } } // 递归生成遍历逻辑的辅助函数 def generateTraversal(tree: c.Tree): c.Tree = { val instanceType = tree.tpe.dealias // 1. 检查当前实例是否匹配任何目标类型,调用对应的handler val handlerCalls = handlerTypePairs.map { case (targetType, handler) => if (instanceType <:< targetType) q"$handler($tree)" else EmptyTree }.filter(_ != EmptyTree) // 2. 处理不同类型的嵌套结构 val nestedTraversal = if (instanceType <:< typeOf[Product]) { // 处理Case Class:遍历所有case accessor字段 val fields = instanceType.decls.collect { case method: MethodSymbol if method.isCaseAccessor => method } fields.map(field => generateTraversal(q"$tree.${field.name}")) } else if (instanceType <:< typeOf[Iterable[_]]) { // 处理集合类型:遍历每个元素 q"$tree.foreach { elem => ${generateTraversal(q"elem")} }" } else if (instanceType <:< typeOf[Option[_]]) { // 处理Option:遍历内部元素(如果存在) q"$tree.foreach { elem => ${generateTraversal(q"elem")} }" } else { // 非目标类型且无嵌套结构,跳过处理 EmptyTree } // 组合所有逻辑:先调用handler,再处理嵌套结构 q"..$handlerCalls; ..$nestedTraversal" } // 生成最终的遍历代码 generateTraversal(instance) } }
你的示例代码的使用方式
trait A trait B trait C case object D extends A { } case object E extends C { } case class F(a: A, b: B, c: Int) extends A case class G(d: C, e: String) extends B object Main extends App { val instanceOfA = F(D, G(E, "Foo"), 1) // 定义各个类型的handler val handlerForA: A => Unit = a => println(s"处理A类型实例: $a") val handlerForB: B => Unit = b => println(s"处理B类型实例: $b") val handlerForC: C => Unit = c => println(s"处理C类型实例: $c") // 调用宏生成的遍历函数 Traverser.traverse(handlerForA, handlerForB, handlerForC)(instanceOfA) }
运行这段代码会输出:
处理A类型实例: F(D,G(E,Foo),1) 处理A类型实例: D 处理B类型实例: G(E,Foo) 处理C类型实例: E
优化:更类型安全的版本(可选)
如果想要编译期严格约束handler和目标类型的匹配,可以结合Shapeless的HList实现强类型版本:
import shapeless._ import shapeless.ops.hlist._ object TypedTraverser { def traverse[Ts <: HList](handlers: Ts)(instance: Any)( implicit ev: Ts ~>> ({ type L[T] = T => Unit })#L ): Unit = macro traverseImpl[Ts] def traverseImpl[Ts <: HList](c: blackbox.Context)( handlers: c.Tree, instance: c.Tree )(ev: c.Tree): c.Tree = { import c.universe._ // 这里的实现逻辑和之前类似,但通过Shapeless HList提取每个handler的类型 // 编译期确保每个handler的类型和Ts中的类型一一对应 // 具体实现可以参考上面的基础版本,替换类型提取逻辑即可 ??? } }
使用方式:
TypedTraverser.traverse[A :: B :: C :: HNil](handlerForA :: handlerForB :: handlerForC :: HNil)(instanceOfA)
关键说明
- 宏在编译期生成代码,运行时没有额外开销,和手写遍历逻辑性能一致
- 自动处理Case Class的所有字段,无需手动编写访问者模式的各个节点逻辑
- 支持集合、Option等常见嵌套类型的遍历,可以根据需求扩展更多类型(比如Map)
内容的提问来源于stack exchange,提问作者Johannes Matokic
相关产品推荐
相关产品推荐

