You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Scala宏实现多基类型的递归Case Class树遍历

实现Scala宏生成多类型递归遍历代码

你的需求完全可以通过Scala宏实现——虽然Scala没有原生可变参数泛型,但我们可以利用宏的编译期类型检查能力,解析多个handler的类型作为遍历目标,自动生成递归遍历Case Class树的代码。下面是具体的实现方案和示例:

核心思路

我们的宏会做这几件事:

  1. 解析传入的每个handler的输入类型,得到需要处理的基类型集合(比如你的A、B、C)
  2. 对目标实例进行递归遍历:
    • 如果当前实例匹配某个基类型,调用对应的handler
    • 如果是Case Class/Product类型,遍历它的所有字段
    • 如果是集合(Iterable)或Option,遍历内部元素
  3. 编译期生成高效的遍历代码,和手写访问者模式性能一致

完整实现代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:37:52