在没有样板代码的情况下遍历/折叠Scala中的嵌套案例类

时间:2016-11-10 22:07:36

标签: scala fold scala-cats

我有一些用于混合总和和产品类型的案例类:

sealed trait Leaf
case class GoodLeaf(value: Int) extends Leaf
case object BadLeaf extends Leaf

case class Middle(left: Leaf, right: Leaf)

case class Container(leaf: Leaf)

case class Top(middle : Middle, container: Container, extraLeaves : List[Leaf])

我希望使用此Top结构进行类似折叠的操作。例子包括:

  • 计算BadLeaf
  • 的出现次数
  • 汇总GoodLeaf s
  • 中的所有值

以下是执行操作的一些代码:

object Top {
  def fold[T](accu: T)(f : (T, Leaf) => T)(top: Top) = {
    val allLeaves =  top.container.leaf :: top.middle.left :: top.middle.right :: top.extraLeaves
    allLeaves.foldLeft(accu)(f)
  }

  private def countBadLeaf(count: Int, leaf : Leaf) = leaf match {
    case BadLeaf => count + 1
    case _ => count
  }

  def countBad(top: Top): Int = fold(0)(countBadLeaf)(top)

  private def sumGoodLeaf(count: Int, leaf : Leaf) = leaf match {
    case GoodLeaf(v) => count + v
    case _ => count
  }

  def sumGoodValues(top: Top) = fold(0)(sumGoodLeaf)(top)
}

我正在处理的现实生活结构比我组建的例子复杂得多。是否有任何技术可以帮助我避免编写大量的样板代码?

我已经将cats库作为依赖项,因此首选使用该库的解决方案。我愿意包含新的依赖项以解决这个问题。

对于我的特定示例,该定义不是递归的,但我也有兴趣看到一个适用于递归定义的解决方案。

1 个答案:

答案 0 :(得分:1)

您可以创建一个函数,返回Top的所有叶子,就像使用allLeaves一样,这样您就可以使用List[Leaf](包含所有现有fold和Scala库,Cats等提供的其他功能。)

例如:

def topLeaves(top: Top): List[Leaf] =
  top.container.leaf :: top.middle.left :: top.middle.right :: top.extraLeaves

val isBadLeaf: Leaf => Boolean = {
  case BadLeaf => true
  case _       => false
}

val leafValue: Leaf => Int = {
  case GoodLeaf(v) => v
  case _           => 0
}

您可以将其用作

import cats.implicits._
// or
// import cats.instances.int._
// import cats.instances.list._
// import cats.syntax.foldable._

val leaves = topLeaves(someTop)

val badCount   = leaves.count(isBadLeaf)
val badAndGood = leaves.partition(isBadLeaf) // (List[Leaf], List[Leaf])
val sumLeaves  = leaves.foldMap(leafValue)

我不确定这对您的实际用例是否有帮助?一般情况下,异构结构(如Top),您可能希望以某种方式将其转换为更加齐同的内容(如List[Leaf]Tree[Leaf]),您可以将其折叠起来。

如果你有一个递归结构,你可以看一些关于递归方案的讨论(在Scala中使用Matryoshka库)。