阅读由专家撰写的Scala文档可以得到尾部递归优于while循环的印象,即使后者更简洁明了.这是一个例子
object Helpers {
implicit class IntWithTimes(val pip:Int) {
// Recursive
def times(f: => Unit):Unit = {
@tailrec
def loop(counter:Int):Unit = {
if (counter >0) { f; loop(counter-1) }
}
loop(pip)
}
// Explicit loop
def :@(f: => Unit) = {
var lc = pip
while (lc > 0) { f; lc -= 1 }
}
}
}
Run Code Online (Sandbox Code Playgroud)
(要清楚的是,专家根本没有解决循环问题,但在这个例子中,他们选择以这种方式编写一个循环,就好像本能一样,这就是我提出的问题:我是否应该发展出类似的本能.. )
while循环唯一可能更好的方面是迭代变量应该是循环体的局部变量,并且变量的变异应该在固定的位置,但Scala选择不提供该语法.
清晰度是主观的,但问题是(尾部)递归样式是否提供了改进的性能?
Hug*_*ira 18
我很确定,由于JVM的局限性,Scala编译器并不是每个潜在的尾递归函数都会被优化掉,因此对性能问题的简短(有时是错误的)答案是否定的.
对你这个更普遍的问题(有优势)的长期答案更加人为.请注意,通过使用while,您实际上是:
逐个错误和可变性的危险将确保从长远来看,您将引入带有while模式的错误.实际上,您的times功能可以很容易地实现为:
def times(f: => Unit) = (1 to pip) foreach f
Run Code Online (Sandbox Code Playgroud)
这不仅更简单,更小,而且还避免了任何瞬态变量和可变性的产生.实际上,如果您调用的函数类型对结果很重要,那么while构造将开始变得更加难以阅读.请尝试使用以下内容实现以下内容whiles:
def replicate(l: List[Int])(times: Int) = l.flatMap(x => List.fill(times)(x))
Run Code Online (Sandbox Code Playgroud)
然后继续定义一个执行相同操作的尾递归函数.
更新:
我听到你说:"嘿!这是作弊!foreach既不是while也不是tail-rec电话".真的吗?看看到Scala的定义foreach为Lists:
def foreach[B](f: A => B) {
var these = this
while (!these.isEmpty) {
f(these.head)
these = these.tail
}
}
Run Code Online (Sandbox Code Playgroud)
如果您想了解有关Scala中递归的更多信息,请查看此博客文章.进入函数式编程后,请疯狂阅读Rúnar的博客文章.更多信息在这里和这里.
通常,直接尾部递归函数(即始终直接调用自身且不能被覆盖的递归函数)将始终while由编译器优化为循环。您可以使用@tailrec注释来验证编译器是否可以针对特定功能执行此操作。
通常,可以将任何尾部递归函数(通常由编译器自动)重写为while循环,反之亦然。
以(尾)递归样式编写函数的目的不是为了最大化性能甚至简明扼要,而是使代码的意图尽可能清晰,同时最小化引入错误的机会(通过消除可变变量,通常这是可变的)使其更难以跟踪函数的“输入”和“输出”是什么)。正确编写的递归函数包含一系列检查终止条件(使用级联if- else或模式匹配)的检查,如果没有满足任何终止条件,则进行递归调用(仅在没有尾递归的情况下为复数)。
当存在几种可能的终止条件时,使用递归的好处最为明显。通常,一系列if条件或模式比将while一堆(可能是复杂且相互关联的)布尔表达式&&组合在一起的单个条件更容易理解,尤其是如果返回值需要根据哪个终止条件而有所不同时,更是如此遇见。
| 归档时间: |
|
| 查看次数: |
8433 次 |
| 最近记录: |