我的第一选择通常是使用递归。它只是稍微不那么紧凑,可能更快(当然不会更慢),并且在提前终止时可以使逻辑更清晰。在这种情况下,您需要嵌套的 def,这有点尴尬:
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),我可能会使用异常处理来生成非本地控制流。也就是说,它毕竟擅长什么,而且错误处理并不是它唯一有用的时候。唯一的技巧是避免生成堆栈跟踪(这真的很慢),这很容易,因为 traitNoStackTrace
和它的子 traitControlThrowable
已经为你做到了。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
任何地方,而不仅仅是包装整个方法。
接下来对我来说是重新实现折叠(我自己或找到一个这样做的库),以便它可以发出提前终止的信号。这样做的两种自然方式是不传播值,而是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
,但它没有,而且手动完成它所需要的额外工作使它成为在这里使用的一个愚蠢的选择。)