Scala 如何有效地遍历递归案例类树

Scala 如何有效地遍历递归案例类树,scala,generics,macros,tree-traversal,scala-macros,Scala,Generics,Macros,Tree Traversal,Scala Macros,我想创建一个宏,它生成一个递归遍历案例类实例树的过程,类似于visitor模式。对于类型派生自多个基类型之一的所有字段,生成的代码都应该递归 我想到了一些类似的代码: 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

我想创建一个宏,它生成一个递归遍历案例类实例树的过程,类似于visitor模式。对于类型派生自多个基类型之一的所有字段,生成的代码都应该递归

我想到了一些类似的代码:

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
val instanceOfA = F (D, G (E, "Foo"), 1)

// expected type   extra types that should be included in traversal
//       vv        vv
traverse[A,        B, C] (handlerForA, handlerForB, handlerForC) (instanceOfA)
遍历的类型如下所示,我知道这不是有效的语法,但没有更好的语法:

def traverse[T, U...] (handleT : T => Unit, handleU... : U... => Unit) : T => Unit = macro traverseImpl

我知道Scala不支持可变泛型,但我不明白为什么不应该有一种方法来实现类似宏的功能。但是我还没有找到一种方法可以将多种类型作为参数传递给def宏。

经过一些调查后,我发现您的问题比乍一看要复杂一些。我起草了一个解决方案,其中我使用DSL而不是宏,基于:

然而,我一开始测试它,就发现了一个问题:

val t = traverse[F](handlerForA)(_.nest(
  Nested(GenLens[F](_.a), handlerForA),
  Nested(GenLens[F](_.b), handlerForB),
  // how to put handlerForC?
))

t(instanceOfA)
我可以使用handlerForC。。。因为您的原始类型不仅仅是产品类型。A可能有不同的实现,B和C也可能有不同的实现

那么,我们可以尝试生成一些更复杂的解决方案,其中考虑了副产品吗?好的,在您的确切示例中,如果您的类/特征是密封的,并且所有直接实现必须在同一个文件中实现,则not-编译器只能派生已知的直接子类

但假设你把A,B和C密封起来。在这种情况下,我会尝试使用Prism,以便只有在可能的情况下才能进入,或者使用模式匹配或任何其他等效解决方案。但这使DSL复杂化了——我想这就是为什么do决定首先查看宏的原因

因此,让我们重新思考这个问题

您必须编写的代码将:

考虑从产品类型中提取值 考虑副产品类型上的分支 为每个副产品类型使用提供的处理程序 尽量减少编写的代码量 这一要求听起来很难实现。现在我在想,如果你能使用if为所有trait创建一个公共父类,将所有类提升到Id,然后编写一个遍历函数和一个应用处理程序的函数,你是否能更快地实现目标


当然,所有这些都可以使用def宏以一种或另一种方式实现,但问题是,需求将以现在的方式实现,我很难确保它不会做坏事,由于我们对每个已处理案例的输出都有非常严格的要求,因此即使它们是嵌套的,也会找到它们,而对输入的最不上界是Any/AnyRef的要求非常宽松,因此层次结构是不密封的。我不确定,只要您不想改变任何假设或要求,运行时反射是否不是实现目标的更容易的方法。

我找到了另一个完全不同的解决方案,但有其自身的缺点。我添加了两个特性Traversable和TraverseHandler:

A、 B和C扩展可遍历[A]。。。它们的遍历方法在所有case类中都实现。我使用注释宏是为了避免它们,但它们可能是以下代码扩展的最简单解决方案:

@traversable [TraverseHandler]
case class F (@expand a: A, @expand b: B, c: Int) extends A

这避免了对密封特性的需要,并允许不同的处理程序,同时不会给类本身增加太多开销

当然,如果有更简单或不需要更改类本身的东西,那就太好了。它还要求处理程序执行递归,而递归在另一方面赋予了删除单个元素的能力,而无需对其进行处理,或者允许进行自定义的预/后处理


也许一个更好的解决方案可能是在一个伴生对象中创建一个方法,它为镜头提供了一个类特定的构造函数。但我仍然担心这种结构的递归性可能会使单片眼镜的使用复杂化。如何创建一个依赖于原始镜头的镜头?或者换句话说:镜头有固定点组合器吗?

你真的不需要像光学这样的东西吗:?我的意思是:将每个类的处理程序与每个嵌套级别的镜头提取器结合起来,你应该得到相同的结果,尽管语法略有不同。谢谢你的提示。Monocle看起来很有趣,它甚至支持创建编辑过的实例。我想知道它是否能提供足够的灵活性和性能,以便在我相当复杂的场景中工作,而不会产生太多的开销。一个问题:您是否希望为F进行分支,例如,您是否希望同时为A和B进行遍历?我正在考虑为sport起草一个实现,因此根据具体情况,我会采取不同的方法。是的,我会立即遍历它们,因为我可以使用递归,其中包括多种类型,如case类H a:a扩展B;对象I扩展B;我想去的地方
立即更改所有F.C。感谢您为找到解决方案所做的努力。递归方案看起来可以满足我的需要。不幸的是,我没有时间完全理解它们。这就是为什么我现在对注释宏使用更面向对象的方法。
trait Traversable[+T] {
    def traverse (handler: TraverseHandler) : T
}
trait TraverseHandler {
    def handleA (a: A) : A
    def handleB (b: B) : B
    def handleC (c: C) : C
}
@traversable [TraverseHandler]
case class F (@expand a: A, @expand b: B, c: Int) extends A
case class F (a: A, b: B, c: Int) extends A {
    def traverse (handler: TraverseHandler) : F = {
        val newA = handler.handleA(a)
        val newB = handler.handleB(b)
        if ((newA ne a) || (newB ne b))
            copy (a = newA, b = newB)
        else
            this
    }
}