scalapass-by-name

What triggers Try argument to be evaluated


I was tinkering with Try, per this:

val files = Stream("a.txt", "b.txt", "c.txt")
files
    .map(s => Try(Source.fromFile(s)))
    .flatMap(t => t match {
      case Failure(x) =>
        Console.err.println(s"Failure: x.getMessage")
        Stream.Empty
      case Success(s) => Stream(s)
    })
  .flatMap(t => t.getLines.toStream)
  .foreach(println)

and while it works, exactly as I hoped/intended, I was left with an uneasy feeling that I could not identify exactly how the "Source.fromFile(s)" part actually gets evaluated. the argument to the Try.apply method is documented as a by-name parameter, so clearly something must force evaluation of that.

But, it seems to me that the very next operation would be the "t match" part, and that's looking at the type of object that the Try.apply created, and that can't work if the object hasn't been created, and it can't be created without evaluating the argument to apply. In which case, I don't see any point the argument being in the first place.

Or, perhaps there's something intrinsically lazy about the behavior of case classes? Or maybe I'm just missing something obvious.

Would someone mind clarifying this for me?


Solution

  • The part you are missing is that the argument to Try has to be by-name so that exceptions thrown by that computation can be caught and reified as Failures. Otherwise the argument would be evaluated before Try has had a chance to catch anything which would defeat the purpose. You can even look at the source code of Try.apply which is very simple. It immediately forces its argument:

    def apply[T](r: => T): Try[T] =
      try Success(r) catch {
        case NonFatal(e) => Failure(e)
      }