我在Scala中有一个简单的树结构,如下所示:
sealed abstract class FactsQueryAst[FactType] {
}
object FactsQueryAst {
case class AndNode[FactType](subqueries: Seq[FactsQueryAst[FactType]]) extends FactsQueryAst[FactType]
case class OrNode[FactType](subqueries: Seq[FactsQueryAst[FactType]]) extends FactsQueryAst[FactType]
case class Condition[FactType](fact: FactType, value: FactValue) extends FactsQueryAst[FactType]
}
是否有任何相对简单的方法可以使此结构与高阶函数(如map,foldLeft或filter)一起使用?有关于为自己的集合实现Traversable trait的好文章(http://daily-scala.blogspot.com/2010/04/creating-custom-traversable.html),但是对于树案例似乎过于复杂,或者至少我遗漏了一些主要内容。
UPD。我试图在下面实现朴素的Traversable,但是只是为了打印值而导致无限循环。
sealed abstract class FactsQueryAst[FactType] extends Traversable[FactsQueryAst.Condition[FactType]]
object FactsQueryAst {
case class AndNode[FactType](subqueries: Seq[FactsQueryAst[FactType]]) extends FactsQueryAst[FactType] {
def foreach[U](f: (Condition[FactType]) => U) {subqueries foreach {_.foreach(f)}}
}
case class OrNode[FactType](subqueries: Seq[FactsQueryAst[FactType]]) extends FactsQueryAst[FactType] {
def foreach[U](f: (Condition[FactType]) => U) {subqueries foreach {_.foreach(f)}}
}
case class Condition[FactType](fact: FactType, value: FactValue) extends FactsQueryAst[FactType]{
def foreach[U](f: (Condition[FactType]) => U) {f(this)}
}
}
无限循环的堆栈跟踪如下所示:
at tellmemore.queries.FactsQueryAst$Condition.stringPrefix(FactsQueryAst.scala:65532)
at scala.collection.TraversableLike$class.toString(TraversableLike.scala:639)
at tellmemore.queries.FactsQueryAst.toString(FactsQueryAst.scala:5)
at java.lang.String.valueOf(String.java:2854)
at scala.collection.mutable.StringBuilder.append(StringBuilder.scala:197)
at scala.collection.TraversableOnce$$anonfun$addString$1.apply(TraversableOnce.scala:322)
at tellmemore.queries.FactsQueryAst$Condition.foreach(FactsQueryAst.scala:23)
at scala.collection.TraversableOnce$class.addString(TraversableOnce.scala:320)
at tellmemore.queries.FactsQueryAst.addString(FactsQueryAst.scala:5)
at scala.collection.TraversableOnce$class.mkString(TraversableOnce.scala:286)
at tellmemore.queries.FactsQueryAst.mkString(FactsQueryAst.scala:5)
at scala.collection.TraversableLike$class.toString(TraversableLike.scala:639)
at tellmemore.queries.FactsQueryAst.toString(FactsQueryAst.scala:5)
at java.lang.String.valueOf(String.java:2854)
at scala.collection.mutable.StringBuilder.append(StringBuilder.scala:197)
at scala.collection.TraversableOnce$$anonfun$addString$1.apply(TraversableOnce.scala:322)
at tellmemore.queries.FactsQueryAst$Condition.foreach(FactsQueryAst.scala:23)
答案 0 :(得分:6)
让我们将FactType
重命名为更像是类型参数的东西。我认为仅仅T
命名它有助于表明它是一个类型参数而不是代码中有意义的类:
sealed abstract class FactsQueryAst[T] extends Traversable[T]
因此FactQueryAst
包含T
类型的内容,我们希望能够遍历树以为每个t:T
执行某些操作。实现的方法是:
def foreach[U](f: T => U): Unit
因此,使用FactType
替换代码中的所有T
并修改T
的签名,我最终得到:
object FactsQueryAst {
case class AndNode[T](subqueries: Seq[FactsQueryAst[T]]) extends FactsQueryAst[T] {
def foreach[U](f: T => U) { subqueries foreach { _.foreach(f) } }
}
case class OrNode[T](subqueries: Seq[FactsQueryAst[T]]) extends FactsQueryAst[T] {
def foreach[U](f: T => U) { subqueries foreach { _.foreach(f) } }
}
case class Condition[T](factType: T, value: FactValue) extends FactsQueryAst[T] {
def foreach[U](f: T => U) { f(factType) }
}
}
这样的工作原理如下:
import FactsQueryAst._
case class FactValue(v: String)
val t =
OrNode(
Seq(
AndNode(
Seq(Condition(1, FactValue("one")), Condition(2, FactValue("two")))),
AndNode(
Seq(Condition(3, FactValue("three"))))))
//> t : worksheets.FactsQueryAst.OrNode[Int] = FactsQueryAst(1, 2, 3)
t.map(i => i + 1)
//> res0: Traversable[Int] = List(2, 3, 4)
显然,当您映射时,实现遍历会丢失结构,但这可能足以满足您的用例。如果您有更具体的需求,可以提出另一个问题。
修改强>
事实证明你的初始版本可能会有效。这是一个几乎完全相同的版本,但请注意我在toString
中覆盖Condition
。我怀疑如果你覆盖toString()
你的版本也会起作用:
case class FactValue(v: String)
case class FactType(t: Int)
sealed abstract class FactsQueryAst extends Traversable[FactsQueryAst.Condition]
object FactsQueryAst {
case class AndNode(subqueries: Seq[FactsQueryAst]) extends FactsQueryAst {
def foreach[U](f: Condition => U) { subqueries foreach { _.foreach(f) } }
}
case class OrNode(subqueries: Seq[FactsQueryAst]) extends FactsQueryAst {
def foreach[U](f: Condition => U) { subqueries foreach { _.foreach(f) } }
}
case class Condition(factType: FactType, value: FactValue) extends FactsQueryAst {
def foreach[U](f: Condition => U) { f(this) }
override def toString() = s"Cond($factType, $value)"
}
}
尝试打印对象时发生无限递归;最有可能的原因是TraversableLike
看到Condition
是Traversable
来电mkString
,调用addString
来调用foreach然后进入一个循环。
答案 1 :(得分:1)
我将在如何保留结构上发布我的答案,因为另一个答案太长了(问题出现在你后面的评论中)。虽然,我怀疑使用Kiama是一个更好的选择,这里是保留您的结构并具有类似foldLeft,map和filter的操作的方法。我已经制作了FactType
和FactValue
类型参数,因此我可以使用Int
和String
提供更短的示例,而且它更通用。
这个想法是你的树结构是一个使用3个构造函数的递归结构:AndNode
和OrNode
接受序列,Condition
接受两个参数。我定义了一个 fold 函数,它通过需要3个函数递归地将这个结构转换为另一个类型R
,每个函数对应一个构造函数:
sealed abstract class FactsQueryAst[T, V] {
import FactsQueryAst._
def fold[R](fAnd: Seq[R] => R, fOr: Seq[R] => R, fCond: (T, V) => R): R = this match {
case AndNode(seq) => fAnd(seq.map(_.fold(fAnd, fOr, fCond)))
case OrNode(seq) => fOr(seq.map(_.fold(fAnd, fOr, fCond)))
case Condition(t, v) => fCond(t, v)
}
def mapConditions[U, W](f: (T, V) => (U, W)) =
fold[FactsQueryAst[U, W]](
AndNode(_),
OrNode(_),
(t, v) => {val uw = f(t, v); Condition(uw._1, uw._2) })
}
object FactsQueryAst {
case class AndNode[T, V](subqueries: Seq[FactsQueryAst[T, V]]) extends FactsQueryAst[T, V]
case class OrNode[T, V](subqueries: Seq[FactsQueryAst[T, V]]) extends FactsQueryAst[T, V]
case class Condition[T, V](factType: T, value: V) extends FactsQueryAst[T, V]
}
mapConditions 是根据 fold 实现的。还有一堆其他功能。这是在行动:
object so {
import FactsQueryAst._
val ast =
OrNode(
Seq(
AndNode(
Seq(Condition(1, "one"))),
AndNode(
Seq(Condition(3, "three")))))
//> ast : worksheets.FactsQueryAst.OrNode[Int,String] =
// OrNode(List(AndNode(List(Condition(1,one))),
// AndNode(List(Condition(3,three)))))
val doubled = ast.mapConditions{case (t, v) => (t*2, v*2) }
//> doubled : worksheets.FactsQueryAst[Int,String] =
// OrNode(List(AndNode(List(Condition(2,oneone))),
// AndNode(List(Condition(6,threethree)))))
val sums = ast.fold[(Int, String)](
seq => seq.reduceLeft((a, b) => (a._1 + b._1, a._2 + b._2)),
seq => seq.reduceLeft((a, b) => (a._1 + b._1, a._2 + b._2)),
(t, v) => (t, v))
//> sums : (Int, String) = (4,onethree)
val andOrSwitch = ast.fold[FactsQueryAst[Int, String]](
OrNode(_),
AndNode(_),
(t, v) => Condition(t, v))
//> andOrSwitch : worksheets.FactsQueryAst[Int,String] =
// AndNode(List(OrNode(List(Condition(1,one))),
// OrNode(List(Condition(3,three)))))
val asList = ast.fold[List[(Int, String)]](
_.reduceRight(_ ::: _),
_.reduceRight(_ ::: _),
(t, v) => List((t, v)))
//> asList : List[(Int, String)] = List((1,one), (3,three))
}
答案 2 :(得分:1)
您很有可能需要查看遍历构建器模式。它不是那么直接,但它保留了使用类似工厂模式的操作之间的结构。我建议你调查一下:
http://docs.scala-lang.org/overviews/core/architecture-of-scala-collections.html
模式需要在伴侣对象中实现隐式构建器对象,以及某些可遍历的实现(例如,使用foreach
实现某些特征,可能还有一些更高阶的方法)
我希望这对你有用:)