我的首选通常是使用递归。它的压缩程度仅适度地降低,可能更快(肯定不会更慢),并且在提前终止时可以使逻辑更清晰。在这种情况下,您需要嵌套的defs有点尴尬:
def sumEvenNumbers(nums: Iterable[Int]) = {
def sumEven(it: Iterator[Int], n: Int): Option[Int] = {
if (it.hasNext) {
val x = it.next
if ((x % 2) == 0) sumEven(it, n+x) else None
}
else Some(n)
}
sumEven(nums.iterator, 0)
}
我的第二个选择是使用return
,因为它可以使其他所有内容保持不变,并且您只需要将折叠包裹在a中,def
这样您就可以返回一些东西-在这种情况下,您已经有了一个方法,因此:
def sumEvenNumbers(nums: Iterable[Int]): Option[Int] = {
Some(nums.foldLeft(0){ (n,x) =>
if ((n % 2) != 0) return None
n+x
})
}
在这种特殊情况下,它比递归要紧凑得多(尽管由于必须进行可迭代/迭代器的转换,所以对递归的使用特别不幸)。当所有其他条件都相等时,应该避免跳跃式控制流程,但事实并非如此。在有价值的情况下使用它无害。
如果我经常这样做,并且希望在某个方法中间的某个地方使用它(因此我不能只使用return),那么我可能会使用异常处理来生成非本地控制流。也就是说,毕竟它擅长于什么,而错误处理并不是唯一有用的时间。唯一的技巧是避免生成堆栈跟踪(这确实很慢),这很容易,因为特征NoStackTrace
及其子特征ControlThrowable
已经为您完成了。Scala已经在内部使用了它(实际上,这就是它从折叠内部实现返回的方式!)。让我们自己做一个(不能嵌套,尽管可以解决):
import scala.util.control.ControlThrowable
case class Returned[A](value: A) extends ControlThrowable {}
def shortcut[A](a: => A) = try { a } catch { case Returned(v) => v }
def sumEvenNumbers(nums: Iterable[Int]) = shortcut{
Option(nums.foldLeft(0){ (n,x) =>
if ((x % 2) != 0) throw Returned(None)
n+x
})
}
在这里使用固然return
更好,但是请注意,您可以放置shortcut
在任何地方,而不仅仅是包装整个方法。
对我而言,下一步是重新实现fold(我自己或寻找一个实现该功能的库),以便它可以表示提前终止。这样做的两种自然方法是不传播值,而是传播Option
包含值的,None
表示终止。或使用第二个指示器功能来指示已完成。Kim Stebel展示的Scalaz惰性折叠已经涵盖了第一种情况,因此我将展示第二种情况(具有可变的实现):
def foldOrFail[A,B](it: Iterable[A])(zero: B)(fail: A => Boolean)(f: (B,A) => B): Option[B] = {
val ii = it.iterator
var b = zero
while (ii.hasNext) {
val x = ii.next
if (fail(x)) return None
b = f(b,x)
}
Some(b)
}
def sumEvenNumbers(nums: Iterable[Int]) = foldOrFail(nums)(0)(_ % 2 != 0)(_ + _)
(是否通过递归,返回,懒惰等实现终止取决于您。)
我认为这涵盖了主要的合理变体;还有其他一些选择,但是我不确定为什么在这种情况下会使用它们。(Iterator
如果有findOrPrevious
,它本身会很好地工作,但如果没有的话,那么手工完成这项工作会花费很多额外的工作,因此在这里使用是一种愚蠢的选择。)