Mon premier choix serait généralement d'utiliser la récursivité. Il n'est que modérément moins compact, est potentiellement plus rapide (certainement pas plus lent) et, en cas de résiliation anticipée, peut rendre la logique plus claire. Dans ce cas, vous avez besoin de defs imbriquées, ce qui est un peu gênant:
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
, car cela garde tout le reste intact et vous n'avez qu'à envelopper le pli dans un def
pour avoir quelque chose à retourner - 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
})
}
ce qui dans ce cas particulier est beaucoup plus compact que la récursivité (bien que nous ayons été particulièrement malchanceux avec la récursion car nous devions faire une transformation itérable / itérateur). Le flux de contrôle instable est quelque chose à éviter quand tout le reste est égal, mais ici ce n'est pas le cas. Aucun mal à l'utiliser dans les cas où il est précieux.
Si je faisais cela souvent et que je le voulais au milieu d'une méthode quelque part (donc je ne pouvais pas simplement utiliser return), j'utiliserais probablement la gestion des exceptions pour générer un flux de contrôle non local. C'est, après tout, ce pour quoi il est bon, et la gestion des erreurs n'est pas la seule fois où c'est utile. La seule astuce est d'éviter de générer une trace de pile (qui est vraiment lente), et c'est facile car le trait NoStackTrace
et son trait enfant le ControlThrowable
font déjà pour vous. Scala l'utilise déjà en interne (en fait, c'est ainsi qu'il implémente le retour depuis l'intérieur du pli!). Faisons le nôtre (ne peut pas être imbriqué, bien que l'on puisse résoudre cela):
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'utilisation return
est meilleure, mais notez que vous pouvez mettre shortcut
n'importe où, pas seulement envelopper une méthode entière.
La prochaine étape pour moi serait de réimplémenter le repli (soit moi-même, soit de trouver une bibliothèque qui le fasse) afin qu'il puisse signaler une résiliation anticipée. Les deux manières naturelles de le faire sont de ne pas propager la valeur mais de Option
contenir la valeur, où None
signifie la terminaison; ou pour utiliser une deuxième fonction d'indicateur qui signale la fin. Le pli paresseux Scalaz montré par Kim Stebel couvre déjà le premier cas, je vais donc montrer le second (avec une implémentation mutable):
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)(_ + _)
(Que vous implémentiez la résiliation par récursivité, retour, paresse, etc. dépend de vous.)
Je pense que cela couvre les principales variantes raisonnables; il existe également d'autres options, mais je ne sais pas pourquoi on les utiliserait dans ce cas. ( Iterator
lui-même fonctionnerait bien s'il avait un findOrPrevious
, mais ce n'est pas le cas, et le travail supplémentaire qu'il faut pour le faire à la main en fait une option ridicule à utiliser ici.)