Partially functional

You can't wait for inspiration. You have to go after it with a club.

Learn you a Cats

• scala

So you have heard of cats and you possibly heard of contravariant functors, monad transformers, applicatives and cartesians, and possibly even stumbled upon the term Kleisli arrows and thought “who needs this?” Well, you don’t need this, but it doesn’t hurt to learn a new thing or two. In this article I will show you a few examples of what you can do by just adding the cats library to your project’s dependencies.


Syntax (extension methods)

One of the most essential cats’ type classes is the Applicative. It extends a number of traits and is packed full of useful methods, but in this section we’ll only focus on the single abstract method defined on the type class:

trait Applicative[F[_]] extends ... {
  def pure[A](a:A): F[A]
}

This represents the ability to lift a value into a context. (i.e. taking a pure value of type A and wrapping it in an F, producing an F[A])

Compare these two lines:

Future.successful(42)
42.pure

pure isn’t a method defined on the Int type. It’s a method defined on the Applicative trait. Scala offers the implicit class construct to facilitate “the creation of classes which provide extension methods to another type.” (Scala SIP-13). In our example, the compiler performs a conversion from Int to some type that supports the pure method.

Extension methods can make working with cats type classes more comfortable. In cats terminology, adding methods onto various types is called syntax enrichment and the implicit classes responsible for introducing the enrichment are referred to as syntax.

Cats provides syntax for all of its type classes.

The Functor[F[_]] type class defines a method def map[A,B](fa: F[A])(f: A=>B): F[B]. Any F[A] could be enriched with a map method with just the f parameter. This makes for a more natural way of interfacing with type classes:

function0Functor.map(myFun)(_.length)
myFun.map(_.length)

In order to use the cats syntax, you will need to:

  • import the syntax for a chosen type class
  • make an implicit instance of that type class available in scope
  • make other implicit parts available in scope

Regarding the last point: in some cases the type class instances are produced by a method requiring an implicit argument. Unless this can be sourced by the compiler, no instance can be constructed. Most notably this is the ExecutionContext which is required for the creation of type class instances for Future.

(Read Eugene’s article to understand how implicits are resolved.)

Let’s say all of your application’s controllers derive from a common controller trait. With the below setup you could do away with all Future.successful’s in your controllers:

import cats.instances.FutureInstances
import cats.syntax.ApplicativeSyntax

trait Controller extends ApplicativeSyntax with FutureInstances

object MyController extends Controller { ... }

Your controller code could then look something like this (assuming an implicit ExecutionContext is around):

    form.bindFromRequest().fold(
      err => BadRequest(someView(err)).pure,
      ok=> callService(ok).ifM(
        ifTrue = doSomething().map(res => someScreen(res)),
        ifFalse = anotherScreen().pure
      ).map(Redirect)))

(This was taken from a Play-framework-bassed application, cut down to omit the full names of rendered views and to hide the reverse-routing.) It demonstrates usage of the applicative syntax - the two pures that lifted a value into Future context required by the asynchronous play action. As doSomething returns a result that’s already in the Future context, we just map it, no lifting is required. You can also see a bit of cats’ flatMap syntax (the ifM method added onto an F[Boolean]) which we will return to discuss later in this article.


Monad transformers

In some cases, when a method returns a F[G[T]] you could make your life a bit easier by pretending the Ts are actually in G context and the F will be wrapped around it eventually. If a method returns Future[Option[Banana]], you could work with a wrapper that is like an Option, but that wrapper is clever enough to not forget, that the Option is in a Future context. Working with OptionT[Future, Banana] is at the end of the day almost as natural as working with good old Option. I am using OptionT as an example but there are transformers for other monads too. Free of charge, when you use cats. Monad transformers make life easier when comprehending over nested monads, as you will see in the example below.

Take a look at this code:

final case class Banana(colour: String)

object FutureOptions {

  def findById(id: Long): Future[Option[Banana]] =
    Future.successful(Some(Banana("yellow")))

  val program = for {
    optBanana <- findById(123L)
  } yield optBanana.map(_.colour)

  def main(args: Array[String]): Unit =
    Await.result(program.map(_.fold(())(println)), Inf)

}

There are two ugly points in the above code listing: a map operation in the yield clause of our program’s for-comprehension; and another mapping of the final future result in order to make use of it (i.e. to print it). Now let’s add cats to our code and we get the following:

object OptionTs extends FutureInstances {

  def findById(id: Long): OptionT[Future, Banana] =
    OptionT.some(Banana("yellow"))

  val program = for {
    banana <- findById(123L)
  } yield banana.colour

  def main(args: Array[String]): Unit =
    Await.result(program.fold(())(println), Inf)

}

