OVO Tech Blog

# Cats and type classes

Posted by Erica Giordo on .
Featured

# Cats and type classes

Posted by Erica Giordo on .

I love going to Scala meetups and living in London gives you tons of chances to attend lots of them. In the past year there has been a spike in the number of Scala communities, making it easy for everybody to attend. Do you fancy an introductory talk? Do you want to dig deeper into FP? Do you want to talk about new shiny libraries? There's a talk for every taste, there's always pizza and the people are really nice.
I like talking to people at these meetups: everybody has a different background, story and nationality and it great to discuss the talks we're going to attend and what everybody expects to learn from them.

Lately there's has been lot of hype around type classes & Co. and often at the end of the talks I hear: "OMG, they're so cool!! I wanna use them in our project too!". So what's the problem?! Listening to these comments more than once made me realize that there's a huge amount of literature on the web, but maybe it's too scattered. What I'll aim to do here is not trying to reinvent the wheel: there are already great articles around (e.g. the amazing Scala with Cats book written by Noel Welsh and Dave Gurnell or the always-helpful posts from Eugene Yokota), so I'll just try to give you a brief summary of the main type classes used in Cats, each one with an easy example. Like a cheat sheet.
For a more mathematical introduction on the topic you can find another blog post that I wrote here.
Let's start.

# Semigroup

A `Semigroup` is a type class with a `combine` method:

`def combine(x: A, y: A): A`

``````import cats.kernel.Semigroup
import cats.instances.int._
import cats.instances.string._

Semigroup[Int].combine(4, 2) // 6 (for Int, sum is used as combine)
Semigroup[String].combine("Hello ", "world!") // "Hello world!" (while concat is used for strings)
``````

But what if we want to use combine for our own case class? We need to implement our own type class instance:

``````case class Error(message: String)
object Error {
implicit val composeError = new Semigroup[Error] {
override def combine(x: Error, y: Error): Error =
Error(s"\${x.message}, \${y.message}")
}
}

val firstError = Error("First error")
val secondError = Error("Second error")

Semigroup[Error].combine(firstError, secondError) // Error("First error", "Second error")
``````

# Monoid

A `Monoid` is a `Semigroup` with a neutral element, that we'll call `empty`:

`def empty: A`

``````import cats.kernel.Monoid
import cats.instances.int._
import cats.instances.string._

Monoid[Int].empty // 0
Monoid[String].empty // ""
``````

In the case of a `Monoid`, we have an alternative version of combine, called `combineAll`. Given that a `Monoid` has an empty element, `combineAll` is easily defined with a `foldLeft`:

``````def combineAll(as: TraversableOnce[A]): A =
as.foldLeft(empty)(combine)

Monoid[Int].combineAll(List(3, 4, 5)) // 12
Monoid[Int].combineAll(List.empty[Int]) // 0

Monoid[String].combineAll(List("H", "e", "l", "l", "o", "!")) // "Hello!"
Monoid[String].combineAll(List.empty[String]) // ""
``````

# Functor

A `Functor` is a type class with a `map` method:

`def map[A, B](fa: F[A])(f: A => B): F[B]`

This is the just `map` that we all know:

``````import cats.Functor
import cats.instances.list._
import cats.instances.option._

Functor[List].map(List(1))((x: Int) => x + 1) // List(2)
Functor[Option].map(Option("Hello!"))((x: String) => x.length) // Some(6)
``````

# Semigroupal

This is our old `Cartesian` friend, that with the 1.0 version of Cats had a revamp. Semigroupal is characterized by the following method:

`def product[A, B](fa: F[A], fb: F[B]): F[(A, B)]`

What `product` does, is just combining `fa` and `fb`. If you remember the cartesian product from your school days, that's exactly what it does (have a look at the List example).

``````import cats.Semigroupal
import cats.instances.option._
import cats.instances.list._
import cats.syntax.option._

Semigroupal[Option].product(1.some, 2.some) // Some((1, 2))
Semigroupal[Option].product(1.some, none) // None

Semigroupal[List].product(List(1, 2), List(3, 4)) // List((1,3), (1,4), (2,3), (2,4))
Semigroupal[List].product(List.empty, List(3, 4)) // List()
``````

