Mon premier choix serait, en général, l'utilisation de la récursivité. Il n'est que modérément moins compacte, est potentiellement plus rapide (certainement pas plus lent), et au début de résiliation peut rendre la logique de plus en plus clair. Dans ce cas, vous devez imbriquée defs qui est un peu maladroit:
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)
}
Mon deuxième choix serait d'utiliser return
, comme cela tout le reste est intact et vous avez seulement besoin d'envelopper le plier dans un def
si vous avez quelque chose à revenir--dans ce cas, vous avez déjà une méthode, donc:
def sumEvenNumbers(nums: Iterable[Int]): Option[Int] = {
Some(nums.foldLeft(0){ (n,x) =>
if ((n % 2) != 0) return None
n+x
})
}
qui, dans ce cas particulier, est beaucoup plus compact que la récursivité (bien que nous avons particulièrement malchanceux avec la récursivité, puisque nous avons dû faire un objet iterable/itérateur de transformation). Les problèmes de flux de contrôle est quelque chose à éviter quand tout le reste est égal, mais ici, il ne l'est pas. Pas de mal à l'utiliser dans les cas où il est précieux.
Si je le faisais souvent, et ce au moyen d'une méthode quelque part (donc je ne pouvais pas l'utiliser juste retour), je serais probablement utiliser de gestion des exceptions pour générer de la non-locales de contrôle de flux. C'est, après tout, ce qu'il est bon, et la gestion des erreurs n'est pas la seule fois où c'est utile. Le seul truc, c'est pour éviter de générer une trace de la pile (qui est vraiment très lent), et c'est facile parce que le trait NoStackTrace
et de ses traits de caractère des enfants ControlThrowable
déjà le faire pour vous. Scala utilise déjà ce à l'interne (en fait, c'est la façon dont il met en œuvre la déclaration de l'intérieur de la fois!). Nous allons faire notre propre (ne peut pas être imbriquées à l', bien qu'on pourrait le corriger):
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
})
}
Ici, bien sûr, à l'aide de return
c'est mieux, mais notez que vous pouvez mettre shortcut
n'importe où, non seulement l'enchaînement d'un ensemble de la méthode.
Le prochain dans la ligne pour moi serait de re-mettre en œuvre des fois (moi-même ou de trouver une bibliothèque qui le fait), de sorte que cela pourrait être le signe de résiliation anticipée. Les deux façons de faire cela est de ne pas propager la valeur, mais un Option
contenant de la valeur, où l' None
signifie la résiliation; ou l'utilisation d'un deuxième indicateur de la fonction de signaux d'achèvement. Le Scalaz paresseux fois illustré par Kim Stebel couvre déjà le premier cas, donc je vais vous montrer le deuxième (avec une mutable mise en œuvre):
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)(_ + _)
(Si vous en œuvre de la résiliation par récurrence, en retour, la paresse, etc. c'est à vous.)
Je pense que le principal raisonnable variantes; il y a quelques autres options aussi, mais je ne sais pas pourquoi on pourrait les utiliser dans ce cas. (Iterator
lui-même serait bien travailler, si elle avait un findOrPrevious
, mais il ne le fait pas, et le travail supplémentaire qu'il faut pour le faire à la main en fait un bête option pour utiliser ici.)