Meow! Start using Cats in your project right now

Gentle introduction to Cats library.

Introduction

Cats is a library providing abstractions for functional programming in Scala.

There are a couple of great posts and courses on Cats out there on the web (like Herding cats and a tutorial on Scala Exercises), but they tend to explore the categories/type classes implemented in the library rather than give practical ready-to-use examples of how to use Cats in existing codebases. This blog post barely scratches the surface of what Cats can do, but instead provides a concise hands-on introduction to the patterns you’re most likely to take advantage of in your Scala project. If you’re using any monads like Future or Option on a daily basis, it’s very likely that Cats can simplify and improve the readability of your code.

Please refer to the Cats wiki on GitHub for guidelines on how to add the library to your project dependencies. We are sticking to version 0.9.0 in the entire post.

Let’s go through the library package-wise, looking at the syntax available in each package.

Helpers for Option and Either

import cats.syntax.option._

Importing this package enables obj.some syntax — equivalent to Some(obj) . The only real difference is that the value is already upcast to Option[T] from Some[T] .

Using obj.some instead of Some(obj) can sometimes improve the readability of unit tests. For example, if you add the following implicit class to your BaseSpec , TestHelper or whatever your base class for tests is called:

then you can use the chained syntax shown below (assuming your unit tests are based on scalamock; also see a post by Bartosz Kowalik):

That’s more readable than Future.successful(Some(user)) , especially if this pattern repeats frequently in the test suite. Chaining .some.asFuture at the end rather than putting it at the front also helps focus on what’s actually being returned rather than on the expected wrapper type.

none[T] , in turn, is shorthand for Option.empty[T] which is just None , but already upcast from None.type to Option[T] . Providing a more specialized type sometimes helps the Scala compiler properly infer the type of expressions containing None .

import cats.syntax.either._

obj.asRight is Right(obj) , obj.asLeft is Left(obj) . In both cases the type of returned value is widened from Right or Left to Either . Just as was the case with .some , these helpers are handy to combine with .asFuture to improve the readability of unit tests:

Either.fromOption(option: Option[A], ifNone: => E) , in turn, is a useful helper for converting an Option to an Either . If the provided option is Some(x) , it becomes Right(x) . Otherwise it becomes Left with the provided ifNone value inside.

instances packages and cartesian syntax

import cats.instances.<F>._

There are a couple of type classes that are fundamental to Cats (and generally to category-based functional programming), the most important ones being Functor , Applicative and Monad . We’re not going into much detail in this blog post (see e.g. the already mentioned tutorial), but what is important to know is that to use most Cats syntax, you also need to import the implicit type class instances for the structures you’re operating with.

Usually, it’s just enough to import the appropriate cats.instances package. For example, when you’re doing the transformations on futures, you’ll need to import cats.instances.future._ . The corresponding packages for options and lists are called cats.instances.option._ and cats.instances.list._ . They provide the implicit type class instances that Cats syntax needs to work properly.

As a side note, if you have trouble finding the required instances or syntax package, the quick workaround is to just import cats.implicits._ . This is not a preferred solution, though, as it can significantly increase compile times — especially if used in many files across the project. It is generally considered good practice to use narrow imports to take some of the implicit resolution burden off the compiler.

import cats.syntax.cartesian._

The cartesian package provides |@| syntax, which allows for an intuitive construct for applying a function that takes more than one parameter to multiple effectful values (like futures).

Let’s say we have 3 futures, one of type Int , one of type String , one of type User and a method accepting three parameters — Int , String and User .

Our goal is to apply the function to the values computed by those 3 futures. With cartesian syntax this becomes very easy and concise:

As pointed out before, to provide the implicit instance (namely, Cartesian[Future] ) required for |@| to work properly, you should import cats.instances.future._ .

This above idea can be expressed even shorter, just:

The result of the above expression will be of type Future[ProcessingResult] . If any of the chained futures fails, the resulting future will also fail with the same exception as the first failing future in the chain (this is fail-fast behavior). What’s important, all futures will run in parallel, as opposed to what would happen in a for comprehension:

In the above snippet (which under the hood translates to flatMap and map calls), stringFuture will not run until intFuture is successfully completed, and in the same way userFuture will be run only after stringFuture completes. But since the computations are independent of one another, it’s perfectly viable to run them in parallel with |@| instead.

Traversing

import cats.syntax.traverse._

traverse

If you have an instance obj of type F[A] that can be mapped over (like Future ) and a function fun of type A => G[B] , then calling obj.map(fun) would give you F[G[B]] . In many common real-life cases, like when F is Option and G is Future , you would get Option[Future[B]] , which most likely isn’t what you wanted.

traverse comes as a solution here. If you call traverse instead of map , like obj.traverse(fun) , you’ll get G[F[A]] , which will be Future[Option[B]] in our case; this is much more useful and easier to process than Option[Future[B]] .

As a side note, there is also a dedicated method Future.traverse in the Future companion object, but the Cats version is far more readable and can easily work on any structure for which certain type classes are available.

sequence

sequence represents an even simpler concept: it can be thought of as simply swapping the types from F[G[A]] to G[F[A]] without even mapping the enclosed value like traverse does.

obj.sequence is in fact implemented in Cats as obj.traverse(identity) . On the other hand, obj.traverse(fun) is roughly equivalent to obj.map(fun).sequence .

flatTraverse

If you have an obj of type F[A] and a function fun of type A => G[F[B]] , then doing obj.map(f) yields result of type F[G[F[B]]] — very unlikely to be what you wanted.

