Mi primera opción suele ser utilizar la recursividad. Es solo moderadamente menos compacto, es potencialmente más rápido (ciertamente no más lento) y en una terminación anticipada puede hacer que la lógica sea más clara. En este caso, necesita defs anidadas, lo cual es un poco incómodo:
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)
}
Mi segunda opción sería usar return
, ya que mantiene todo lo demás intacto y solo necesita envolver el pliegue en un def
para tener algo de lo que regresar; en este caso, ya tiene un método, así que:
def sumEvenNumbers(nums: Iterable[Int]): Option[Int] = {
Some(nums.foldLeft(0){ (n,x) =>
if ((n % 2) != 0) return None
n+x
})
}
que en este caso particular es mucho más compacto que la recursividad (aunque tuvimos especialmente mala suerte con la recursividad ya que tuvimos que hacer una transformación iterable / iterador). El flujo de control saltado es algo que se debe evitar cuando todo lo demás es igual, pero aquí no lo es. No está de más usarlo en los casos en que sea valioso.
Si estuviera haciendo esto a menudo y lo quisiera en medio de un método en algún lugar (por lo que no podría simplemente usar return), probablemente usaría el manejo de excepciones para generar un flujo de control no local. Después de todo, eso es en lo que es bueno, y el manejo de errores no es el único momento en que es útil. El único truco es evitar generar un seguimiento de pila (que es realmente lento), y eso es fácil porque el rasgo NoStackTrace
y su rasgo secundario ControlThrowable
ya lo hacen por usted. Scala ya usa esto internamente (de hecho, ¡así es como implementa el retorno desde dentro del pliegue!). Hagamos el nuestro (no se puede anidar, aunque se podría arreglar eso):
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
})
}
Aquí, por supuesto, usar return
es mejor, pero tenga en cuenta que puede poner en shortcut
cualquier lugar, no solo envolver un método completo.
El siguiente en la fila para mí sería volver a implementar el pliegue (ya sea yo mismo o encontrar una biblioteca que lo haga) para que pueda indicar la terminación anticipada. Las dos formas naturales de hacer esto son no propagar el valor sino Option
contener el valor, donde None
significa terminación; o para utilizar una función de segundo indicador que señala la finalización. El pliegue perezoso de Scalaz mostrado por Kim Stebel ya cubre el primer caso, así que mostraré el segundo (con una implementación 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)(_ + _)
(Si implementa la terminación por recursividad, retorno, pereza, etc., depende de usted).
Creo que cubre las principales variantes razonables; También hay algunas otras opciones, pero no estoy seguro de por qué se usarían en este caso. (en Iterator
sí mismo funcionaría bien si tuviera un findOrPrevious
, pero no lo tiene, y el trabajo adicional que se necesita para hacerlo a mano lo convierte en una opción tonta para usar aquí).