# Apply

`Apply` inherits from `Functor` and `Semigroupal` and adds a method `ap`:

`def ap[A, B](ff: F[A => B])(fa: F[A]): F[B]`

The definition of the function passed in the `Apply` context can look weird and discouraging at the beginning, but with an example we can sort it out easily:

``````import cats.Apply
import cats.syntax.option._
import cats.instances.option._

val ff: Option[String => Int] = Option(_.length)
Apply[Option].ap(ff)("Hello!".some) // Some(6)
``````

A little note: in the previous example we imported `import cats.syntax.option._` that, in our case, allows us to lift a `String` into an `Option[String]`.

Another example with `Apply`:

``````import cats.Apply
import cats.instances.list._

val ff: List[Int => Boolean] = List(_ > 2)
Apply[List].ap(ff)(List(0, 1, 2, 3, 4)) // List(false, false, false, true, true)
``````

You could ask yourself "so what's the difference between `ap` and `map`?". Well, it's in the signature of the passed function. While `map` takes `A => B`, here `ap` expects a function such as `F[A => B]`.

# Applicative

`Apply` with the addition of the method `pure` forms an `Applicative`:

`def pure[A](x: A): F[A]`

`pure` just takes an element and lifts it into the `Applicative Functor` (easy peasy).

``````import cats.Applicative
import cats.instances.option._
import cats.instances.list._

Applicative[Option].pure(1) // Some(1)
Applicative[List].pure(1) // List(1)
``````

# FlatMap

Adding to an `Apply` the methods `flatten` and `flatMap`, we are rewarded with a `FlatMap` and one step away from a `Monad`.

`def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]`

`def flatten[A](ffa: F[F[A]]): F[A]`

Even if the `FlatMap` type class is not often mentioned, we always use these two methods.

``````import cats.FlatMap
import cats.syntax.option._
import cats.instances.option._
import cats.instances.list._

// mouse is imported here just to shake things up a bit
// (https://github.com/typelevel/mouse)
// given a boolean condition, `.option` will return None if false,
// otherwise it will return an Option of the argument
//
// e.g.
// ("Hello".length == 1).option("World") // None
// ("Hello".length != 0).option("World") // Some("World")
import mouse.boolean._

FlatMap[Option].flatten(1.some.some) // Some(1)
FlatMap[List].flatten(List(List(1))) // List(1)

val fOpt = (x: Int) => (x == 1).option(true)
FlatMap[Option].flatMap(none)(fOpt) // None
FlatMap[Option].flatMap(1.some)(fOpt) // Some(true)

val fStr = (x: String) => x.toCharArray.toList
FlatMap[List].flatMap(List.empty)(fStr) // List()
FlatMap[List].flatMap(List("Hello!"))(fStr) // List(H, e, l, l, o, !)
``````

Finally! What's a `Monad` then? It is a type class that extends `FlatMap` and `Applicative`.

# Cheat sheet

Now, as promised:

Type class Inherits from Methods
`Semigroup` `def combine(x: A, y: A): A`
`Monoid` `Semigroup` `def empty: A`
`Functor` `def map[A, B](fa: F[A])(f: A => B): F[B]`
`Semigroupal` `def product[A, B](fa: F[A], fb: F[B]): F[(A, B)]`
`Apply` `Functor`
`Semigroupal`
`def ap[A, B](ff: F[A => B])(fa: F[A]): F[B]`
`Applicative` `Apply` `def pure[A](x: A): F[A]`
`FlatMap` `Apply` `def flatten[A](ffa: F[F[A]]): F[A]`
`def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]`
`Monad` `FlatMap`
`Applicative`

# Conclusion

Type classes and Cats are your friends, don't be scared just because they have fancy names. The Cats documentation is amazing and if you have any doubt while coding have a look at it before opening Stack Overflow (yes, I know, it is THAT amazing, you don't even have to Google your problems - sometimes).
I find the Scala with Cats book really good as an intro for these structures and if you want to practice more on these concepts there's a great website from 47 Degrees that will help you out a lot with that.

Happy coding! 🐱