You wouldn't think the Internet would need a an introduction to cats, but then this Cats is a Scala library, not a small furry bed-hogging mammal. Cats is the spiritual successor to Scalaz: a library of absolutely essential utilities you really want to be using in your Scala code. Compared to Scalaz, Cats is more modular and it is using some newer tools to make its code base easier to work with.
Cats is still a closer to being a kitten than the king of the alley, but it has definitely reached the stage where it is usable. In this article I'll provide a basic introduction to getting started with Cats.
A snapshot release of Cats is now available on Sonatype. Cats, as I mentioned above, is highly modularised, so there are some dependencies you need for the full Cats experience:
algebra
, which defines basic abstractions like Monoid
and Semigroup
;cats-core
, which defines the basic abstractions in Cats;algebra-std
, for implementations of the abstractions in algebra
for types in the standard library; andcats-std
, which has implementations of the abstractions in Cats for the standard library.That sounds like a lot of stuff, but you can just copy and paste the snippet below in your build.sbt
to get going.
Also note: to run some of the examples below you will need to clone cats
from Github and run sbt publish-local
. Cats is moving fast and the snapshot release lags behind the development head.
val snapshots = "Sonatype Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"
val algebraVersion = "0.2.0-SNAPSHOT"
val catsVersion = "0.1.0-SNAPSHOT"
val algebra = "org.spire-math" %% "algebra" % algebraVersion
val algebraStd = "org.spire-math" %% "algebra-std" % algebraVersion
val cats = "org.spire-math" %% "cats-core" % catsVersion
val catsStd = "org.spire-math" %% "cats-std" % catsVersion
scalaVersion := "2.11.6"
libraryDependencies ++=
Seq(
algebra, algebraStd,
cats, catsStd
)
resolvers += snapshots
Cats has a fairly straightforward organisation.
In the package cats
you will find the basic types. Cats reexports some of the types from algebra
, which provides very basic facilities like Order
and Monoid
.
In cats.data
there are data types that work with the abstractions defined in cats
. These are the tools you are most likely to use in your day-to-day work.
The package cats.std
holds type class instances for classes in the standard library, such as Option
, List
, and Map
.
Finally, cats.syntax
contains useful syntax (implicit classes) for working more easily with Cats.
The types in cats.data
are probably the first place you'll explore when you start using Cats. If you've used Scalaz before you will find analogues to many familiar abstractions. Here are some of the highlights.
Xor[A, B]
is a right-biased Either
. An instance of Xor
is either a Xor.Left[A]
or a Xor.Right[B]
. Being right-biased means that the Right
case is considered a success, and Left
is considered failure. Unlike Either
, Xor
has flatMap
and map
methods. These methods do something if the actual instance is Right
, in the same way that flatMap
on Option
only does something if the actual instance is a Some
. This choice gives rise to the term "right-biased".
To work better with type classes, the Xor
companion object provides convenience constructors left
and right
that return results of type Xor
. These are the preferred way to construct instances.
Here's an example.
import cats.data.Xor
object XorExample {
// Get Xor.left and Xor.right into scope
import Xor.{left, right}
// The type we will be working with
type Result[A] = String Xor A
val l: Result[Int] = left("Failed")
val r: Result[Int] = right(1)
// Nothing happens when we map a left
println(l map (x => x + 1))
// The right is transformed
println(r map (x => x + 1))
}
Xor
is a great type to represent the results of some computation, using Left
to hold an error message and Right
to hold a successful result.
The Validated[E, A]
type is the equivalent of Scalaz's Validation
. Like Xor
it has two cases, which are Validated.Invalid[E]
and Validated.Valid[A]
. Validated
is not a monad, so it does not have a flatMap
method. It is, however, an applicative functor, and its typical usage is to accumulate errors.
Whereas an Xor
stops running as soon as it encounters a Left
, when we combine Validated
instances the error messages contained within any Invalid
instances will be appended together in the final result.
Like Xor
, Validated
has convenience constructors defined on the companion object.
The standard way to combine Validated
instances is using the |@|
syntax. Here's an example.
import cats.syntax.apply._ // For |@| syntax
import cats.std.list._ // For semigroup (append) on List
object ValidatedExample {
import Xor.{left, right}
import Validated.{invalid, valid}
// We are going to compare the behaviour of Xor and Validated. First we
// define some instances. Then we combine them using flatMap
// (for-comprehension) or `|@|` as appropriate.
type Error = List[String]
type XorR = Xor[Error, Int]
type ValidatedR = Validated[Error, Int]
val x1: XorR = right(1)
val x2: XorR = left(List("Stops here"))
val x3: XorR = left(List("This will be ignored"))
val v1: ValidatedR = valid(1)
val v2: ValidatedR = invalid(List("Accumulates this"))
val v3: ValidatedR = invalid(List("And this"))
// Stops as soon as we encounter an error
println(
for {
x <- x1
y <- x2
z <- x3
} yield x + y + z
)
// Accumulates all the errors
println(
(v1 |@| v2 |@| v3) map { _ + _ + _ }
)
}
Validated
and Xor
are the two most commonly used data types in cats.data
. Let's look at the other packages now.
The base cats
packages contains the fundamental types like Monad
and Applicative
. If you're used to Scalaz there are a few differences here. Cats defines a FlatMap
trait that Monad
extends. Like Scalaz, Applicative
extends Apply
, and it is Apply
that defines the |@|
syntax we used when we looked at Validated
.
Cats defines syntax is a separate package, as in Scalaz. The tools in this package enrich types with extra methods that makes working with Cats more convenient. We have already seen an example when we imported cats.syntax.apply._
to get the |@|
syntax. There are similar helpers for monad
, eq
, and other types. If you're used to Scalaz you will note some surprising omissions, such as the lack of syntax for constructing Xor
instances with .left
and .right
. This is one area where Cats shows its youth -- these helpers are still under development.
In cats.std
are the type class instances for the standard library. Import cats.std.list._
to get the Monad
, Semigroup
, and other instances defined on List
. Instances defined on the value types are in cats.std.anyval._
This has been a very quick overview of Cats. There are other packages, like cats.free
, that are also part of the project, and of course it is still changing rapidly. The best place to track the development of Cats is its very active gitter Room.
Cats also has an emphasis on accessibility that I found lacking from Scalaz. Basic documentation is available, with more under development, and Cats also subscribes to the Typelevel code of conduct to ensure a welcoming environment for all.