scalapuzzlers / scalapuzzlers.github.com

Github Pages behind scalapuzzlers.com
www.scalapuzzlers.com
161 stars 53 forks source link

Adding puzzler 'Exceptional Errors' #128

Closed iximeow closed 9 years ago

iximeow commented 9 years ago

Future does interesting things when failed with something other than an Exception! Tested this on 2.11.4 and 2.11.7, probably still happens on master - seems like intentional decisions in scala.concurrent.impl.Promise.

demobox commented 9 years ago

@a-wortman Thanks for submitting! I'm rushing around this week so it might take a few days before I can have a look at this, but I'll try to get round to this as soon as I can.

Thanks for your patience!

demobox commented 9 years ago

Hi Andy

What do you think about the following formulation, which stays pretty close to your version, but makes the Exception vs. Error choice a bit more obvious:

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}

val f = Future({ throw new Error("bang!") }) recoverWith { 
  case ex: Exception => Future.successful("Not so bad: " + ex.getMessage)
  case err: Error => Future.failed(err)
}
f onComplete {
  case Success(res) => println("Yay: " + res)
  case Failure(e) => println("Oops: " + e.getMessage)
}

with possible candidate answers:

Or the following one, which is more compact but obscures the exception vs. error distinction:

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}

Future({ throw new Error("bang!") }) onComplete { 
  case Success(res) => println("Yay: " + res)
  case Failure(e) => println("Oops: " + e.getMessage)
}

with candidate answers potentially being:

iximeow commented 9 years ago

I like your first suggestion quite a bit! I realized when I had to drop in Thread.sleep(100) that there was some way to make the sample a little more elegant, glad to see it's there.

I prefer the first version to the second because the crux of the puzzle is that the Error is magically boxed into an Exception. The second version has that, of course, but seems a stretch to reach back to Boxed Error. The first version is a more "honest" puzzle in a way, I think.

demobox commented 9 years ago

The second version has that, of course, but seems a stretch to reach back to Boxed Error.

...which may make it a bit more puzzling, depending on how you look at it ;-) But I agree that, in the second version, the only thing that's really puzzling is the message, not the execution path that the code takes.

Actually, looking at the first version again, the "throws an exception" candidate answer doesn't seem very plausible since we're explicitly catching Error. That would be more plausible as an option in the second case, actually: "Hm, I wonder if onComplete actually handles Error? Perhaps it just throws it?"

demobox commented 9 years ago

Bearing the above in mind, perhaps a hybrid would work best?

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}

val f = Future({ throw new Error("bang!") }) recoverWith { 
  case ex: Exception => Future.successful("Not so bad: " + ex.getMessage)
}
f onComplete {
  case Success(res) => println("Yay: " + res)
  case Failure(e) => println("Oops: " + e.getMessage)
}

with candidate answers:

iximeow commented 9 years ago

Leaving case err: Error => ... out does make "throws an exception" a more plausible answer, yeah. That last revision is also very similar to the initial submission, but with onComplete instead of a sleep and attempt to mach f.value, so of course I like it ;)

One of the parens or curlies on the fifth line isn't necessary though, just to be a stickler I prefer

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}

val f = Future { throw new Error("bang!") } recoverWith {
  case ex: Exception => Future.successful("Not so bad: " + ex.getMessage)
}
f onComplete {
  case Success(res) => println("Yay: " + res)
  case Failure(e) => println("Oops: " + e.getMessage)
}

(curlies for the Future rather than parens)

There's also the option to have a more generic recoverWith:

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}

val f = Future { throw new Error("bang!") } recoverWith {
  case t: Throwable => Future.successful("Not so bad: " + t.getMessage)
}
f onComplete {
  case Success(res) => println("Yay: " + res)
  case Failure(e) => println("Oops: " + e.getMessage)
}

with

as answers. This makes the first answer far more plausible, but does obscure the execution path point.

demobox commented 9 years ago

@a-wortman: I'll close this one for now - let's continue the discussion over at #129...?