将使用访问者的 Traversable 迁移到 Scala 2.13 中的 Iterable

问题描述

migration guide to Scala 2.13 说明 Traversable 已被删除,而应使用 Iterable。对于一个使用访问者来实现树的 foreach 类中的 Node 方法的项目来说,这种变化尤其令人讨厌:

case class Node(val subnodes: Seq[Node]) extends Traversable[Node] {
  override def foreach[A](f: Node => A) = Visitor.visit(this,f)
}

object Visitor {
  def visit[A](n: Node,f: Node => A): Unit = {
    f(n)
    for (sub <- n.subnodes) {
      visit(sub,f)
    }
  }
}

object Main extends App {
  val a = Node(Seq())
  val b = Node(Seq())
  val c = Node(Seq(a,b))
  for (Node(subnodes) <- c) {
    Console.println("Visiting a node with " + subnodes.length + " subnodes")
  }
}

输出

Visiting a node with 2 subnodes
Visiting a node with 0 subnodes
Visiting a node with 0 subnodes

迁移到 Scala 2.13 的一个简单解决方法是首先将访问过的元素存储在 remaining 缓冲区中,然后用于返回迭代器:

import scala.collection.mutable
import scala.language.reflectiveCalls

case class Node(val subnodes: Seq[Node]) extends Iterable[Node] {
  override def iterator: Iterator[Node] = {
    val remaining = mutable.Queue.empty[Node]
    Visitor.visit(this,item => iterator.remaining.enqueue(item))
    remaining.iterator
  }
}

// Same Visitor object
// Same Main object

这个解决方案的缺点是它引入了新的分配,给 GC 带来压力,因为访问的元素数量通常非常大。

您是否有关于如何使用现有访问者但不引入新分配从 Traversable 迁移到 Iterable 的建议?

解决方法

如您所见,您需要扩展 Iterable 而不是 Traversable。你可以这样做:

case class Node(name: String,subnodes: Seq[Node]) extends Iterable[Node] {
  override def iterator: Iterator[Node] = Iterator(this) ++ subnodes.flatMap(_.iterator)
}

val a = Node("a",Seq())
val b = Node("b",Seq())
val c = Node("c",Seq(a,b))
val d = Node("d",Seq(c))

for (node@Node(name,_) <- d) {
  Console.println("Visiting node " + name + " with " + node.subnodes.length + " subnodes")
}

输出:

Visiting node d with 1 subnodes
Visiting node c with 2 subnodes
Visiting node a with 0 subnodes
Visiting node b with 0 subnodes

然后你可以做更多的操作比如:

d.count(_.subnodes.length > 1)

代码在 Scastie 运行。

,

这是一个示例,您的代码可以使用 pool = multiprocessing.Semaphore(multiprocessing.cpu_count() - 1) #this will detect the number of cores in your system and creates a semaphore with that value. 实现并且不需要访问者:

LazyList

输出

case class Node(val subnodes: Seq[Node]) {
  
  def recursiveMap[A](f: Node => A): LazyList[A] = {
    def expand(node: Node): LazyList[Node] = node #:: LazyList.from(node.subnodes).flatMap(expand)
    expand(this).map(f)
  }
}

val a = Node(Seq())
val b = Node(Seq())
val c = Node(Seq(a,b))

val lazyList = c.recursiveMap { node =>
  println("computing value")
  "Visiting a node with " + node.subnodes.length + " subnodes"
}

println("started computing values")

lazyList.iterator.foreach(println)

如果您不自己存储 started computing values computing value Visiting a node with 2 subnodes computing value Visiting a node with 0 subnodes computing value Visiting a node with 0 subnodes 引用并且只存储迭代器,那么 JVM 将能够随时对值进行 GC。

,

我们最终编写了一个最小的 .content { max-width: 980px } .section,.code { margin-top: 16px !important; margin-bottom: 16px !important } .code,.codespan { font-family: Consolas,"courier new"; font-size: 16px } .code { width: auto; background-color: #fff; padding: 8px 12px; border-left: 4px solid #4CAF50; word-wrap: break-word } .codespan { color: crimson; background-color: #f1f1f1; padding-left: 4px; padding-right: 4px; font-size: 110% } trait,只实现了我们代码库中使用的方法。这样就没有额外的开销,也不需要改变访问者的逻辑。

Traversable