Мой первый выбор обычно - использовать рекурсию. Он лишь умеренно менее компактен, потенциально быстрее (конечно, не медленнее) и при раннем завершении может сделать логику более понятной. В этом случае вам нужны вложенные 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
, поскольку он сохраняет все остальное нетронутым, и вам нужно только обернуть складку, 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
где угодно, а не просто обернуть весь метод.
Следующим в очереди для меня было бы повторно реализовать свертку (либо я, либо найти библиотеку, которая это делает), чтобы это могло сигнализировать о раннем завершении. Два естественных способа сделать это - не распространять значение, а Option
содержать значение, где None
означает завершение; или использовать вторую функцию индикатора, сигнализирующую о завершении. Ленивая свертка 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
, но его нет, и дополнительная работа, необходимая для выполнения этого вручную, делает его глупым вариантом для использования здесь.)