Forever Functional: Of Maps And Pipes, Chains And Nests, And More
Published: 2024-05-30 09:14:55


by [Federico Kereki](https://blog.openreplay.com/authors/federico-kereki)




<blockquote><em>
Can we combine functions by using mapping? What about this in relation to function composition and pipelining? And what does it have to do with chaining and nesting? This article will reply to all those questions, showing you how to join functions together for clearer, more expressive code.
</em></blockquote>




<div style="background-color:#efefef; border-radius:8px; padding:10px; display:block;">
<hr/>
<h3><em>Session Replay for Developers</em></h3> 
<p><em>Uncover frustrations, understand bugs and fix slowdowns like never before with <strong><a href="https://github.com/openreplay/openreplay" target="_blank">OpenReplay</a></strong> — an open-source session replay suite for developers. It can be <strong>self-hosted</strong> in minutes, giving you complete control over your customer data.</em></p>
<img alt="OpenReplay" style="margin-top:5px; margin-bottom:5px;" width="768" height="400" src="https://raw.githubusercontent.com/openreplay/openreplay/main/static/openreplay-git-hero.svg" class="astro-UXNKDZ4E" loading="lazy" decoding="async">
<p><em>Happy debugging! <a href="https://openreplay.com" target="_blank">Try using OpenReplay today.</a></em><p>
<hr/>
</div>




The array [`.map(...)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) method is very powerful and lets us write more understandable code. Let's ask a theoretical question and see where it takes us. Could we apply `map()` to other types and not only to arrays? Why not? 

In this article we'll start by extending types to allow mapping, and then get into pipelines, chaining, nesting, and several other functional programming concepts; just wait!

## Mapping for other types

First, we could add it to basic types, such as `boolean`s, `number`s, `bigInt`s, and `string`s. On the other hand, it seems that adding mapping to `null`, `undefined`, and `symbol` wouldn't be that great, so we'd skip that. And, obviously, adding a `map()` method to objects (or to class definitions) is straightforward; no mystery there.

In all cases code would be something like this -- we'll only show two definitions to avoid repetition:

```javascript
Boolean.prototype.map = 
  function(fn) { 
    return Boolean(fn(this));
  };

Number.prototype.map = 
  function(fn) {
    return Number(fn(this));
  };
```

We can now write things like the following two examples, and let's use [pointfree style](https://medium.com/stackanatomy/forever-functional-pointfree-style-programming-c3877c229ceb) for a more "functional-programming-ish" style:

```javascript
const not = (x) => -x;
let someFlag = true;
someFlag.map(not); // false

const negate = (z) => -z;
let aNumber = 22;
aNumber.map(negate); // -22
```

This isn't a real win; we could have written `not(someFlag)` and `negate(aNumber)` equally well. There's a slim advantage: we can now clearly chain sequences of operations. Let's go with a simple example.

```javascript
const add1 = z => z+1;
const times10 = z => 10 * z;

aNumber.map(add1).map(times10).map(negate); // -230
```

This starts to look more interesting; the alternative way, `negate(times10(add1(aNumber)))` wouldn't have been equally clear. We'll look into this a bit more, but first, we must answer the question: what about using `map()` with functions?


<CTA_Middle_Programming />



## Mapping functions

Functions are (trivially!) essential, and we should investigate what mapping would mean for them. The logical definition would be as follows; if we have `f1` and `f2` functions and we write `f1.map(f2)`, that should return a function that first runs `f1` and then applies `f2` to whatever `f1` returned.

```javascript
Function.prototype.map = function (fn) {
  return (x) => fn(this(x));
};

add1.map(times10)(3); // 40
```

So, `x => f1.map(f2)(x)` is equivalent to `x => f2(f1(x))`, and in general `x => f1.map(f2).map(f3)...map(fn)(x)` would be the same as `x => fn(...f3(f2(f1(x))))`; this is a well known functional programming concept that we should look into!

## Maps as composition?

The kind of expression we wrote in the previous paragraph, when you have a sequence of functions call in which the output of one function is the input of the next one, is called *composition*. Formally, in mathematical terms, instead of `fn(...f3(f2(f1(x))))` you could also write `(fn∘...∘f3∘f2∘f1)(x)`. If you wish, you can read `∘` as *after*, so `f2∘f1` is `f2` after `f1`, etc.

If we wish, we can write a general `compose(...)` function; it's not hard - can you see why and how works the code below?

```javascript
function compose(...fns) {
  return fns.reduceRight(
    (f, g) =>
      (...args) =>
        g(f(...args)),
  );
}
```

So, instead of writing `x => f1.map(f2).map(f3)(x)` you could go with `x => compose(f3, f2, f1)(x)` - but that goes a bit against the grain; the order of functions is reversed! There's another concept we should look into: pipelining!

## Maps as pipes

In Unix and Linux, the idea of running a first command, passing its output as input to a second command, whose output will be input for yet a third command, etc., is called a *pipeline*. This is a basic philosophy of Unix, and Doug McIlroy, the creator of the pipelining concept, explained:

* Make each program do one thing well. To do a new job, build afresh rather than complicate old programs by adding new features.
* Expect the output of every program to become the input to another, so far unknown program.

To give an elementary example (from my [Mastering JavaScript Functional Programming](https://www.amazon.com/Mastering-JavaScript-Functional-Programming-maintainable/dp/1804610135) book) if you wanted to count how many LibreOffice text documents there are in a directory, the following pipeline would do:

```
ls -1 | grep "odt$" | wc -l
```

How does this work?
* The first command lists all the files in a single column, one filename per line.
* The second command gets the list of files and filters (let pass) only those that end with "odt", the standard file extension for LibreOffice Writer documents.
* The third command gets the filtered list and counts how many lines there are in it.

It seems then that something like `f1.map(f2).map(f3)` is like a Unix pipeline, sort of `f1 | f2 | f3` thing. It so happens that there is [a proposal](https://github.com/tc39/proposal-pipeline-operator) to include such an operator in JavaScript, written `|>` as in `f1 |> f2 |> f3`, but it isn't accepted yet.

Given that pipelining is just like composition but working "the other way", we can quickly whip up the following:

```javascript
function pipeline(...fns) {
  return fns.reduce(
    (result, f) =>
      (...args) =>
        f(result(...args)),
  );
}
```

In that case, we could write any of the following versions -- though the last two won't work until the pipeline operator proposal is accepted:

```javascript
aNumber.map(add1).map(times10).map(negate);
aNumber.map(add1.map(times10).map(negate));

pipeline(add1, times10, negate)(aNumber);
aNumber.map(pipeline(add1, times10, negate));

(add1 |> times10 |> negate)(aNumber); 
aNumber.map(add1 |> times10 |> negate);
```

What do you think? Personally, I believe that this "*chaining style*" for successive functions is clearer than the alternative "*nesting style*" (based on composing) which adds the extra complexity of listing the functions in reverse order. The piping way, the [fluent interface pattern](https://en.wikipedia.org/wiki/Fluent_interface), is more understandable, and adding our `map/pipe` method allows us to easily use it.

## Conclusion

We started this article by wondering about extending the usage of `map()` to other data types, went from there to using it on functions, and ended up considering typical functional programming topics such as composition and pipelining. JavaScript can be considered a multi-paradigm language, and the examples we've seen here confirm that... and also allow you greater freedom to achieve the maximum clarity in your code!