Throwing exceptions in Scala, what is the "official rule"
Asked Answered
I

2

52

I'm following the Scala course on Coursera. I've started to read the Scala book of Odersky as well.

What I often hear is that it's not a good idea to throw exceptions in functional languages, because it breaks the control flow and we usually return an Either with the Failure or Success. It seems also that Scala 2.10 will provide the Try which goes in that direction.

But in the book and the course, Martin Odersky doesn't seem to say (at least for now) that exceptions are bad, and he uses them a lot. I also noticed the methods assert / require...

Finally I'm a bit confused because I'd like to follow the best practices but they are not clear and the language seems to go in both directions...

Can someone explain me what i should use in which case?

Inspiration answered 14/10, 2012 at 20:33 Comment(3)
I think the more you biased to pure functional haskell/scalaz side, the more you will use Either/Try. Move from exceptions to Either is a continuum and it's hard to say where is the edge between them.Unsought
Good question, I was also surprised by him using exception throwing all over the place. But then it's true, the Nothing type is kind of elegant for polymorphism.Coloration
I asked a similar question: #13012649 (thanks to om-nom-nom for the link)Germain
P
51

The basic guideline is to use exceptions for something really exceptional**. For an "ordinary" failure, it's far better to use Option or Either. If you are interfacing with Java where exceptions are thrown when someone sneezes the wrong way, you can use Try to keep yourself safe.

Let's take some examples.

Suppose you have a method that fetches something from a map. What could go wrong? Well, something dramatic and dangerous like a segfault* stack overflow, or something expected like the element isn't found. You'd let the segfault stack overflow throw an exception, but if you merely don't find an element, why not return an Option[V] instead of the value or an exception (or null)?

Now suppose you're writing a program where the user is supposed to enter a filename. Now, if you're not just going to instantly bail on the program when something goes wrong, an Either is the way to go:

def main(args: Array[String]) {
  val f = {
    if (args.length < 1) Left("No filename given")
    else {
      val file = new File(args(0))
      if (!file.exists) Left("File does not exist: "+args(0))
      else Right(file)
    }
  }
  // ...
}

Now suppose you want to parse an string with space-delimited numbers.

val numbers = "1 2 3 fish 5 6"      // Uh-oh
// numbers.split(" ").map(_.toInt)  <- will throw exception!
val tried = numbers.split(" ").map(s => Try(s.toInt))  // Caught it!
val good = tried.collect{ case Success(n) => n }

So you have three ways (at least) to deal with different types of failure: Option for it worked / didn't, in cases where not working is expected behavior, not a shocking and alarming failure; Either for when things can work or not (or, really, any case where you have two mutually exclusive options) and you want to save some information about what went wrong; and Try when you don't want the whole headache of exception handling yourself, but still need to interface with code that is exception-happy.

Incidentally, exceptions make for good examples--so you'll find them more often in a textbook or learning material than elsewhere, I think: textbook examples are very often incomplete, which means that serious problems that normally would be prevented by careful design ought instead be flagged by throwing an exception.

*Edit: Segfaults crash the JVM and should never happen regardless of the bytecode; even an exception won't help you then. I meant stack overflow.

**Edit: Exceptions (without a stack trace) are also used for control flow in Scala--they're actually quite an efficient mechanism, and they enable things like library-defined break statements and a return that returns from your method even though the control has actually passed into one or more closures. Mostly, you shouldn't worry about this yourself, except to realize that catching all Throwables is not such a super idea since you might catch one of these control flow exceptions by mistake.

Personage answered 14/10, 2012 at 21:0 Comment(5)
I'd add that much of the Odersky book is really devoted to the Scala language itself and transitioning from Java thus missing some good FP practices.Polyphemus
Sorry to ask this one year later but can you explain what you mean by your second edit? I didn't catch what you meanInspiration
@SebastienLorber - If you have something like def f(xs: Seq[Int]): Int = { xs.foreach(i => if (i>10) return i); 0 }, that return is not a real return because when you're in the foreach you're actually inside a completely different method! To get back out, Scala actually throws an exception with the return value instead of a stack trace, and catches it with a try block placed around the contents of f.Personage
ok @RexKerr thanks I guess it's ControlThrowable, also present in NonFatalInspiration
@SebastienLorber - For return, it's actually scala.runtime.NonLocalReturnControl, a subclass of ControlThrowable.Personage
C
12

So this is one of those places where Scala specifically trades off functional purity for ease-of-transition-from/interoperability-with legacy languages and environments, specifically Java. Functional purity is broken by exceptions, as they break referential integrity and make it impossible to reason equationally. (Of course, non-terminating recursions do the same, but few languages are willing to enforce the restrictions that would make those impossible.) To keep functional purity, you use Option/Maybe/Either/Try/Validation, all of which encode success or failure as a referentially-transparent type, and use the various higher-order functions they provide or the underlying languages special monad syntax to make things clearer. Or, in Scala, you can simply decide to ditch functional purity, knowing that it might make things easier in the short term but more difficult in the long. This is similar to using "null" in Scala, or mutable collections, or local "var"s. Mildly shameful, and don't do to much of it, but everyone's under deadline.

Croom answered 14/10, 2012 at 22:36 Comment(5)
How do exceptions break referential integrity? With Nothing as bottom-type I don't think anything gets "broken". If your code is purely functional, exceptions shouldn't pose a problem, no? They only become tricky when side effects are involved.Coloration
Nope, even without side-effects, laziness makes exceptions non-referentially transparent.Croom
Could you provide a link about referential integrity in scala, I don't quite get what you mean with breaking functional purity, thanks...Germain
My understanding of "How do exceptions break referential integrity" is this: I think what Dave means is that "exceptions break referential transparency". Loosely speaking, functions are referentially transparent if, for any given input, the same output will always be produced. E.g. someFunction(x) = someOtherFunction(y) if x = y. But exceptions aren't return values - they break the paradigm of "input gives output" by adding "unless something goes wrong". So, if you can encode exceptions as just another form of return type, you could still have referential transparency between functions.Hinds
In Java you have to declare that a method throws an exception. This makes the exception part of the public interface. Also, if a method is declared to throw an exception, you have to call it in a try block. Scala is different in this regard. You don't have to declare that an exception is thrown. Because in Java you have to both declare and handle exceptions, they kind of become part of the return type. So to my mind this "referential transparency" problem really is a problem only in Scala.Inflight

© 2022 - 2024 — McMap. All rights reserved.