Scala - display sequence, stopping immediately when element cannot be processed

I need a function that maps a function f over a sequence of xs, and if f (x) (where x is an element from xs), it turns Failure

out and then does not process any additional elements of xs , but return immediately Failure

. If f (x) succeeds for all x, return a Success

containing the sequence of results.

So the type signature could be something like

def traverse[A, B](xs: Seq[A])(f: A => Try[B]): Try[Seq[B]]

      

And some test cases:

def doWork(i: Int): Try[Int] = {
  i match {
    case 1 => Success(10)
    case 2 => Failure(new IllegalArgumentException("computer says no"))
    case 3 => Success(30)
  }
}

traverse(Seq(1,2,3))(doWork)
res0: scala.util.Try[Seq[Int]] = Failure(java.lang.IllegalArgumentException: computer says no)

traverse(Seq(1,3))(doWork)
scala.util.Try[Seq[Int]] = Success(List(10, 30))

      

What would be the most elegant way to do this?

+3


source to share


2 answers


Simple implementation:

def traverse[A, B](xs: Seq[A])(f: A => Try[B]): Try[Seq[B]] =
  xs.foldLeft[Try[Seq[B]]](Success(Vector())) { (attempt, elem) => for {
    seq <- attempt
    next <- f(elem)
  } yield seq :+ next
  }

      

The problem is that until the function evaluates f

after it appears Failure

, the function will traverse the sequence to the end, which may not be desirable in case of complex Stream

, so we can use some specialized version:

def traverse1[A, B](xs: Seq[A])(f: A => Try[B]): Try[Seq[B]] = {
  val ys = xs map f
  ys find (_.isFailure) match {
    case None => Success(ys map (_.get))
    case Some(Failure(ex)) => Failure(ex)
  }
}

      



which uses staging collection, which leads to unnecessary memory overhead in case of strict collection

or we could override fold

from scratch:

def traverse[A, B](xs: Seq[A])(f: A => Try[B]): Try[Seq[B]] = {
  def loop(xs: Seq[A], acc: Seq[B]): Try[Seq[B]] = xs match {
    case Seq() => Success(acc)
    case elem +: tail =>
      f(elem) match {
        case Failure(ex) => Failure(ex)
        case Success(next) => loop(tail, acc :+ next)
      }
  }
  loop(xs, Vector())
}

      

As we could see, the inner one loop

will keep on iterating, while it only deals withSuccess

+1


source


One way, but is it the most elegant?



def traverse[A, B](xs: Seq[A])(f: A => Try[B]): Try[Seq[B]] = {
  Try(xs.map(f(_).get))
}

      

0


source







All Articles