Meine erste Wahl wäre normalerweise die Rekursion. Es ist nur mäßig weniger kompakt, möglicherweise schneller (sicherlich nicht langsamer) und kann bei vorzeitiger Beendigung die Logik klarer machen. In diesem Fall benötigen Sie verschachtelte Defs, was etwas umständlich ist:
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)
}
Meine zweite Wahl wäre zu verwenden return
, da es alles andere intakt hält und Sie nur die Falte in eine einwickeln müssen, def
damit Sie etwas zurückgeben können - in diesem Fall haben Sie bereits eine Methode, also:
def sumEvenNumbers(nums: Iterable[Int]): Option[Int] = {
Some(nums.foldLeft(0){ (n,x) =>
if ((n % 2) != 0) return None
n+x
})
}
Dies ist in diesem speziellen Fall viel kompakter als die Rekursion (obwohl wir mit der Rekursion besonders unglücklich waren, da wir eine iterable / iterator-Transformation durchführen mussten). Der nervöse Kontrollfluss ist etwas zu vermeiden, wenn alles andere gleich ist, aber hier ist es nicht. Kein Schaden bei der Verwendung in Fällen, in denen es wertvoll ist.
Wenn ich dies oft tun würde und es irgendwo in der Mitte einer Methode haben wollte (also konnte ich nicht einfach return verwenden), würde ich wahrscheinlich die Ausnahmebehandlung verwenden, um einen nicht lokalen Kontrollfluss zu generieren. Das ist schließlich das, was es kann, und die Fehlerbehandlung ist nicht das einzige Mal, dass es nützlich ist. Der einzige Trick besteht darin, zu vermeiden, dass eine Stapelverfolgung generiert wird (was sehr langsam ist), und das ist einfach, da die Eigenschaft NoStackTrace
und ihre untergeordnete Eigenschaft dies ControlThrowable
bereits für Sie tun. Scala verwendet dies bereits intern (tatsächlich implementiert es so die Rückgabe aus dem Inneren der Falte!). Lassen Sie uns unsere eigenen machen (kann nicht verschachtelt werden, obwohl man das beheben könnte):
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
})
}
Hier ist natürlich die Verwendung return
besser, aber beachten Sie, dass Sie shortcut
überall platzieren können, nicht nur eine ganze Methode einwickeln.
Als nächstes müsste ich fold erneut implementieren (entweder ich selbst oder eine Bibliothek finden, die dies tut), damit dies eine vorzeitige Beendigung signalisieren kann. Die zwei natürlichen Wege, dies zu tun, bestehen darin, den Wert nicht zu verbreiten, sondern Option
den Wert zu enthalten, wobei None
die Beendigung bedeutet; oder um eine zweite Anzeigefunktion zu verwenden, die den Abschluss signalisiert. Die von Kim Stebel gezeigte faule Falte von Scalaz deckt bereits den ersten Fall ab, daher zeige ich den zweiten (mit einer veränderlichen Implementierung):
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)(_ + _)
(Ob Sie die Kündigung durch Rekursion, Rückkehr, Faulheit usw. implementieren, liegt bei Ihnen.)
Ich denke, das deckt die wichtigsten vernünftigen Varianten ab; Es gibt auch einige andere Optionen, aber ich bin mir nicht sicher, warum man sie in diesem Fall verwenden würde. ( Iterator
selbst würde gut funktionieren, wenn es eine hätte findOrPrevious
, aber es funktioniert nicht, und die zusätzliche Arbeit, die erforderlich ist, um dies von Hand zu tun, macht es zu einer dummen Option, hier zu verwenden.)