Моїм першим вибором, як правило, було б використовувати рекурсію. Він лише помірно менш компактний, потенційно швидший (звичайно, не повільніший) і на початку припинення може зробити логіку більш зрозумілою. У цьому випадку вам потрібні вкладені 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
де завгодно, а не просто обертати цілий метод.
Наступним на черзі для мене буде повторна реалізація fold (або я, або пошук бібліотеки, яка це робить), щоб це могло сигналізувати про дострокове припинення. Два природні способи зробити це не розповсюдження значення, а 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
, але цього немає, і додаткова робота, яка потрібна , щоб зробити це вручну, робить його безглуздим варіантом для використання тут.)