This code reads more naturally. The futurity of the Option is hidden away from our view. Cats’ OptionT is that aforementioned “clever wrapper” that keeps track of the un-nested Future monad. It is able to do so thanks to an instance of Applicative[Future] in scope. Where did it come from? Well, in our example we mixed the various cats instances for Future via the FutureInstances trait. We could also achieve the same by a more localised import of cats.instances.future._

The first cats-free sample required these imports:

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.Duration._
import scala.concurrent.{Await, Future}

The catified code sample required two additional lines of imports:

import cats.data.OptionT
import cats.instances.FutureInstances

Cats monad transformers come handy for un-nesting nested monads, but also provide a method (.value) for re-nesting them. Just as you can unnest F[M[T]] using a transformer for the monad M into MT[F, T], you can go back from MT[F, T] to F[M[T]] via the .value method.

Type aliases

This is not specifically cats-related advice, but you could consider defining type aliases in some cases:

// nested, verbose, awkward to work with
def getUser(id: String): Future[Either[QueryError, User]]

// un-nested, more pleasant to work with, but still verbose
def getUser(id: String): EitherT[Future, QueryError, User]

//let's use an alias for the above return type
type Query[T] = EitherT[Future, QueryError, T]

// nice
def getUser(id: String): Query[User]

If you are only ever going to need Query[T] in one or two places, then defining an alias would just add insult to injury. However, in cases when the type representing the error is the same and all your repository/service methods are asynchronous (wrapped in a Future or Task or the like), then why not fix those two types in a type alias and focus only on the actual type to be returned.


Parallel Future execution with Cartesians

In a number of my previous posts I have expected you to have cats on your classpath. For example in this article about parallel evaluation of Futures I made good use of some of the constructs provided by cats. Namely Cartesians and the Traverse type class.

Below is an example of mixing sequential and parallel execution of Futures by using the cats-provided cartesian builder (and the scream operator |@|)

import cats.instances.FutureInstances
import cats.syntax.CartesianSyntax

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.Duration.Inf
import scala.concurrent.{Await, Future}

final case class Profile(iid: String, name: String, age: Int)

object SequentialAndParallelFutures extends CartesianSyntax with FutureInstances {

  def main(args: Array[String]): Unit = {
    val program = for {
      id <- getProfileId("reteP")
      p <- getProfile(id)
      (prefs, balance) <- (getPreferences(p.iid) |@| getBalance(p.iid)).tupled
    } yield println(s"${System.currentTimeMillis()} ${p.name}'s balance is $balance. Preferences: $prefs")
    Await.result(program, Inf)
  }

  val getProfileId: String => Future[String] = in => Future {
    println(s"${System.currentTimeMillis()} getProfileId")
    Thread.sleep(1000)
    in
  }

  val getProfile: String => Future[Profile] = pid => Future {
    println(s"${System.currentTimeMillis()} getProfile")
    Thread.sleep(1000)
    Profile(pid.reverse + pid, pid.reverse, pid.length)
  }

  val getPreferences: String => Future[String] = iid => Future {
    println(s"${System.currentTimeMillis()} getPreferences")
    Thread.sleep(1000)
    iid
  }

  val getBalance: String => Future[Long] = iid => Future {
    println(s"${System.currentTimeMillis()} getBalance")
    Thread.sleep(1000)
    System.currentTimeMillis
  }

}

Note the times printed in the output. Each operation takes roughly one second to complete. The first two happen one after another, then the calls to getPreferences and getBalance are triggered in parallel and the final result is logged roughly a second after they both started executing.

1496000914339 getProfileId
1496000915341 getProfile
1496000916346 getPreferences
1496000916346 getBalance
1496000917351 Peter's balance is 1496000917351. Preferences: PeterreteP

Using the Applicative instance provided by cats, and the cartesian syntax we were able to collect the results of concurrently running futures into a tuple, which we can easily deconstruct in the left-hand side of the for-comprehension and use the results in any subsequent operations. Isn’t that neat?


Traversing

Let’s say we have a number of places that will potentially (read: “will” -> Future, “potentially” -> Option) produce a value of type T. We could list these various places like so:

List(f1(), f2(), f3())

which would lead to a List of Future Options of T. What would be perhaps more useful is a Future List of all Ts that we were able to obtain from our various places. Here’s how you could go about doing that with the cats library:

List(f1(), f2(), f3()).sequence.map(_.flatten)

The full code listing follows. We’re using the Traverse syntax (methods defined on the traverse type class tacked onto applicable types as extension methods) and cats’ instances for List and Future.

import cats.instances.{FutureInstances, ListInstances}
import cats.syntax.TraverseSyntax

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.Duration.Inf
import scala.concurrent.{Await, Future}

object TraversingAsap extends TraverseSyntax with FutureInstances with ListInstances {

