Monads: Functional Programming in Scala

Reading Time: 3 minutes

Introduction

Monads have a deep basis in the mathematical side of computer science, coming out of category theory. Monads are nothing more than a mechanism to sequence computations around values augmented with some additional feature. This section will therefore present a condensed and simplified take on monads.

As we said, a monad augments a value with some additional features. Such features are called effects. Some well-known effects are managing the nullability of a variable or managing the asynchronicity of its computation. In Scala, the corresponding monads to these effects are the Option[T] type and the Future[T] type.

As we can see, both types – Option and Future – define a type parameter. In fact, a monad adds an effect to a value wrapping it around a context. In Scala, one way to implement a monad is to use a parametric class on the type.

For example, let’s try to add to values of a generic type T the effect of laziness using monads. Hence, we define the Lazy[A] class as:

class Lazy[+A](value: => A) {
  private lazy val internal: A = value
}

Unit Function

Monads must provide a function that allows wrapping a generic value with the monad’s context. These functions are known as unit. It’s said that the unit function lifts the value in the monadic context. In Scala, we can use the apply method of a companion object to implement the unit function:

object Lazy {
  def apply[A](value: => A): Lazy[A] = new Lazy(value)
}

In our example, the use of the unit function allows us to add the effect of the lazy initialization to a value, wrapping it inside the Lazy context.The below code doesn’t print anything once executed because its execution’s laziness is lifted to monadic value.

val lazyInt: Lazy[Int] = Lazy {
  println("Respone 100")
  100
}

Flatmap Function

We need a mechanism to sequence computations over a value wrapped inside a monad. To overcome this problem, monads must provide the flatMap function. This function takes as input another function from the value of the type wrapped by the monad to the same monad applied to another type:

def flatMap[B](f: (=> A) => Lazy[B]): Lazy[B] = f(internal)

It transforms the value inside a monad into another value without performing any extraction to make it simpler. Hence, if we need to transform the lazyInt value into a String, we can use the flatMap function:

val lazyString42: Lazy[String] = lazyInt.flatMap { intValue =>
  Lazy(intValue.toString)
}

Once again, no string will be printed to the standard output because of all the computation’s laziness.

Monads’ Laws

The three monad laws are:

  • Left identity
  • Right identity
  • Associativity

Monads and their laws define a design pattern from a programming perspective, a truly reusable code resolving a generic problem.

Left Identity

The first of the three laws, called “left identity”, says that applying a function f using the flatMap function to a value x lifted by the unit function is equivalent to applying the function f directly to the value x:

def unit[A](a: => A): F[A]
flatMap(x)(unit) == x
flatMap(unit(y))(f) == f(y)

Hence, we can substitute flatMap(f) with f(y), so the property holds by definition.

Right Identity

The second monadic law is called “right identity”. It states that application of the flatMap function using the unit function as the function f results in the original monadic value:

x.flatMap(y => Monad.unit(y)) = x
Lazy(x).flatMap(y => Lazy(y)) == Lazy(x)

As we can substitute the term flatMap(y => Lazy(y)) with the result of the application, Lazy(x), the property holds by definition.

Associativity

The last of the three monadic laws is the hardest to deal with and is called “associativity”. This law says that applying two functions and g to a monad value using a sequence of flatMap calls is equivalent to applying g to the result of the application of the flatMap function using as the parameter:

case class Order(item: Item, quantity: Int)
case class Item(name: String, price: Double)

val genOrder: Gen[Order] = for {
  name <- Gen.stringN(3)	
  price <- Gen.uniform.map(_ * 10)	
  quantity <- Gen.choose(1,100)	
} yield Order(Item(name, price), quantity)

Conclusion

In this blog, we introduced monads in scala. We began by giving a simple definition of monads, and then we introduced the minimum set of functions that a monad must implement: the unit and the flatMap.

Finally, monads are a fascinating and useful concept that pervades many types in the Scala standard library. OptionFutureEither, and more or less all the collection types such as ListTree, and Map, to name a few, are monads.

Written by 

Akash Kumar is a Software Intern at Knoldus Software LLP. He has done B.tech from Abdul Kalam Technical University. He is majorly focused on Scala and Angular. On the personnel side, he loves to play Cricket and online video games.

Leave a Reply