如何使用Scala实现的树与高阶集合函数一起使用?

时间:2013-06-10 02:42:45

标签: scala

我在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)

3 个答案:

答案 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看到ConditionTraversable来电mkString,调用addString来调用foreach然后进入一个循环。

答案 1 :(得分:1)

我将在如何保留结构上发布我的答案,因为另一个答案太长了(问题出现在你后面的评论中)。虽然,我怀疑使用Kiama是一个更好的选择,这里是保留您的结构并具有类似foldLeft,map和filter的操作的方法。我已经制作了FactTypeFactValue类型参数,因此我可以使用IntString提供更短的示例,而且它更通用。

这个想法是你的树结构是一个使用3个构造函数的递归结构:AndNodeOrNode接受序列,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实现某些特征,可能还有一些更高阶的方法)

我希望这对你有用:)