Tag: scala

  • map is a just a loop

    I first encountered it while learning Scala. Having written mostly procedural code up until then, functional programming was certainly a challenge. I eventually got used to reading and writing immutable code, although I feel debugging will always be an issue.

    Obi-Wan Kenobi trying to debug a Scala program.

    Since then, I have seen immutable patterns creep in and lately overtaking parts of code in many other languages. Java, Kotlin, JavaScript are some of them. Specifically, the map and filter functions seem to be very popular. In this post I would like to break down the first of these two easily misunderstood functions with some examples.

    map is just a loop

    Say we have a list

    // Create a test list of integers
    const numbers = [1, 2, 3, 4, 5];

    We want to add 2 to each element of this list, thus creating a list with contents [3, 4, 5, 6, 7].

    One way to do that, is using a good old for loop:

    const updatedNumbers = [];
    
    // Use a for loop to add 2 to each number
    for (let i = 0; i < numbers.length; i++) {
        updatedNumbers.push(numbers[i] + 2);
    }

    This produces a new list with the expected contents of [3, 4, 5, 6, 7].

    But we can do the same thing using map.

    // Use map to add 2 to each number
    const updatedNumbers = numbers.map(num => num + 2);

    This also produces a new list with the expected contents of [3, 4, 5, 6, 7].

    A little boy with the caption "I don't understand any of this".

    So let’s break this down. We can think of map as just another function that operates on a list. Same way the .length function returns the length of a list, the .map function returns something about the list. In this case, a modified list. The num part inside the parenthesis signifies a single element of the loop so we get to define it. It can be anything, same way we usually use i in a for loop. We don’t need to pass the length of the list, the .map function knows that already. The code after the => sign is the content of our for loop. The whole thing can be thought of something like this:

    numbers.map(for num { num + 2 })

    For many people, especially ones that have grown up with linear programming, the for loop is easier to understand. Most languages still offer both ways to iterate over a list, so why would anyone want to switch to using .map?

    An obvious reason is that the for way is undeniably longer. We must establish a variable before the loop, and make sure we update it inside it. We also have to set the upper limit and step interval of our loop counter. But we don’t have to do any of that that with .map.

    A very skeptical Danny Pudi with the caption "That's cool, I guess".

    But using .map has added benefits. We can use maps consecutively. Picture this:

    const updatedNumbers = numbers.map(num => num + 2).map(num => num * 2).map(num => num - 2);

    Here we first go through the list adding 2 to each element, then multiplying each element by 2, and finally subtracting 2 from each element. Although trivial and largely unnecessary, this example shows how simple it is to chain manipulations to a list in a single command. The three for loops needed for the same result is clearly subpar.

    const updatedNumbers1 = [];
    for (let i = 0; i < numbers.length; i++) {
        updatedNumbers1.push(numbers[i] + 2);
    }
    const updatedNumbers2 = [];
    for (let i = 0; i < updatedNumbers1.length; i++) {
        updatedNumbers2.push(numbers[i] + 2);
    }
    const updatedNumbers3 = [];
    for (let i = 0; i < updatedNumbers2.length; i++) {
        updatedNumbers3.push(numbers[i] + 2);
    }

    So, there you have it. The map function can be scary at first sight but substituting it whenever you can will make your code cleaner and will eventually open the door to more streamlined development in the future.