Monad In Scala


functional programming, monad, scala

Monad In Scala


Daniel Shin - October 13, 2015

During last few months, I’ve learned a bit of functional programming in Scala. To document what I’ve learned so far, there will be a few posts regarding various functional concepts mostly around typeclasses and scalaz library. These posts are purely for self-documenting purposes and it lacks proper explanation on various concepts. Unlike other posts, this post won’t be very useful to anyone since trying to explain even just the basics of these typeclasses can easily get out of hand so I didn’t bother. Also I put more emphasis on the practicality rather than theoretical correctness.

To understand Monad, we start our journey with what typeclass is.

Typeclass

Typeclass, which originates from Haskell, is nothing like class. It is an interface where a type can conform to. It is implemented with trait in scala.

Typeclass requires you to define a certain primitive methods from which it derives other useful methods which you can use.

All that sounds very abstract because they are. It is an abstraction over types that share common patterns. Some of the examples of typeclass are Functor, Applicative, Monad etc.

Typeclass exists for one primary reason. To enable better code reuse through abstraction.

Any type that implements primitive methods required by a given typeclass, becomes an instance of that typeclass (or more mathematically correct way to say is that the given type forms a given typeclass).

Like learning any abstract concept, iterating definitions lead nowhere. We need concrete examples to understand these, otherwise, abstract nonsense.

We start with Functor.

Functor

Functor is a typeclass which requires primitive method map, which should be familiar to most if not all.

Functor is hardly useful by itself since it doesn’t do much. However, it forms the base class from which the other many useful typeclasses extend from.

Functor lets you operate on values within a context.

By context, we mean something that adds additional meanings to the type by wrapping the type within it.

Think how Option, which is an instance of Functor (i.e it implements map method) gives an additional meaning (context) to the type it wraps. Option[Int] is an Int that may or may not be present. It gives a context (of its existentiality) over the type that it wraps within.

Apply

Apply is a typeclass which requires primitive method ap. It extends Functor and therefore has access to all the methods in Functor. This is where things start getting interesting.

Here is the signature of ap.

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

ap isn’t much different from map other than the fact that its function is wrapped in the same context as the values it operate on where F is the context.

So using Option example, our function can be wrapped inside an Option context.

Why is this useful? Using ap method, we can achieve something like this.

def map2[A, B, Z](fa: F[A], fb: F[B])(f: F[(A, B) => Z]): F[Z] =
  ap(fb)(ap(fa)(map(f)(f => (a: A) => (b:B) => f(a, b))))

This is map2 implementation from Scalaz. It looks very scary but all that we need to understand is that ap allows our operations on values within context to be nested. This is possible because as said, ap takes in a function that is within the context.

Most commonly, you will use |@| operator in place of ap method, which is one of the derived methods from ap.

You can do things like:

(Option(1) |@| Option(2) |@| Option(3)) { (one, two, three) one + two + three } // 6

It allows to operate values within the same context all at the same time. Apply shines particularly when the context is Future where it enables the concurrent execution of all Future context-bound values.

Applicative

Applicative is a typeclass which requires primitive method pure. It extends Apply (therefore, also Functor).

pure is also known as unit, point, etc. What it does is simple. It wraps a given value within its context.

def pure[A](x: A): F[A]

Applicative doesn’t do much that Apply can’t, other than providing pure method, which is useful whenever you need to wrap some value within the context like:

Apply[Option].pure(1) // Option[Int] // which is equivalent to
Option(1)

To me, Applicative is significant not because of what it provides but rather how it bridges us to a realm of Monad by providing pure method.

As with, Apply, Applicative is useful when you operate on values within the same context, which are independent to each other, therefore allowing us to operate on all of them concurrently.

Monad

Monad is a typeclass which requires primitive method flatMap (or sometimes, instead, flatten). It extends Applicative (therefore also, Apply and Functor).

Monad is a very useful concept. It allows us to operate on values within the context that are possibly dependent on the results of each other.

Note how operations in Monad can depend on the previous operation’s result unlike Applicative where all operations must be independently executed.

To understand how this is achieved, let’s look at the type signature of primitive method that it requires, flatMap:

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

flatMap takes a function that takes A and returns B with context F and returns B with context F.

Unlike map where with function A => F[B] would inevitably return F[F[B]], flatMap instead returns F[B]. Its nested contexts F[F[_]] is flattened to a single context F[_], therefore the name flatMap where it map the function to the value within context and then flattens the results F[F[B]] => F[B].

This is immensely useful since now it means we can do nest one Monadic operation within another Monadic operation, which lets the nested operation to access its previous operation’s result through A.

Here is an example with Option:

Option(1).flatMap(one => if (one == 1) Option(10) else Option.empty[Int]  ) // Option(10)

It’s a stupid example but it illustrates the point nicely. Our operation of returning either Option(10) or Option.empty[Int] depend on the previous result of operation (which is really just the value of a value in the context since Option() simply wraps the value within Option context).

Monad allows us to chain together operations in a monadic context.

Here is less stupid example using Future, which is yet another Monad instance:

def fetchId: Future[Int] = ???
def fetchName(id: Int): Future[String] = ???
def fetchAddress(id: Int, name: String): Future[Address] = ???

fetchId.flatMap(id => 
  fetchName(id).flatMap(name => 
    fetchAddress(id, name)
  )
) // Future[Address]

Note how each operation is nested to depend on the result of previous operations all within the context of Future, without having to unwrap that context.

That’s Monad.

Let’s see how all these things connect.

Functor allows us to operate on values within context with function that produces another value.

Applicative allows us to operate on values within context with function within context that produces another value. (Often Apply is merged into Applicative as a single concept.)

Monad allows us to operate on values within context with function that produces another value within context.

context seems to be a recurring word. What is context?

context is a type that wraps another type (and thus give the value an additional context / meaning).

A type that can wrap another type is called a type constructor or higher-kinded type. As for what kind is, refer to [wiki](https://en.wikipedia.org/wiki/Kind_(type_theory)). It is basically a type for a type.

So context is a type constructor and what we mean when we say type A is an instance Monad, what we really mean is that type constructor A defines primitive methods required by typeclass Monad.

Why do we bother making our type A making an instance of Monad when we can still use method like flatMap without explicitly making it an instance?

Because Monad typeclass provides myriads of other methods that derive from our primitive method flatMap. We get all of these methods for free without ever implementing them just by making our type A conform to Monad. This facilitates DRY principle and leads to better code reuse.

For example, by simply implementing a single method flatMap on A, we get all primitive methods and its derived methods from Functor, Apply, and Applicative plus the derived methods directly from flatMap.

Takeaway

Monad lets us operate on values within context and chain such monadic operations in a dependent way without unwrapping the context.