  def f1() = Future {
    Thread.sleep(100)
    println(s"${System.currentTimeMillis()} I return Foo")
    Some("Foo")
  }

  def f2() = Future {
    Thread.sleep(50)
    println(s"${System.currentTimeMillis()} No. Not here")
    None
  }

  def f3() = Future {
    println(s"${System.currentTimeMillis()} I return Bar")
    Some("Bar")
  }

  val program = List(f1(), f2(), f3()).sequence map (_.flatten)

  def main(args: Array[String]): Unit = Await.result(program map println, Inf)

}

note IntelliJ uses a lightweight presentation compiler and struggles with the .sequence syntax. You will see highlighted errors but the application still compiles and runs fine from SBT.

We used Thread.sleep in a way that almost guarantees that Bar is computed before anything else, and Foo is obtained last. In the program output, however, the final List is ordered correctly as one would expect:

1496080141824 I return Bar
1496080141876 No. Not here
1496080141927 I return Foo
List(Foo, Bar)

The Futures in the above example would be submitted to the execution context for evaluation ASAP. If you would like to control when the list of future-value-producing functions starts evaluating, you could create a list of Function0 values. You would then traverse the list of functions with another function that applies them. Like so:

  val program = List(() => f1(), () => f2(), () => f3()).traverse(_ ()) map (_.flatten)

Monadic IF

I have become quite fond of the ifM flatMap syntax that cats provides and use it quite extensively. ifM extension method appears on any F[Boolean] once you’ve either imported cats.syntax.flatMap._ or mixed in the FlatMapSyntax trait. It’s effectively delegating to the flatMap operation of the F monad, but the mapping function that will be used for flatmapping will depend on the boolean value contained in the F context. You provide one for ifTrue and one for ifFalse and then just sit back and watch the show.

There’s a nice example of ifM in action in one of my previous articles: Conditional flatMap


Semigroups, Monoids and foldMap

Both the Semigroup and Monoid type classes are used for combining a number of values into one. The important detail is that the combine operation is associative. Monoid, which extends Semigroup, has additionally the ability to combine 0 values.

We will discuss Monoids in this article. The thing to remember about Semigroups is that there are some types for which a Monoid instance cannot be created, as there doesn’t exist an identity element of that type. A good example would be a data type like the cats.data.NonEmptyList, which simply cannot be empty. Thus we can’t implement the empty method that Monoid typeclass adds to Semigroup’s API.

There can be multiple Monoid implementations for a single data type. Certain operations over a data type demonstrate monoidal properties. These operations must be binary (take two operands) and must satisfy these two axioms:

  • associativity: for all values a, b, c : (a op b) op c == a op (b op c)
  • identity element: there exists a value e for which: a op e == e op a == a

For example integer addition, where + is the binary operation and 0 the identity element:

1 + (2 + (3 + 4)) == ((1 + 2) + 3) + 4 == 10
5 + 0 = 0 + 5 = 5

It’s trivial to implement a Monoid for integer addition:

implicit object intAdditionMonoid extends Monoid[Int] {
  def combine(i1: Int, i2: Int): Int = i1 + i2
  def empty: Int = 0
}

This is not particularly interesting. We could create monoids that can combine multiple maps into one, or collect a number of validations into a single validation result, etc.

With relevant Monoid instance in scope, we can make use cats’ FoldableSyntax to add foldMap operation onto foldable types. If we import the type class instance of Foldable[List] any List will be eligible for wrapping up in an implicit class that will give it the foldMap method. Here’s an example of foldMap in action:

case class Cat(name:String, age: Int)
val catz = List(Cat("Mitzi", 2), Cat("Fluffy", 5), Cat("Ginger", 3))

import cats.syntax.foldable._
import cats.instances.list._

Let’s sum up the cats’ age:

catz.map(_.age).sum
// res1: Int = 10

catz.map(_.age).fold(0)(_+_)
// res2: Int = 10

catz.foldLeft(0)((total, cat) => total + cat.age)
// res3: Int = 10

catz.foldMap(_.age)
// res4: Int = 10

The first two ways of adding up the cats’ age take two steps to achieve the desired result. Both create an intermediate, throw-away list of integers. .sum method simply delegates to a foldLeft and the fold in the second line delegates to foldLeft too.

If we wanted to avoid creating an intermediate collection of cats’ ages, we could do a foldLeft as in the third example. We can’t use a fold anymore, as the accumulator type is not a supertype of Cat. The resulting code is not too bad in that it performs the summation of cats’ ages without an intermediate step. Nowever, for what it does, it is unnecessarily verbose.

With foldMap the mapping and folding happens in a single step. The code is easy to read and understand - it looks like a simple map operation, but as long as there’s a Monoid instance for the type mapped-into, we can fold down the cats into a single value in one go.