用递归术语来表达你的问题常常会使问题简化,如果应用了尾递归优化(可以通过注释检测),编译器甚至会将你的代码转换为正常的循环。对比一个标准的命令式版本的堆排序(fix-down):
每次进入while循环,我们工作在前一次迭代时污染过的状态。每个变量的值是那一分支所进入函数,当找到正确的位置时会在循环中返回。
(敏锐的读者会在Dijkstra的“Go To声明是有害的”一文找到相似的观点)
考虑尾递归的实现:
final def fixDown(heap: Array[T], i: Int, j: Int) {
if (j < i*2) return
val m = if (j == i*2 || heap(2*i) < heap(2*i+1)) 2*i else 2*i + 1
if (heap(m) < heap(i)) {
swap(heap, i, m)
fixDown(heap, m, j)
}
每次迭代都是一个明确定义的历史清白的变量,并且没有引用单元:到处都是不变的(invariants)。更容易实现,也容易阅读。也没有性能方面的惩罚:因为方法是尾递归的,编译器会转换为标准的命令式的循环。
Returns可以用于切断分支和建立不变量(establish invariants)。这减少了嵌套,并且容易推断后续的代码的正确性,从而帮助了读者。这尤其适用于卫语句(guard clauses):
def compare(a: AnyRef, b: AnyRef): Int = {
if (a eq b)
val d = System.identityHashCode(a) compare System.identityHashCode(b)
if (d != 0)
return d
// slow path..
}
使用return增加了可读性
上面是针对命令式语言的,在Scala中鼓励省略return
def suffix(i: Int) =
else if (i == 2) "nd"
else if (i == 3) "rd"
但使用模式匹配更好:
def suffix(i: Int) = i match {
case 1 => "st"
case 2 => "nd"
case 3 => "rd"
case _ => "th"
}
在字节码层实现为一个异常的捕获/声明(catching/throwing)对,用在频繁的执行的代码中,会有性能影响。
for对循环和聚集提供了简洁和自然的表达。 它在扁平化(flattening)很多序列时特别有用。for语法通过分配和派发闭包隐藏了底层的机制。这会导致意外的开销和语义;例如:
for (item <- container) {
if (item != 2) return
}
如果容器延迟计算(delays computation)会引起运行时错误,使返回不在本地上下文 (making the return nonlocal)
因为这些原因,常常更可取的是直接调用foreach, flatMap, map和filter —— 但在其意义清楚的时候使用for。
assert(stream != null)
相反,require用于表达API契约: