9

What is the idiomatic way of applying a function A => Try[B] on a List[A] and return either the first succesful result Some[B] (it short-circuits) or if everything fails, returns None

I want to do something like this:

val inputs: List[String] = _

def foo[A, B](input: A): Try[B] = _

def main = {
  for {
   input <- inputs 
  } foo(input) match {
    case Failure(_) => // continue
    case Success(x) => return Some(x) //return the first success
  }
  return None   // everything failed
}
pathikrit
  • 32,469
  • 37
  • 142
  • 221

3 Answers3

14

You can do the same thing using collectFirst in one less step:

inputs.iterator.map(foo).collectFirst { case Success(x) => x }
Michael Zajac
  • 55,144
  • 7
  • 113
  • 138
8

You want this:

inputs
  .iterator // or view (anything lazy works)
  .map(foo)
  .find(_.isSuccess)
  .map(_.get)

It returns an Option[B].

Nate
  • 2,205
  • 17
  • 21
  • No, I want it to "short circuit" i.e. not evaluate after first success. `foo` in my case is expensive and slow and my list of inputs is large. – pathikrit Nov 06 '14 at 21:43
  • That's what the `.iterator` is for. It's also why I said anything lazy works. – Nate Nov 06 '14 at 21:44
0

Here's the same thing but it will return the last Failure in the list if none are successful. Theoretically tries.find(_.isSuccess).getOrElse(tries.last) would work, but on a list view (lazy list) that causes all the attempts to be evaluated twice. Instead, this works for lazy lists (throws on empty collection):

val tries = inputs.view.map(foo)

val firstSuccessOrLastFailure = tries.zipWithIndex.collectFirst({
  case (s @ Success(_), _) => s
  case (f, i) if (i == tries.size - 1) => f
}).get
Ian
  • 5,704
  • 6
  • 40
  • 72