Through this blog, I would like to throw some light on the two most basic and most commonly used operations, map() and flatmap(), for collections in Scala. Since scala follows most aspects of functional programming hence map in scala might seem somewhat similar to Relation and Mapping in mathematics. As we know that in mathematics a function defined from a set A to set B is written as function f: A => B and read as f is a function such that it maps A to B, given that every element in A maps to at least 1 element in B but vice-versa is not necessary. Let’s see an example to understand this :-

Consider two sets, A = {-2, -1, 0, 1, 2} and B = {0.5, 1, 1.5, 2.5, 4, 4.5, 5, 5.5} and a function          f: A => B

y = x ^ 2 + 0.5;  x is an element from set A and y corresponds to an element from set B, now we see that function f is applied to every element of set A but the result could be a subset of set B also.

So from the above text, we can draw the analogy that sets A and B can be seen as any collection in programming paradigm. Now what is “f”, so “f” could be seen as a function that takes an element from A and returns an element that exists in B, the point here to note is that, as scala promotes immutability whenever we apply map (or any other transformer) on some collection of type A, it returns a new collection of the same type with elements of type B. It would be helpful to understand it from the snippet below.

val result: List[B] = List[A].map(f: A => B)

So when a map operation is applied on a collection (here a List) of type A, with passing f as its argument it applies that function to every element of List of type A returns a new collection (again a List) of type B.

The code snippet below might not be exactly same as the internal working of  map function but it would greatly help to understand and visualize the working of map, notice the argument of the method customMap() is a lambda expression that takes an argument of type T and returns an argument of type U, hence we can say that map function is a transformer function.

case class CustomMap[T, U](list: List[T]) {
  def customMap(f: T => U): List[U] = {
    val initialList = List.empty[U]

    def customMapHelper(in: List[T], out: List[U]): List[U] = {
      in match {
        case Nil => out
        case head :: Nil => f(head) :: out
        case head :: tail => customMapHelper(tail, f(head) :: out)
      }
    }
    customMapHelper(list, initialList) reverse
  }
}

flatMap()

Now, let’s move on to flatMap(), so as the name signifies flatMap() flatten the hierarchy level by one level each time it is applied. A flatMap method also takes a function but, the function in this case returns a sequence. The flatMap() operation flattens the result in the resultant list, it applies that function to each element so, for instance, we have a list of list of integers like the list of integers in below code snippet, now let’s see what happens when we apply map() and flatMap() operations……

// List of Integers
val integersList: List[Int] = List(1, 2, 3, 4)

//function, precisely the simple lambda
val addSubUnity = (x: Int) => List(x - 1, x + 1)

//Code generated by scala REPL
scala> listToBeFlatten.flatMap(addSubUnity)
res5: List[Int] = List(0, 2, 1, 3, 2, 4, 3, 5)

scala> listToBeFlatten.map(addSubUnity)
res6: List[List[Int]] = List(List(0, 2), List(1, 3), List(2, 4), List(3, 5))

Above code is generated by scala REPL. Notice, the difference between the result when map() and flatMap() applied to the list. We can clearly see the flatMap operation combined the result in one single list containing the elements but map() produced a List of List which might something we don’t want, hence the usage of both is largely dependent upon the use case, below is the class CustomMap with added method customFlatMap()…..

case class CustomMap[T, U](list: List[T]) {
  def customMap(f: T => U): List[U] = {
    val initialList = List.empty[U]

    def customMapHelper(in: List[T], out: List[U]): List[U] = {
      in match {
        case Nil => out
        case head :: Nil => f(head) :: out
        case head :: tail => customMapHelper(tail, f(head) :: out)
      }
    }
    customMapHelper(list, initialList) reverse
  }

  def customFlatMap(f: T => List[U]): List[U] = {
    val initialList = List.empty[U]
    def customFlatMapHelper(in: List[T], out: List[U]): List[U] = {
      in match {
        case Nil => out
        case head :: Nil => out ::: f(head)
        case head :: tail => customFlatMapHelper(tail, out ::: f(head))
      }
    }
    customFlatMapHelper(list, initialList)
  }
}

Both the method above; customMap() and customFlatMap() doesn’t depict the actual implementation of map() and flatMap() respectively, but they are just for us to better understand the working of both of them. As a functional programmer we get the essence of mathematics in most of the concepts and yes they are correlated and they can help us to better understand the concepts. For the code snippet above you may also look into my GitHub repository here is the link map and flatMap for collections in scala

So that’s all for this time, in my next blog I would discuss how we can better use these methods when we have to deal with Futures and Options. Till then happy coding.


knoldus-advt-sticker


 

One comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s