问题描述
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