Connecting...

W1siziisimnvbxbpbgvkx3rozw1lx2fzc2v0cy9zawduawz5lxrly2hub2xvz3kvanbnl2jhbm5lci1kzwzhdwx0lmpwzyjdxq

ScalaFP: Firsthand With Scala-Cats Monads – #1 by Harmeet Singh

W1siziisijiwmtgvmdcvmtgvmdkvntivmzivmzq3l2jvb2tzlwjvb2tzdg9yzs1ib29rlxjlywrpbmctmtu5nzexlmpwzwcixsxbinailcj0ahvtyiisijkwmhg5mdbcdtawm2uixv0

Today's learn is all about monads again, this time Harmeet Singh explores some of the monads which are frequently used in the applications but not available within the Scala predefined libraries. This article follows on from yesterdays blog post, if you missed it check it out here

 

'In the previous post, we had a look at the reasons behind the monads and how monads can help us design the programs in a functional style. Now we are going to explore some of the monads which are frequently used in the applications but not available within the Scala predefined libraries. We will be exploring the monads which are provided by the scala-cats functional library.

Scala-Cats library contains a lot of monads for some specific task and also contain a common trait for describing the structure of monad like map, flatMap and pure  methods as below:

trait Monad[F[_]] {
  def pure[A](a: A): F[A]

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

  def map[A, B](fa: F[A])(f: A => B): F[B] = {
    flatMap(fa)(a => pure(f(a)))
  }
}

Another thing, scala-cats monads syntax have come from various packages like, cats.syntax.functors, cats.syntax.applicative and cats.syntax.flatMap. The reason is that the Monads are functors as well as applicative. Cats handle flatMap as an independent trait (FlatMap) because in some of the scenarios we don’t want to handle the pure method which is also mentioned in the documentation like :
“We can implement a map or a flatMap that transforms the values of Map[K, ?], but we can’t implement pure(because we wouldn’t know what key to use when instantiating the new Map)”

Today we are going to explore only 2 of the monads from the Scala cats :

  1. Identity Monad
  2. Eval Monad
 

Identity Monad:

Identity monad is one of the most interesting monads in scala-cats, which is most commonly used and we will be discussing one of the scenarios today. The code definition of Identity monad is:

type Id[A] = A

Id contains nothing, but it is just an alias of normal value which wraps into some type called Id. But scala-cats Monads, Functors, and FlatMap allow us to call the map, flatMap and pure methods on Id type.

Let’s try to implement Id monad behaviors with the custom implementation below:

type Id[A] = A
def pure[A](value: A): A = value
def map[A, B](fa: Id[A])(f: A => B): Id[B] = f(fa)
def flatMap[A, B](fa: Id[A])(f: A => Id[B]): Id[B] = f(fa)

Now the question that comes to our mind is “What is the benefit of Id monad if it is just a  simple normal value? ”

Answer: Okay, First let’s have a look at the below example.


object IdExample1 extends App {

  def sumSquare[F[_]: Monad](a: F[Int], b: F[Int]): F[Int] = {
    a.flatMap(x => b.map(y => x*x + y*y))
  }

  import cats.instances.list._
  import cats.instances.option._

  val result1 = sumSquare(Option(2), Option(5))
  println(result1)

  val result2 = sumSquare(List(1, 2, 3), List(4, 5, 6))
  println(result2)
  
//  val result3 = sumSquare(2, 4) //Getting error
//  println(result3)
  
  val result4 = sumSquare(2: Id[Int], 4: Id[Int]) //Getting error
  println(result4)
}

In the above example, we are creating a generic method which accepts int of monad type and performs a square of the elements. We are performing the operation with Option and `List` monads and getting the successful result but while we are passing the normal integer value, we will get a compile-time error. In that case of reusability, scala-cats provides us an Identity (Id) monad for dealing with generics monad methods with simple or normal values. In the result4 which is declared in above example, we are converting our integer values into Id type and successfully performing the required operations.

This is one of the use cases for Id monad. The other use cases will depend on our requirements and scenarios.

 

Eval Monad:

In Scala, we are familiar with three types of evaluations which are called eager, lazy and always.

Eager == val  // (compute during load)
lazy == lazy val  // (compute on demand)
always == def  // (compute always on demand)
In scala-cats we have evaluation monad called Eval which has all three equivalent properties of the eager, lazy and always evaluations. The example of Eval as below:
object EvalExample1 extends App {
    
    val eager = Eval.now {
        println("Hey !! I am eager eval")
        "Hello Eval Eager"
    }
    
    val lazyEval = Eval.later {
        println("Hey !! I am lazy eval")
        "Hello Eval Lazy"
    }
    
    val always = Eval.always {
        println("Hey !! I am always eval")
        "Hello Eval Always"
    }
    
    println(s"Eval Eager: ${eager}")
    println(s"Eval Eager 1: ${eager.value}")
    println(s"Eval Eager 2: ${eager.value}")
    println(s"Eval Lazy: ${lazyEval}")
    println(s"Eval Lazy 1: ${lazyEval.value}")
    println(s"Eval Lazy 2: ${lazyEval.value}")
    println(s"Eval Always: ${always}")
    println(s"Eval Always 1: ${always.value}")
    println(s"Eval Always 2: ${always.value}")
}

/* OUTPUT
Hey !! I am eager eval
Eval Eager: Now(Hello Eval Eager)
Eval Eager 1: Hello Eval Eager
Eval Eager 2: Hello Eval Eager
Eval Lazy: cats.Later@61a485d2
Hey !! I am lazy eval
Eval Lazy 1: Hello Eval Lazy
Eval Lazy 2: Hello Eval Lazy
Eval Always: cats.Always@39fb3ab6
Hey !! I am always eval
Eval Always 1: Hello Eval Always
Hey !! I am always eval
Eval Always 2: Hello Eval Always
*/

In the above example, we are creating Eval.now, Eval.later and Eval.always which is same as scala val, lazy valand def. According to the example, First, while our EvalExample1 class is loaded into memory, the eager is initialized and the message is printed and the value in eager block memorized. Second, while we call “value” on lazy eval, at that time, lazy eval prints the message and memorizes the value and the Third one, always eval, while we call “value” the message is printed but the value is not memorized that means every time when we call message will print.

Now the next Question that pops up is “In Scala, we have val, lazy val and def for memorizing and on-demand functionality, then why would we require Eval monad separately?”

Answer: The answer to this is quite simple. As we know, Eval is a monad, that means we have methods like mapand flatMap for performing operations. In that case, whenever we need to perform some operation on our values in a composable manner, we are going to use Eval monad. Let’s take an example:

object EvalExample2 extends App {
    
    def twice(value: Int): Int = value * value
    
    def divideBy2(value: Int): Int = value / 2
    
    val value = 42
    val intermediateResult = twice(value)
    val result = divideBy2(intermediateResult)
    println(result)
    
    val result1 = Eval.now(42).map(twice).map(divideBy2)
    println(result1)
    println("Demand The Eval Results")
    println(result1.value)
}

/* OUTPUT
Without Eval Result: 882
Eval Declared only: cats.Eval$$anon$6@39fb3ab6
Demand The Eval Results
With Eval Result: 882
*/

In the above example, We need to declare some eager value and after that, we need to perform the twice operation on that value and the results are going to divide by 2.

Without Eval monad, we need to perform these simple business requirements with the imperative style which makes our code contain more boilerplate and these operations are performed on a load of EvalExample2 class into memory. But Eval monad provides us a more composable and functional way of performing our operations on the value which are always lazy. In other words, Eval operations are only performed when we demand the value otherwise not.

As in the example output, without eval monad, our operations are performed during the load example into memory. But eval operations are only performed when we call “value” on it. In such a scenario, we will have a requirement of Eval monad. As mentioned earlier, the other scenarios depend on your requirements and business logic.

In our further blogs, we will try to explore some of the other monads which are provided by scala-cats.

References:

  1. Scala With Cats By Noel Welsh and Dave Gurnel.'