Functional Error Accumulation in Scala

This post originally appeared on longcao.org in July 2015 and is republished below.

In a previous post, we dipped into what it means to handle application errors functionally in Scala and took a brief overview of the types that can be used to accomplish that. A common theme with those error handling types like Try  or the disjunctions is that they all fail fast: only the first error in a purely functional call chain is returned.

Let’s keep the functional hype train going and explore more of the goodies that we can bring into our application logic. Stepping back from the fail-fast error handling discussion, let’s consider something else: what if we wanted to execute all functions applied to an input and report back any and all possible errors? In other words:

  • I have a bag of functions that I want to apply to inputs that I feed them
  • While I know that some of them can and will fail, none of them are showstoppers
  • I also want a nice little bow-tied package in the end that gives me everything I could want to know: the possible errors or the result type.

Another Coffee-based Example

coffee

As I’m drinking a coffee in a coffee shop while I write this, let’s reuse my favorite example domain, coffee, to sketch out what we might want to do.

Let’s say that I’m a coffee shop owner and I want my shop to be known for having the highest quality coffee in the city, an accolade only possible by sourcing the best of roasted beans to use. Since I have an elite sense of discernment toward coffee quality, I want to apply my expertise towards evaluating the roasts I want to buy for my shop. Since I want this process to be repeatable, I will attempt to look at each characteristic of a roast objectively and independently – I want to validate that the beans I’m looking at pass my standards.

Therein lies the basis for an example: the characteristics I’m looking to check against are the individual pieces of logic that I will use to evaluate a roast, and a select roast can fail none, some, or all of the logic. For the sake of this blog post [1], here’s a vastly simplified list of coffee-evaluating functions:

  • evaluateDarkness  – fails if the roast is too extreme (too dark or too light)
  • evaluateFreshness  – fails if the roast isn’t fresh enough
  • evaluateEvenness  – fails if the roast is uneven (different shades of color indicate uneven roast)

Without giving any thought yet, let’s start by implementing these functions in only the Scala standard library (all code samples are intended to be fully compilable upon copy/paste and with any jar dependencies in scope):

Our evaluation function evaluateRoast  returns a pretty clear container type: either I should get back a list of problems with the roast, or the checks pass and I get back an instance of the approved roast. By flatten ing a List[Option[_]] , I get back only a List  of the Options  that were Some .

Taking It Further With Error Accumulation Types

For some, the previous example may already do most of the job: we’re using the Scala standard library to roll up errors as we execute functions and we’re returning a container type that can contain either a collection of errors or the result type we want. However, if we want to arbitrarily tack on more error accumulating functions with less of the manual labor of doing so, there are more attractive alternatives.

cats.data.Validated

In the following example using cats.data.Validated, we are doing the same validation logic except we return ValidatedNel[RoastProblem, A]  instances, which is a type alias for Validated[NonEmptyList[RoastProblem], A]  – it collects errors in a list that cannot be empty, thus if you have an Invalid  you know you have at least one error.

This example is conceptually quite a bit more complex, there’s a few pieces to unpack to mostly understand what’s going on in the bigger picture. In this example, each function returns a Validated instance which can essentially be smushed together by what’s called the home alone operator – |@| . Then, we’re able to map  the “good” values coming out of those individual roast property validators to the ultimate return type we want – an ApprovedRoast  ready to be brewed. The end result will be either a list of problems with the roast, or an ApprovedRoast  that passed our stringent coffee requirements.

The example code is made possible through a combination of things, and at risk of handwaving a bit too much I’ll mention them anyways:

  • Validated  is an applicative functor [2], which has properties that allow you to independently run these validations yet still combine the returned results for either accumulated errors or a nice return type.
  • The implicit resolution from importing cats.std.list._  and cats.syntax.apply._ , which gives you the |@| syntax for applicative building.
  • The implicit semigroup val defines the ability to add together NonEmptyLists  so we can roll up errors.

A Similiar option: scalaz.Validation

As with Validated , we can write our code using Validation  in the same manner with only minor adjustments to function names. Everything said about Validated  applies here since they are nearly the same.

The Return of org.scalactic.Or

Returning from the last blog post is org.scalactic.Or : it turns out that the written language-friendly container type from Scalactic can also handle error accumulation.

The accumulation is enabled through the notion of using its Every  non-empty collection, much like NonEmptyList  is to cats and scalaz. Instead of having to go through the exercise of applicative building, Scalactic provides an Accumulation.withGood  function that returns you either the Good  result or the Bad  result with every error rolled up within. A side benefit of using this type is being able to switch between fail-fast and accumulation with less overall refactoring if you wish – the right side of Or  is purposefully built to handle both.

Wrapping Up

In the end, we can again see the benefits (subject to your own opinion, of course) of using more advanced types despite the more complex machinery underneath: the return types more accurately reflect the computations that are going on and enforce a tighter contract. The mentioned error accumulating types present an advantage in being able to mix and match validation functions and continue chaining more validations. Since I return a type containing either a collection of errors or a result, I can take that same type and run them through some more functions that continue validation. Tying it back to the example, I can take my approved roast and apply more validation functions to further scrutinize, grade, or otherwise classify roasts.

Hopefully I haven’t tired out the coffee analogy too much for you. Until next time!

Further Reading

As always, a few other people have written about the same topic. Diversify your bonds and check them out as well:

[1] The SCAA defines a coffee cupping protocol that’s fascinatingly thorough and has way more nuance than can be described in a programming blog post.

[2] This StackOverflow answer does a much better job contextualizing the ‘why’ of an applicative functor using scalaz.Validation .

Shoutout to @tixxit and @meestaveesa for help on writing this blog post.

A Picture of Long Cao

LONG CAO

Long Cao is a software engineer at MediaMath in New York City. A Houston native, he is interested in Scala, Clojure, Play, functional programming, natural language processing, and machine learning. You can find his thoughts at longcao.org, his photography at sotohp.tumblr.com, and his person at the local coffeeshop.
0 Comments.

Leave a Reply

Your email address will not be published. Required fields are marked *