Cats makes heavy use of implicits. Both as a user and an extender of the library, it will be useful to have general idea on where things are coming from. If you’re just starting out with Cats, you can use the following the imports and skip this page, assuming you’re using Cats 2.2.0 and above:
scala> import cats._, cats.data._, cats.syntax.all._
Prior to Cats 2.2.0 it was:
scala> import cats._, cats.data._, cats.implicits._
Let’s quickly review Scala 2’s imports and implicits! In Scala, imports are used for two purposes:
Given some type A
, implicit is a mechanism to ask the compiler for a specific (term) value for the type. This can be used for different purposes, for Cats, the 2 main usages are:
Implicits are selected in the following precedence:
Now let’s see what gets imported with import cats._
.
First, the names. Typeclasses like Show[A]
and Functor[F[_]]
are implemented as trait, and are defined under the cats
package. So instead of writing cats.Show[A]
we can write Show[A]
.
Next, also the names, but type aliases. cats
’s package object declares type aliases like Eq[A]
and ~>[F[_], G[_]]
. Again, these can also be accessed as cats.Eq[A]
if you want.
Finally, catsInstancesForId
is defined as typeclass instance of Id[A]
for Traverse[F[_]]
, Monad[F[_]]
etc, but it’s not relevant. By virtue of declaring an instance within its package object it will be available, so importing doesn’t add much. Let’s check this:
scala> cats.Functor[cats.Id]
res0: cats.Functor[cats.Id] = cats.package$$anon$1@3c201c09
No import needed, which is a good thing. So, the merit of import cats._
is for convenience, and it’s optional.
In March 2020, Travis Brown’s #3043 was merged and was released as Cats 2.2.0. In short, this change added the typeclass instances of standard library types into the companion object of the typeclasses.
This reduces the need for importing things into the lexical scope, which has the benefit of simplicity and apparently less work for the compiler. For instance, with Cats 2.4.x the following works without any imports:
scala> cats.Functor[Option]
val res1: cats.Functor[Option] = cats.instances.OptionInstances$$anon$1@56a2a3bf
See Travis’s Implicit scope and Cats for more details.
Next let’s see what gets imported with import cats.data._
.
First, more names. There are custom datatype defined under the cats.data
package such as Validated[+E, +A]
.
Next, the type aliases. The cats.data
package object defines type aliases such as Reader[A, B]
, which is treated as a specialization of ReaderT
transformer. We can still write this as cats.data.Reader[A, B]
.
What then is import cats.implicits._
doing? Here’s the definition of implicits object:
package cats
object implicits extends syntax.AllSyntax with instances.AllInstances
This is quite a nice way of organizing the imports. implicits
object itself doesn’t define anythig and it just mixes in the traits. We are going to look at each traits in detail, but they can also be imported a la carte, dim sum style. Back to the full course.
Thus far, I have been intentionally conflating the concept of typeclass instances and method injection (aka enrich my library). But the fact that (Int, +)
forms a Monoid
and that Monoid
introduces |+|
operator are two different things.
One of the interesting design of Cats is that it rigorously separates the two concepts into “instance” and “syntax.” Even if it makes logical sense to some users, the choice of symbolic operators can often be a point of contention with any libraries. Libraries and tools such as sbt, dispatch, and specs introduce its own DSL, and their effectiveness have been hotly debated.
AllInstances
is a trait that mixes in all the typeclass instances for built-in datatypes such as Either[A, B]
and Option[A]
.
package cats
package instances
trait AllInstances
extends FunctionInstances
with StringInstances
with EitherInstances
with ListInstances
with OptionInstances
with SetInstances
with StreamInstances
with VectorInstances
with AnyValInstances
with MapInstances
with BigIntInstances
with BigDecimalInstances
with FutureInstances
with TryInstances
with TupleInstances
with UUIDInstances
with SymbolInstances
AllSyntax
is a trait that mixes in all of the operators available in Cats.
package cats
package syntax
trait AllSyntax
extends ApplicativeSyntax
with ApplicativeErrorSyntax
with ApplySyntax
with BifunctorSyntax
with BifoldableSyntax
with BitraverseSyntax
with CartesianSyntax
with CoflatMapSyntax
with ComonadSyntax
with ComposeSyntax
with ContravariantSyntax
with CoproductSyntax
with EitherSyntax
with EqSyntax
....
Or, I’d like to call dim sum style, where they bring in a cart load of chinese dishes and you pick what you want.
If for whatever reason if you do not wish to import the entire cats.implicits._
, you can pick and choose.
As I mentioned above, after Cats 2.2.0, you typically don’t have to do anything to get the typeclass instances.
cats.Monad[Option].pure(0)
// res0: Option[Int] = Some(value = 0)
If you want to import typeclass instances for Option
for some reason:
{
import cats.instances.option._
cats.Monad[Option].pure(0)
}
// res1: Option[Int] = Some(value = 0)
If you just want all instances, here’s how to load them all:
{
import cats.instances.all._
cats.Monoid[Int].empty
}
// res2: Int = 0
Because we have not injected any operators, you would have to work more with helper functions and functions under typeclass instances, which could be exactly what you want.
Typeclass syntax are broken down by the typeclass. Here’s how to get injected methods and operators for Eq
s:
{
import cats.syntax.eq._
1 === 1
}
// res3: Boolean = true
Cats datatype syntax like Writer
are also available under cats.syntax
package:
{
import cats.syntax.writer._
1.tell
}
// res4: cats.data.package.Writer[Int, Unit] = WriterT(run = (1, ()))
Standard datatype syntax are broken down by the datatypes. Here’s how to get injected methods and functions for Option
:
{
import cats.syntax.option._
1.some
}
// res5: Option[Int] = Some(value = 1)
Here’s how to load all syntax and all instances.
{
import cats.syntax.all._
import cats.instances.all._
1.some
}
// res6: Option[Int] = Some(value = 1)
This is the same as importing cats.implicits._
.
Again, if you are at all confused by this, just stick with the following first:
scala> import cats._, cats.data._, cats.syntax.all._