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 :
- Identity Monad
- 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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 val and 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 map and 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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: