Essential Scala tricks.

If you have some experience with Scala but are new to advanced functional programming concepts, used / propagated by libraries like ScalaZ or Cats, then a few of the tricks mentioned here are good to have, and a few others are must have.

First & foremost, you must understand the concepts as mentioned in the Cats jump start guide. A lot of the concepts here are taken from this same document, so let’s start the adventure.

1. Converting a value to an optional value

Suppose, you have to convert an Int to an Option[Int]. The usual way is:

Option(5)
Some(5)

Using cats:

import cats.syntax.option._

5.some

2. Converting a value to an Either

The usual way:

Right(7)
Left(“some error occurred”)

Using cats:

import cats.syntax.either._

7.asRight
“some error occurred”.asLeft

3. Converting a value to a Future value

The usual way:

Future.successful(5)

We can use the following implicit class:

import scala.concurrent.Future

implicit class ObjOps[T](obj: T) {
  def asFuture: Future[T] = Future.successful(obj)
}

And then we may use:

5.asFuture

Now you may say that we haven’t gained much apart from the convenience of a few less keystrokes, but these are huge time savers when you have to change a lot of existing values to a different type of value. e.g. suppose there was a function which takes an Int parameter, and for some reason, we modified that function to take an Option[Int] parameter, and suppose this function is getting called multiple times, then to wrap an int value with Option() or Some() is a lot more tedious than just appending .some to that value 😀

4. Converting a List[Right[_]] or List[Left[_]] to Right[List[_]] or Left[List[_]]

val xs: List[Either[String, Int]] = List(Right(1), Right(2), Right(3))
val seqXs = xs.sequence // Right(List(1, 2, 3))

val ys: List[Either[String, Int]] = List(Right(1), Left("error"), Right(3))
val seqYs = ys.sequence // Left(error)

5. Getting a product of two Either(s)

Suppose you have two Either values, and if you know that both of them are Right values, then you can get a tuple (product) of both the Right values as follows:

import cats.syntax._

type EitherString[T] = Either[String, T]

val r1: Either[String, Int] = 5.asRight
val r2: Either[String, Int] = 6.asRight
val mergedRs: EitherString[(Int, Int)] = Semigroupal[EitherString].product(r1, r2)
println(s"merged rights: $mergedRs") // merged rights: Right((5,6))

Be careful, that if both of them are Left values, then you do not get the product of the left values but only the first left value (error), as is evident by the type signature.

val e1: Either[String, Int] = "Can't divide by zero".asLeft
val e2: Either[String, Int] = "Can't divide by negative number".asLeft
val mergedLs: EitherString[(Int, Int)] = Semigroupal[EitherString].product(e1, e2)
println(s"merged lefts: $mergedLs") // merged lefts: Left(Can't divide by zero)

6. Getting results from unrelated Futures

Suppose you have a few functions which return Future(s), and these functions are not related to each other, meaning that the return value of any of the functions is not being used in any other function, then instead of calling those functions using a for-comprehension (because then, they will all execute sequentially, one after the other), you can use cats mapN function (because then, these futures will execute parallelly).

package example

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import java.time.LocalDateTime
import java.time.Duration
import scala.util.Failure
import scala.util.Success
import java.time.ZoneOffset

import cats.instances.future._
import cats.syntax.apply._

object FutureEx extends App {

  val before = LocalDateTime.now()

  val sum = for {
    a <- getA
    b <- getB
    c <- getC
  } yield (a + b + c)

  sum onComplete {
    case Failure(ex) => println(s"Error: ${ex.getMessage()}")

    case Success(value) =>
      val after = LocalDateTime.now()
      println(s"before: $before")
      println(s"after: $after")

      val diff = getDiff(before, after)
      println(s"diff: $diff")
      println(s"sum: $value")
  }

  // let the above finish
  Thread.sleep(20000)

  val beforeN = LocalDateTime.now()
  val usingMapN = (getA, getB, getC).mapN(add)

  usingMapN onComplete {
    case Failure(ex) => println(s"Error: ${ex.getMessage()}")

    case Success(value) =>
      val afterN = LocalDateTime.now()
      println(s"beforeN: $beforeN")
      println(s"afterN: $afterN")

      val diff = getDiff(beforeN, afterN)
      println(s"diffN: $diff")
      println(s"sum: $value")
  }

  // let the above finish
  Thread.sleep(20000)

  def getA: Future[Int] = Future {
    println("inside A")
    Thread.sleep(3000)
    2
  }

  def getB: Future[Int] = Future {
    println("inside B")
    Thread.sleep(3000)
    3
  }

  def getC: Future[Int] = Future {
    println("inside C")
    Thread.sleep(3000)
    4
  }

  def add(a: Int, b: Int, c: Int) = a + b + c

  def getDiff(before: LocalDateTime, after: LocalDateTime): Long = {
    Duration.between(before.toInstant(ZoneOffset.UTC), after.toInstant(ZoneOffset.UTC)).toMillis()
  }
}

/** Using for comprehension
  *
  * inside A
  * inside B
  * inside C
  * before: 2021-02-01T19:07:44.148
  * after: 2021-02-01T19:07:53.343
  * diff: 9195
  * sum: 9
  */

/** Using mapN
  *
  * inside A
  * inside C
  * inside B
  * beforeN: 2021-04-29T11:14:51.508
  * afterN: 2021-04-29T11:14:54.530
  * diffN: 3022
  * sum: 9
  */

As you can see in the comments above, the first version (using for comprehension) took more than 9 secs. (because we’ve used Thread.sleep(3000) in each of the getA, getB and getC functions). However, the second version (using mapN) took a little over 3 secs. because all 3 functions started executing together and in fact, in this particular run, the function getC started executing before the function getB.

Leave a comment