Basic understanding of Monads, Monoids, and Functor

Reading Time: 4 minutes

Monoid is based on an associative function. Formally, a functor is a type F[A] with an operation
map with type (A => B) => F[B]. In functional programming one typically only deals with one category, the category of types. A functor is an interface with one method i.e a mapping of the category to category. Monads basically is a mechanism for sequencing computations. A monad is a way to wrap stuff, then operate on the wrapped stuff without unwrapping it.

Monoids

In the world of object-oriented programming and Scala, monoids are types that include a binary associative function and an identity element for this function. Examples of monoids include:

  • Integers, with the sum and zero.
  • Integers, with the product and one.
  • Strings, with the concatenation (“+”) and the empty string.
  • Lists, with the concatenation of lists (“++”) and the empty list.
  • Positive numbers, with min and zero.

Why monoids are important?

Monoids are important because it gives a sequence of the elements of the type, we can combine them with the function in any order. This is because of the associativity of the function. Therefore, we can use the higher-order functions foldLeft, foldRight, fold, and aggregate 1 with any monoid. The result will be always the same.

Example

Let’s consider the algebra of string concatenation. We can add “food” + “shop” to get “foodshop”, and the empty string is an identity element for that operation. That is, if we say (s + “”) or (“” + s) , the result is always s . Furthermore, if we combine three strings by saying (r + s + t), the operation is associative —it doesn’t matter whether we parenthesize it ((r + s) + t) or (r + (s +t)) .

//We can express this with a Scala trait:
trait Monoid[A] {
def op(a1: A, a2: A): A
def zero: A
}

//An example instance of this trait is the String monoid:
val stringMonoid = new Monoid[String] {
def op(a1: String, a2: String) = a1 + a2
def zero = ""
}

//List concatenation also forms a monoid:
def listMonoid[A] = new Monoid[List[A]] {
def op(a1: List[A], a2: List[A]) = a1 ++ a2
def zero = Nil
}

Functors

We may define functions and classes that are parametric with respect to one or more types. This type of generalization can be pushed even forward.

Let us consider types as List[Int], Array[String], and Something[A] all containing different elements of a basic type (i.e., Int, String, and A) and implementing the function map. The function map given a function from the basic type to another (i.e., f: A=>B) transforms Something[A] into Something[B]. We say that such type is a functor.

When applying functors from a set A to a set B, we expect that if on the set A there is an identity i A and a function f A : (A, A) → A, and if on the set B there is an identity i B and a function f B : (B, B) → B, then

  • f (i A ) = i B ,
  • f ( f A (a, b)) = f B ( f (a), f (b)) for all a in A and b in B.

Examples of Functors

To understand the functor we do not have to think about traversing a list but think like transforming a list with every value inside it in a one go.

List(1, 2, 3).map(n => n + 1)
// res0: List[Int] = List(2, 3, 4)

We specify the funcঞon to apply, and the map ensures it is applied to every item. The values change but the structure of the list remains the same

Monads

Monads are one of the most common abstractions in Scala. Informally, a monad is anything with a constructor and a flatMap method.

Let us consider a list of integers, its decomposition into prime numbers, and then
the list of all primes. We will have something like:

List(2,10,15,45) => List(List(2),List(2,5),List(3,5),List(3,3,5))
                 => List(2,2,5,3,5,3,3,5)

In the above program, we see computation in terms of a function f that given an integer returns
the list of factors, and then a function flattens that given a list of lists of elements returns just a list of elements.

If we generalize these types we have that we can use any type M[A] instead of the original list of integers, and the output can be any type M[B]. We use two types A and B as the function f could transform the type of the elements.

Generalizing Monads

A monad is a generalization of this process considering the type M[A] (in our case a List[Int] in the above example), a function f from A to M[B] (in our case from Int to List[Int], and then the function flatten that given a M[M[B]] returns a M[B].

A monad can be seen as a combination of a functor (we have M[A] with a map that permits us to apply f to each element in M[A] and obtain M[M[B]]) and a monoid (that permits to flatten M[M[B]] into M[B] by means of the associative operator: e.g., concatenation in the case of lists).

Monads are defined in terms of two functions:

unit -> A function that given an element of type A returns an element of M[A].

flatMap -> A higher-order function that is given a M[A] and a function from A to
M[B] returns the data transformed into M[B].

Conclusion

Monads are useful in any language. Scala makes a lot of use of them to eliminate boilerplate. It is not a class or a trait, Monads are the concept. Scala Monad is an object that wraps another object in Scala. In Monads, the output of a calculation at any step is the input to other calculations, which run as a parent to the current step.

knoldus

Written by 

Gulshan Singh is a Software Consultant at Knoldus Inc. Software Developers are forever students. He is passionate about Programming.

Leave a Reply