1

I want to apply a function f to each element of a List and not stop at the first error but throw the last error (if any) only:

@annotation.tailrec
def tryAll[A](xs: List[A])(f: A => Unit): Unit = {
  xs match {
    case x :: xt =>
      try {
        f(x)
      } finally {
        tryAll(xt)(f)
      }
    case _ =>
  }
}

But, the above code does not compile - it is complaining that this function is not tail recursive. Why not?

pathikrit
  • 32,469
  • 37
  • 142
  • 221

4 Answers4

1

This solution iterates over all elements and produces (throws) the last error if any:

def tryAll[A](xs: List[A])(f: A => Unit): Unit = {
  val res = xs.foldLeft(Option.empty[Throwable]) {
    case (maybeThrowable, a) =>
      Try(f(a)) match {
        case Success(_) => maybeThrowable
        case Failure(e) => Option(e)
      }
  }

  res.foreach(throwable => throw throwable)
}
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
0

As mentioned by @HristoIliev, your method cannot be tail recursive because the finally call is not guaranteed to be the tail call. This means that any method using try in this way will not be tail recursive. See this answer, also.

Calling the method again is a weird way of trying something repeatedly until it succeeds, because at each stage it's throwing an exception you are presumably not handling. Instead, I'd argue using a functional approach with Try, taking failures from a view until the operation succeeds. The only disadvantage to this approach is that it doesn't throw any exceptions for you to handle along the way (which can also be an advantage!).

def tryAll[A](xs: List[A])(f: A => Unit): Unit =
    xs.view.map(x => Try(f(x))).takeWhile(_.isFailure).force


scala> val list = List(0, 0, 0, 4, 5, 0)

scala> tryAll(list)(a => println(10 / a))
2

If you really want to handle the exceptions (or just the last exception), you can change the return type of tryAll to List[Try[Unit]] (or simply Try[Unit] if you modify the code to only take the last one). It's better for the return type of the method to describe part of what it's actually doing--potentially returning errors.

Community
  • 1
  • 1
Michael Zajac
  • 55,144
  • 7
  • 113
  • 138
-1

Not sure the intention of the method, but you can something like that:

  final def tryAll[A](xs: List[A])(f: A => Unit): Unit = {
      xs match {
        case x :: xt =>
          try {
            f(x)
          } catch {
            case e => tryAll(xt)(f)
          }
        case _ => //do something else
      }
    }
Shawn Xiong
  • 470
  • 3
  • 14
-1

I know this way to use @annotation.tailrec

From this:

def fac(n:Int):Int = if (n<=1) 1 else n*fac(n-1)

You should have this:

@scala.annotation.tailrec 
def facIter(f:Int, n:Int):Int = if (n<2) f else facIter(n*f, n-1) 
def fac(n:Int) = facIter(1,n)
mychemicalro
  • 232
  • 3
  • 21
  • You have to accumulate values in f, so when you reach the base case, return it. It does not make sense for your method :/ – mychemicalro Feb 14 '17 at 18:44