An overly-simplified solution could be:
import scala.util.{Try, Success, Failure}
def parseLines(lines: List[String]): List[Try[Int]] =
lines map { l => Try (l.toInt) }
val lines = List("12", "13", "13a", "14", "foo")
println("LINES: " + lines)
val parsedLines = parseLines(lines)
println("PARSED: " + parsedLines)
val anyFailed: Boolean = parsedLines.exists(_.isFailure)
println("FAILURES EXIST?: " + anyFailed)
val failures: List[Throwable] = parsedLines.filter(_.isFailure).map{ case Failure(e) => e }
println("FAILURES: " + failures)
val parsedWithIndex = parsedLines.zipWithIndex
println("PARSED LINES WITH INDEX: " + parsedWithIndex)
val failuresWithIndex = parsedWithIndex.filter{ case (v, i) => v.isFailure }
println("FAILURES WITH INDEX: " + failuresWithIndex)
Prints:
LINES: List(12, 13, 13a, 14, foo)
PARSED: List(Success(12), Success(13), Failure(java.lang.NumberFormatException: For input string: "13a"), Success(14), Failure(java.lang.NumberFormatException: For input string: "foo"))
FAILURES EXIST?: true
FAILURES: List(java.lang.NumberFormatException: For input string: "13a", java.lang.NumberFormatException: For input string: "foo")
PARSED LINES WITH INDEX: List((Success(12),0), (Success(13),1), (Failure(java.lang.NumberFormatException: For input string: "13a"),2), (Success(14),3), (Failure(java.lang.NumberFormatException: For input string: "foo"),4))
FAILURES WITH INDEX: List((Failure(java.lang.NumberFormatException: For input string: "13a"),2), (Failure(java.lang.NumberFormatException: For input string: "foo"),4))
Given that you could wrap all this in a helper class, abstract parsing function, generalize input and output types and even define error type whether it's an exception or something else.
What I'm suggesting is a simple map based approach, the exact types could be defined based on a task.
The annoying thing is that you have to keep a reference to parsedWithIndex
in order to be able to get indexes and exceptions unless your exceptions will hold indexes and other context information.
Example of an implementation:
case class Transformer[From, To](input: List[From], f: From => To) {
import scala.util.{Try, Success, Failure}
lazy val transformedWithIndex: List[(Try[To], Int)] =
input map { l => Try ( f(l) ) } zipWithIndex
def failuresWithIndex =
transformedWithIndex.filter { case (v, i) => v.isFailure }
lazy val failuresExist: Boolean =
! failuresWithIndex.isEmpty
def successfulOnly: List[To] =
for {
(e, _) <- transformedWithIndex
value <- e.toOption
} yield value
}
val lines = List("12", "13", "13a", "14", "foo")
val res = Transformer(lines, (l: String) => l.toInt)
println("FAILURES EXIST?: " + res.failuresExist)
println("PARSED LINES WITH INDEX: " + res.transformedWithIndex)
println("SUCCESSFUL ONLY: " + res.successfulOnly)
Prints:
FAILURES EXIST?: true
PARSED LINES WITH INDEX: List((Success(12),0), (Success(13),1), (Failure(java.lang.NumberFormatException: For input string: "13a"),2), (Success(14),3), (Failure(java.lang.NumberFormatException: For input string: "foo"),4))
SUCCESSFUL ONLY: List(12, 13, 14)
Try
can be replaced with Either
or your own custom Failure
.
This does feel a bit more object oriented rather than functional.