I am curious why exception handling is a topic often ignored in Scala. Is it the goal of the language (or the style) to not rely on exception handling except for external input/code?
I was hoping the style guide had some discussion on exception checking/handling. Is there a good reference that I could consult to understand if there are any differences in handling exceptions in Scala vs. languages I’m more used to (C++/C#/Java/Ruby/etc)?
Would someone with some Scala years under their belt be willing to write down guidelines for exception handling that are Scala specific? or is the answer simply “do as you would in Java”? What is “the functional way”?
3
Good functional style does not need defensive coding nearly as much as the imperative style. try and catch is not the only control structure which is neglected in Scala (and other functional languages); for and while loops are endangered species and even if…else is used more selectively (although much more effectively, since it returns a value).
In the functional world, you match your code to the shape of the data. Or you choose a shape which gives your code the structure you want. This is true not only with Option and Either but List and Set and Map and more. All of them are paired with powerful Higher Order Functions – map, fold, filter etc.
To take a trivial example, if a Java or C++ coder wants to fetch the first (or all) of the items in a list and manipulate it, they either have to first check that the list is not empty or wrap it in try/catch to handle the error. If they have to deal with a collection of lists, the latter option is more likely to be chosen (and even less likely to match the context which might generate an error).
In Scala you can simply do this to the list of Ints called xs:
xs.headOption map (_ * 2)
// Returns twice the first item - if there is one - as an Option
or
xs map (_ * 2)
// Doubles everything in the list. Returns a list.
If there is nothing in the list, nothing will happen. If you want something to happen, you can detect the failure and react.
xs.headOption map (_ * 2) orElse Some(0)
// Returns Some(twice the head item) if there is one or Some(0)
xs.headOption map (_ * 2) getOrElse 0
// Returns twice the head item or 0 if there is none
Even better, if you are dealing with a lists of lists, any one of which might be empty, mapping over each of them will produced results for every populated list and no problem at all with any empty ones. That is very powerful when dealing with large collections of unpredictable data.
What these functional types (Option and Either and List and Map and all the other – sorry to use the ugly word – monads) also offer, very importantly, is separation of concerns. Notice how I used map in all the above examples. In two examples it is Option.map, while in the other it is List.map. In all cases, map is doing the same thing: ¨Apply my function to the data inside that container, preserving the context¨. If it is a list context, you get a list of transformed data back. If it is a ¨may or may not exist¨ context, you may get your transformed object. If it is a ¨this could really go badly wrong¨ context, you either get your object or a chance to complain. And so on.
So you get a separation of concerns between the action you want to perform and the context in which it is applied. The big advantage is that if you decide to change the context (e.g. Set of unique objects rather than arbitrary List of objects), the rest of the code doesn´t need to change at all. map will still do the right thing (as will filter, fold and the rest).
Built-in imperative keywords like try..catch or if…else…if…else do not have that power. For a start, they have no real meaning of their own and have to be bodged together (distorting the code tangled within them) and they offer no guarantee. Imagine having used those to manage an arbitrary list of objects and then deciding you want to guarantee uniqueness. How much of that imperative code will you have to change? How can you be confident it will work?
Leave exceptions to deal with the truly unexpected and unsolveable. Functional types can be confident about their guarantees, to help you avoid the common and predictable errors.
6
Scala encourages the use of constructs like options and futures for error handling. First of all, lazy evaluation makes exceptions problematic, because there is a different stack when a function is executed than when it’s queued to execute. Scala isn’t lazy by default, but it can be lazy when you choose.
Also, options and futures are much more powerful than exceptions. You can chain operations on them, and they aren’t limited to propagating straight up the stack like exceptions are. You can store them in collections and reduce duplication in handler code.
Pretty much the only time you use exceptions in Scala is for errors you can’t handle that will kill your program anyway.
11