How should Scala Platform libraries handle failure conditions?


#1

In the November 2017 SPP meeting, one of the questions raised was about how Platform libraries should handle failures of various sorts. It was asked originally about better-files, which generally throws exceptions for IO failure, and though good reasons were given why better-files in particular should keep its behaviour (because it is meant to simply wrap Java’s API), the question remains for other Platform libraries. Should they throw exceptions? Should they return Options, Eithers, or some other functional construct? Some combination of the different approaches?

I think it is worth noting that both approaches have legitimate uses. If you expect whatever operation you are performing (e.g. reading a file) to always succeed, throwing exceptions is both simpler and cleaner to code, and easier to debug. Having to deal with an Option or Either to handle results - or potentially several (if you are performing many IO operations) - can be extra work, as well as unnecessary noise in the code. On the other hand, if you expect the operation you are performing to fail in normal circumstances, throwing exceptions for every failure is undesirable (even wrapped in a Try, because filling in stack traces is expensive). For expected failures, having a functional construct such as Option or Either can make it significantly easier to handle many results: whether by filtering out failures, mapping one result type, or splitting attempts into different collections for successes or failures.

What approach should Scala Platform libraries follow?


#2

Just for completeness, because it’s probably not feasible to do this in every library: parboiled2 has this DeliveryScheme typeclass which allows users of the library to choose whether they want exceptions, Try or Either, or possibly something else.


#3

As an user, the reality is that the only answer I can give is “it depends”. I personnaly much prefer to always get a Either[SomeErrorType_PerhapsException, T] than an exception, or a Try[T] (and extremelly rarely an Option[T] for each the semantic is different). But my mileage may vary, and I also know that other people have other preferences.

So, the best answer I can give is: let me choose at call site.

I know it’s possible, because @propensive’s Rapture used to have a configurable error handling return type, see: https://github.com/propensive/rapture/blob/dev/doc/core.md
And even if you are not as dedicated to that goal as @propensive was in Rapture (and in fact, I don’t know what are the tradeoff in term of performance, added complexity, and so on), it may be possible to at least add an .attempt implicit transformation from exception to Either[Throwable, T] as some libraries do.
For example, Doobie allow that and even has an excellent documentation on it: http://tpolecat.github.io/doobie/docs/09-Error-Handling.html (thanks @tpolecat !)

Hope it helps!


#4

Oh, what a neat idea. Need to stick this one in my back pocket…


#5

The doc @fanf points to is a bit out of date; this is now swept up into the cats abstraction ApplicativeError and its stronger cousin MonadError. These characterize effects that may compute error values, as the doobie effects can.

But as long as I’m here … In functional Scala we find it helpful to distinguish deterministic “pure” failures (as raised by "foo".toInt for example) from nondeterministic ones (as might be raised by file.write(...) for any number of reasons). The former type can be encoded reasonably as Option, Either, Validated, etc.; but the latter type requires some understanding of side-effects, as you get with IO for instance. There is no such type in stdlib Scala so the options here are limited. Simply throwing and requiring the user to catch or wrap with Try may be the most reasonable option in these cases.