Minha primeira escolha normalmente seria usar recursão. É apenas moderadamente menos compacto, é potencialmente mais rápido (certamente não é mais lento) e, no encerramento antecipado, pode tornar a lógica mais clara. Neste caso, você precisa de defs aninhados, o que é um pouco estranho:
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)
}
Minha segunda opção seria usar return
, pois mantém todo o resto intacto e você só precisa embrulhar a dobra def
para ter algo de onde retornar - neste caso, você já tem um método, então:
def sumEvenNumbers(nums: Iterable[Int]): Option[Int] = {
Some(nums.foldLeft(0){ (n,x) =>
if ((n % 2) != 0) return None
n+x
})
}
que, neste caso específico, é muito mais compacto do que a recursão (embora tenhamos tido azar especial com a recursão, uma vez que tivemos que fazer uma transformação iterável / iterador). O fluxo de controle instável é algo a evitar quando tudo o mais é igual, mas aqui não é. Não há mal nenhum em usá-lo nos casos em que é valioso.
Se eu estivesse fazendo isso com frequência e quisesse no meio de um método em algum lugar (portanto, não poderia apenas usar return), provavelmente usaria o tratamento de exceções para gerar um fluxo de controle não local. Afinal, é nisso que ele é bom, e o tratamento de erros não é o único momento em que é útil. O único truque é evitar a geração de um rastreamento de pilha (que é muito lento), e isso é fácil porque o traço NoStackTrace
e o traço filho ControlThrowable
já fazem isso por você. O Scala já usa isso internamente (na verdade, é assim que ele implementa o retorno de dentro do fold!). Vamos fazer o nosso (não pode ser aninhado, embora isso possa ser corrigido):
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
})
}
Aqui, return
é claro, usar é melhor, mas observe que você pode colocar em shortcut
qualquer lugar, não apenas envolver um método inteiro.
O próximo passo para mim seria reimplementar o fold (eu mesmo ou para encontrar uma biblioteca que o faça) para que possa sinalizar o encerramento antecipado. As duas maneiras naturais de fazer isso são não propagar o valor, mas Option
conter o valor, onde None
significa o encerramento; ou para usar uma segunda função de indicador que sinaliza a conclusão. A dobra preguiçosa do Scalaz mostrada por Kim Stebel já cobre o primeiro caso, então mostrarei o segundo (com uma implementação mutável):
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)(_ + _)
(Se você implementa a rescisão por recursão, retorno, preguiça, etc., depende de você.)
Acho que isso cobre as principais variantes razoáveis; há algumas outras opções também, mas não sei por que alguém as usaria neste caso. ( Iterator
funcionaria bem se tivesse um findOrPrevious
, mas não tem, e o trabalho extra necessário para fazer isso manualmente torna-o uma opção boba de usar aqui).