Map as a grep

Elizabeth Mattijsen - Oct 27 '22 - - Dev Community

This is part 4 of the "Don't fear the grepper!" series.

Expanding the notion of grep

The grep method allows one to filter a list of values: either a value gets through, or it does not. In this way, the functionality of grep is rather limited.

What if you would not only like to filter out unwanted values, but also would like to adapt an acceptable value on the fly? Or turn a single value into multiple values? With the map method, you can!

The map method provides a superset of the functionality of grep. But you can also use it as grep with a block to do the filtering (instead of using something to smart-match against).

In many ways, understanding map well, will make understanding a lot of aspects of the Raku Programming Language a lot easier! So let's focus on that a bit.

Using map as grep

Let's go back to the original example of grep in this series, this time using the topic variable in a block:

say (1..10).grep({ $_ %% 2 }); # (2 4 6 8 10)
Enter fullscreen mode Exit fullscreen mode

You could write this using map as:

say (1..10).map({
    if $_ %% 2 {   # is it divisible by 2?
        $_         # yes, accept
    }
    else {         # not divisible by 2
        Empty      # don't accept
    }
}); # (2 4 6 8 10)
Enter fullscreen mode Exit fullscreen mode

A little verbose, indeed. But please hang in there!

As we've seen before with blocks, the last value evaluated will be returned by the block. So if the condition $_ %% 2 is True, it will return $_, else it will return Empty. Now, what is Empty, you might ask?

Slipping away

The Raku Programming Language has a number of special values that have special meanings. One of them is Empty, a Slip without any elements. A Slip is a special type of list that automatically flattens in any outer list-like structure.

So if the condition $_ %% 2 is not true, an empty Slip will be put into the result of the map, and thus not produce a value. So effectively removing the current value of $_ from the list.

But you are not limited to slipping an empty list! You can use the slip subroutine to create a Slip with any values you give it. For example:

say (1..10).map({
    if $_ %% 2 {                 # divisible by 2?
        slip($_ - 0.5, $_ + 0.5) # produce values around it
    }
    else {                       # not divisibly by 2
        Empty                    # don't accept
    }
}); # (1.5 2.5 3.5 4.5 5.5 6.5 7.5 8.5 9.5 10.5)
Enter fullscreen mode Exit fullscreen mode

In this example the even numbers are replaced by two values, one 0.5 less and one 0.5 more than the topic.

Consequences of not slipping

So, what would happen if you do not slip these values, but just produce the two values?

say (1..10).map({
    if $_ %% 2 {           # divisible by 2?
        $_ - 0.5, $_ + 0.5 # produce list with values
    }
    else {                 # not divisible by 2
        Empty              # don't accept
    }
}); # ((1.5 2.5) (3.5 4.5) (5.5 6.5) (7.5 8.5) (9.5 10.5))
Enter fullscreen mode Exit fullscreen mode

You would get a 5-element list of 2-element lists. Instead of a single 10-element list. In other words, the produced lists do not get flattened.

Note that it was not necessary to put parentheses around the 2-element list. In Raku the comma (aka the , operator) creates lists, not parentheses!

If that is what you wanted, then be happy. In this blog post, it is not what we wanted, so we used a slip instead.

Automatic emptying

Using an if / else structure is still pretty verbose though, and yes this can be expressed in a shorter way:

say (1..10).map({
    if $_ %% 2 {
        slip($_ - 0.5, $_ + 0.5)
    }
}); # (1.5 2.5 3.5 4.5 5.5 6.5 7.5 8.5 9.5 10.5)
Enter fullscreen mode Exit fullscreen mode

What? You just removed the else? Yup! The reason this works, is that the value of a failed if (or elsif for that matter), is Empty. So you don't have to specify the else clause explicitly!

Still, it feels like this could still be shorter. And you'd be right! Since there is no else in the code anymore, you can use the "statement modifier" version of if (sometimes also referred to as "postfix if"):

say (1..10).map({
    slip($_ - .5, $_ + .5) if $_ %% 2
}); # (1.5 2.5 3.5 4.5 5.5 6.5 7.5 8.5 9.5 10.5)
Enter fullscreen mode Exit fullscreen mode

That reduces on curly braces, but is also known to not improve readability for some people. To yours truly, it feels very natural language-like. Like everything in life, YMMV!

Helicopter view

Whenever you're coding something to reach a certain goal, you should always think about the way you got to a solution. And think about whether there couldn't be an easier way to reach the same result.

This is not just about efficiency of code execution, but also about whether your code shows the intent clearly, or not. And clear intent will make it easier for you, or anybody else, now or in the future, to grok the source code.

In this case, producing the values 1.5 through 10.5 with an interval of 1, can be done much easier, because with map we can also change values unconditionally!

say (1..10).map({ $_ + .5 });
# (1.5 2.5 3.5 4.5 5.5 6.5 7.5 8.5 9.5 10.5)
Enter fullscreen mode Exit fullscreen mode

Much simpler, isn't it? It is even so simple that we can use "whatever-currying"!

say (1..10).map(* + .5);
# (1.5 2.5 3.5 4.5 5.5 6.5 7.5 8.5 9.5 10.5)
Enter fullscreen mode Exit fullscreen mode

And there you have a hopefully easy way to grok the use of the map method in a nutshell!

Conclusion

This concludes the fourth part of the series, this time introducing the map method. And also introducing the concept of Empty, and Slip in general. And also showing that you can have a statement modifier version of if if you don't need an else or an elsif.!

Questions and comments are always welcome. You can also drop into the #raku-beginner channel on Libera.chat, or on Discord if you'd like to have more immediate feedback.

I hope you liked it! Thanks again for reading all the way to the end.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player