Traversing the obj instead of mapping helps a little — you’ll get G[F[F[B]] instead. Since G is usually something like Future and F is List or Option , you would end up with Future[Option[Option[A]] or Future[List[List[A]]] — a bit awkward to process.

The solution could be to map the result with a _.flatten call like:

and this way you’ll get the desired type G[F[B]] at the end.

However, there is a neat shortcut for this called flatTraverse :

and that solves our problem for good.

Monad transformers

import cats.data.OptionT

An instance of OptionT[F, A] can be thought of as a wrapper over F[Option[A]] which adds a couple of useful methods specific to nested types that aren’t available in F or Option itself. Most typically, your F will be Future (or sometimes slick’s DBIO , but this requires having an implementation of Cats type classes like Functor or Monad for DBIO ). Wrappers such as OptionT are generally known as monad transformers.

A quite common pattern is mapping the inner value stored inside an instance of F[Option[A]] to an instance of F[Option[B]] with a function of type A => B . This can be done with rather verbose syntax like:

With the use of OptionT , this can be simplified as follows:

The above map will return a value of type OptionT[Future, String] .

To get the underlying Future[Option[String]] value, simply call .value on the OptionT instance. It’s also a viable solution to fully switch to OptionT[Future, A] in method parameter/return types and completely (or almost completely) ditch Future[Option[A]] in type declarations.

There are several ways to construct an OptionT instance. The method headers in the table below are slightly simplified: the type parameters and type classes required by each method are skipped.

In production code you’ll most commonly use the OptionT(...) syntax in order to wrap an instance of Future[Option[A]] into Option[F, A] . The other methods, in turn, prove useful to set up OptionT -typed mock values in unit tests.

We have already come across one of OptionT ‘s methods, namely map . There are several other methods available and they mostly differ by the signature of the function they accept as the parameter. As was the case with the previous table, the expected type classes are skipped.

In practice, you’re most likely to use map and semiflatMap .

As is always the case with flatMap and map , you can use it not only explicitly, but also under the hood in for comprehensions, as in the example below:

The OptionT[Future, Money] instance returned by getReservedFundsForUser will enclose a None value if any of the three composed methods returnes an OptionT corresponding to None . Otherwise, if the result of all three calls contains Some , the final outcome will also contain Some .

import cats.data.EitherT

EitherT[F, A, B] is the monad transformer for Either — you can think of it as a wrapper over a F[Either[A, B]] value.

Just as in the above section, I simplified the method headers, skipping type parameters or their context bounds and lower bounds.

Let’s have a quick look at how to create an EitherT instance:

Just to clarify: EitherT.fromEither wraps the provided Either into F , whereas EitherT.right and EitherT.left wrap the value inside the provided F into Right and Left , respectively. EitherT.pure , in turn, wraps the provided B value into Right and then into F .

Another useful way to construct an EitherT instance is to use OptionT ‘s methods toLeft and toRight :

toRight is pretty analogous to the method Either.fromOption mentioned before: just as fromOption built an Either from an Option , toRight creates an EitherT from an OptionT . If the original OptionT stores Some value, it will be wrapped into Right ; otherwise the value provided as the left parameter will be wrapped into a Left .

toLeft is toRight ‘s counterpart which wraps the Some value into Left and transforms None into Right enclosing the provided right value. This is less commonly used in practice, but can serve e.g. for enforcing uniqueness checks in code. We return Left if the value has been found, and Right if it doesn’t yet exist in the system.

The methods available in EitherT are pretty similar to those we’ve seen in OptionT , but there are some notable differences. You might get into some confusion at first when it comes to e.g. map . In the case of OptionT , it was pretty obvious what should be done: map should go over the Option enclosed within Future , and then map the enclosed Option itself. This is slightly less obvious in case of EitherT : should it map over both Left and Right values, or only the Right value?

The answer is that EitherT is right-biased, therefore plain map actually deals with the Right value. This is unlike Either in the Scala standard library up to 2.11, which is in turn unbiased: there’s no map available in Either , only for its left and right projections.

Having said that, let’s take a quick look at the right-biased methods that EitherT[F, A, B] offers:

As a side note, there are also certain methods in EitherT (that you’re likely to need at some point) which map over the Left value, like leftMap , or over both Left and Right values, like fold or bimap .

EitherT is very useful for fail-fast chained verifications:

In the above example, we’re running various checks against the item one by one. If any of the checks fails, the resulting EitherT will contain a Left value. Otherwise, if all of the checks yield a Right (of course we mean a Right wrapped into an EitherT ), then the final outcome will also contain Right . This is a fail-fast behavior: we’re effectively stopping the for comprehension flow at the first Left -ish result.

If you’re instead looking for validation that accumulates the errors (e.g. when dealing with user-provided form data), cats.data.Validated may be a good choice.

Common issues

If anything doesn’t compile as expected, first make sure all the required Cats implicits are in the scope — just try importing cats.implicits._ and see if the problem persists. As mentioned before, though, it’s better to use narrow imports, but if the code doesn’t compile it’s sometimes worth just importing the entire library to check if it solves the problem.

If you’re using Futures , make sure to provide an implicit ExecutionContext in the scope, otherwise Cats won’t be able to infer implicit instances for Future ‘s type classes.

The compiler might quite often have problems with inferring type parameters for traverse and sequence methods. An obvious workaround is to specify those types directly, like list.traverse[Future, Unit](fun) . This might become quite verbose in certain cases, though, and the better way is to try the equivalent methods traverseU and sequenceU , like list.traverseU(fun) . They do some type-level trickery (with cats.Unapply , hence the U ) to help the compiler infer the type parameters.

IntelliJ sometimes reports errors in Cats-loaded code even though the source passes under scalac. One such example are invocations of the methods of cats.data.Nested class, which compile correctly under scalac, but don’t type check under IntelliJ’s presentation compiler. It should work without trouble under Scala IDE, though.