One of the most frequently used operations in functional programming is the map operation. The map operation takes a collection of values, performs some transformation on each element, and creates a new collection containing those new elements. The elements in the new collection can be the same type as the elements in the original collection, or they may be of a different type. It just depends on the transformation.
Sequential Collections
Let’s look at a couple of examples. First, say that you have a list of integers, and you need the squares of those integers:
You can define the function f to calculate the square of a number. Then you can call the map
method to apply f to each member in the input list and create a new list containing the squares of the items in the original list. In functional programming parlance, we say that you map f over the list to produce the new list. The code looks like this:
val f = (n: Int) => n * n val squares = List(2,4,6,8) map f // squares: List[Int] = List(4, 16, 36, 64)
Of course, you can also define the mapping function inline:
val squares = List(2,4,6,8) map { n => n * n } // squares: List[Int] = List(4, 16, 36, 64)
Second, suppose you need to compute the length of each string in a list:
Similar to the last example, just define a function to calculate the length of a string, and map it over the list of strings with the map
method:
val lengths = List("five", "six", "pick up", "sticks") map { _.length } // lengths: List[Int] = List(4, 3, 7, 6)
Notice two things:
- In both examples, the output list maintains the order of the elements in the input list. This happens because a list is a sequential collection. The same is also true for an array and a sequence. In contrast, as we will see in a moment, order is not maintained when mapping over a non-sequential collection like a set.
- Whereas the type of both the input list and the output list is the same in the first example—they both contain integers—in the second example, the type of the elements in the output list (integers) is different from the type of the elements of the input list (strings). This happens frequently in functional programming. It is not uncommon to move a collection of inputs through a series of transformations, changing the type with each transformation.
Sets
The Set
class(es) also defines a map
method. It behaves essentially the same as the map
methods for lists, arrays, or sequences, but there are some minor differences. Sets are not sequential collections, so the order of their elements is not guaranteed. In other words, if you iterate through the input set and get the elements out in a certain order, after you map over it to get the output set, iterating over the elements of the output set may not yield the same order as the counterpart elements in the input set. Furthermore, by definition, sets cannot contain duplicates. So then, what happens if you map a function over the input set that produces duplicate output values? Let’s see:
val lengths = Set("four", "five", "six", "seven") map { _.length } // lengths: scala.collection.immutable.Set[Int] = Set(4, 3, 5)
That makes sense. Duplicates are dropped. Well, now you know. And knowing is half the battle. (What’s the other half, though? They never did tell us that!)
Strings
Finally, you can map over strings as well. Mapping over strings operates on each character in the string:
val allcaps = "sesquipedalian" map { _.toUpper } // allcaps: String = SESQUIPEDALIAN
If you need to transform the characters into values of another type, map
can take a function that transforms characters into a different type:
val allcaps = "sesquipedalian".toSeq map { _.toInt } // allcaps: Seq[Int] = Vector(115, 101, 115, ... )