Solutions to "Scala with Cats": Chapter 5

April 5, 2023

These are my solutions to the exercises of chapter 5 of Scala with Cats.

Table of Contents

Exercise 5.4: Transform and Roll Out

We can rewrite Response using a monad transformer as follows:

import cats.data.EitherT
import scala.concurrent.Future

type Response[A] = EitherT[Future, String, A]

We can implement getPowerLevel as follows. Note that we need an implicit ExecutionContext in scope so that we can have an instance of Functor for Future, even if we just create our Futures with Future.successful (which doesn’t need one). We are using the global ExecutionContext for convenience.

import scala.concurrent.ExecutionContext.Implicits.global

def getPowerLevel(autobot: String): Response[Int] =
  powerLevels.get(autobot) match {
    case Some(powerLevel) => EitherT.right(Future.successful(powerLevel))
    case None => EitherT.left(Future.successful(s"Autobot $autobot is unreachable"))
  }

To implement canSpecialMove we can request the power levels of each autobot and check if their sum is greater than 15. We can use flatMap on EitherT which makes sure that errors being raised on calls to getPowerLevel stop the sequencing and have canSpecialMove return a Response with the appropriate error message.

def canSpecialMove(ally1: String, ally2: String): Response[Boolean] =
  for {
    powerLevel1 <- getPowerLevel(ally1)
    powerLevel2 <- getPowerLevel(ally2)
  } yield (powerLevel1 + powerLevel2) > 15

To implement tacticalReport, we need to produce a String from a Future, so we must use Await.

import scala.concurrent.Await
import scala.concurrent.duration._

def tacticalReport(ally1: String, ally2: String): String = {
  Await.result(canSpecialMove(ally1, ally2).value, 5.seconds) match {
    case Left(msg) =>
      s"Comms error: $msg"
    case Right(true) =>
      s"$ally1 and $ally2 are ready to roll out!"
    case Right(false) =>
      s"$ally1 and $ally2 need a recharge."
  }
}