Scala is a great candidate to build micro-services and has great compiler checks, it is built with scalability in mind, and it runs in the Java Virtual Machine (JVM).
About the Author:
Joaquín Terrasa is a passionate engineer who enjoys tinkering with data technologies and AI in his free time. An advocate of open-source technologies, he also collaborates in organizations like GDG or Spain AI. Currently, he is working as Software Engineer in the Data Layer team at Tupl.
About this article:
Scala is a great candidate to build micro-services and has great compiler checks, it is built with scalability in mind, and it runs in the Java Virtual Machine (JVM).
However, when building services that process high volumes of data, you will also require high-performance solutions, such as streaming or asynchronous computations. This leads to a more complex codebase; therefore, it becomes essential to apply abstractions.
There are several projects that offer a solution to this problem, such as scalaz or cats. cats provides modular and efficient abstractions from the functional programming (FP) world, extending Scala’s FP core. As FP relies on type theory, category theory and lambda calculus, we can carefully control the application logic, as well as catch more bugs that would happen at runtime.
In this article, we will focus on cats, which also comes with a full-fledged ecosystem, with libraries like circe, for processing JSON objects, or sttp, for building HTTP clients.
Introduction
Let’s take the following case as an example – a method that builds an email report, and retrieves the email IDs and subjects from the email service. It succeeds only if the email list is not empty and all emails contain a subject; otherwise, it fails.
The main issue here is that exceptions break the function’s referential transparency, that is to say, its evaluation can cause side effects. Moreover, its behaviour cannot be fully tracked in the type system, as exceptions are not included in the method’s signature. A possible solution is to use the Either type:
However, this might be difficult if you are using Scala 2.11 or previous versions, as Either is unbiased – that is, it is required to use .right and .left to explicitly use the right or left projection, respectively. In more recent versions, you might also have an issue managing the nested result, as you will need to unpack firstly Future and then Either.
Increasing your development productivity
One of the key goals of cats is to provide an unobtrusive and performant way to add well-defined behaviour to your Scala application. This is possible as it is based on functional programming concepts, which are mathematically proved. These concepts might be hard to grasp at first, but in the long run, they provide a solid foundation to build your applications.
A good thing about cats is that you can start by using only the modules that you find useful. We will take a look at some of the most used ones, such as Either, Validated and Semigroup, and we will see why they are useful for us.
Semigroup
A semigroup is a type that has an associative binary operation, such as the natural numbers with the + (addition) operation. This means that we can select two natural numbers, and its sum will also return a natural number. For example, 3 + 4 = 7, being 7 another natural number.
If we are handling an Either collection , we can use its Semigroup instance to combine them by using reduce. This happens because Either is implemented with short-circuit; thus, if at least a Left is found, then the result will be that Left; otherwise, it will collect all Right values. This behaviour is also found in Option.
Either and EitherT
As stated earlier, the Either type from cats is essential in Scala 2.11 or previous versions. As it is biased, as in Scala 2.12 or more recent versions, they include some functionality that makes our life easier:
In addition, we can also use EitherT, which provides methods for manipulating Either instances inside a container, such as Future[Either[_, _]] or List[Either[_, _]]. This is pretty helpful for using the for {…} yield syntax, as it will unwrap both the Either and container types to get the right instance:
Note that to get Future[Either[String, String]] instead of EitherT[Future, String, String], you must use the .value method.
Validated
When we need to handle multiple inputs, an efficient way to validate them is to check them separately and accumulate the errors, if any. If you are using Either or Option, you are tied to solving errors one by one, as they can short-circuit. To solve this problem, we use Validated, which will keep all the errors in a collection.
For example, we can validate the training data and hyper-parameters used to train a Machine Learning model. Note that Validation provides a thin abstraction for keeping a syntax similar to that of Try, and that errors are accumulated in a collection.
We need to make sure that the number of epochs is smaller than 1000, and that the model is supported. In addition, we want to check if the CSV input contains the expected columns.
This way, we are able to report multiple errors in our systems in a single pass:
OptionT
OptionT allows to deal with Option instances that are inside a container type, much like it is done in EitherT:
This way, it would build an email report for the last email from the sender, only if both the subject and body are available.
Wrap-up
By extending the core FP functionalities of Scala, cats provides a sound foundation for developing safer, simpler and more efficient applications. Since we started using cats, we have increased the number of bugs we catch in the testing stage, and our codebase is more maintainable. Moreover, it also plays nicely with some high-performance frameworks we use, such as Apache Kafka or Akka.
If you want to learn more about cats, we recommend you to take a look at the available resources for learners.
References
• Cats library