Decomposing Composition
Tags: javascript, programming, tutorial, functional
Published: 2021-12-16 05:21:45

Functional libraries like Ramda.js are great, and give us some very powerful, useful, simple functionality. But they’re the kind of thing you might not know you need, unless you know you need them.

I’ve been reading (well, okay, _devouring) [Eric Elliott](https://medium.com/u/c359511de780?source=post_page-----d06df9c0b8a-----------------------------------)’s **Composing Software** book (and before that, the series of blog posts). Powerful read, easy to understand, lot of meat under that skin. But it’s easy to get lost in there. Trying to understand both the what and why of function composition (and later, object composition) can be challenging.

So I wanted to break it down into a step-by-step, and see if we can make more sense of this as an “evolution of the idea.”

## Defining the Problem

Let’s take an absurd example, reversing a string. It’s a lesson we see in all sorts of introductory lessons, and the steps are pretty easy to follow:

1. turn the string into an array of letters,
2. reverse the array of letters,
3. rejoin the reversed array back into a string,
4. return the reversed (transformed) string.

Easy to follow, and easy to write. A perfect introduction to methods of core objects in javascript.

## Step 1

```js
const reverseString = function reverseString(string){
  const stringArray = string.split('');
  const reversedStringArray = stringArray.reverse();
  const joinedReversedStringArray = reversedStringArray.join('');

  return joinedReversedStringArray;
}
```

So we take each step of the problem, and do that thing. Each step is assigned to a variable because we can, and that variable is passed to the next step as its input. Easy to read, but kind of wasteful.

Wasteful why? Because of _method chaining_. When we call `String.prototype.split()`, that returns an array, and we can chain directly onto that. The `Array.prototype.reverse()` acts on an array and modifies it in place, returning the same array, and `Array.prototype.join()` returns a string, which we are returning. So we can call each of those methods on their returned result, without needing the intermediary variables

## Step 2

```js
const reverseString = function reverseString(string){
  return string.split('').reverse().join('');
}
```

And that does all four steps in one line. Beauty! Note the order of the functions being called there — we `split` the string, we `reverse` the array, we `join` the array.

It is much shorter, and it reads very well. This is often the solution that we as mentors in online programming courses might point to as the cleanest and easiest solution, and it really works. And it _does_ get us closer to where I want us to be.

But this? This is about _functional composition_. We’ve got a ways to go yet, but we’re closer. Let’s look at another way of doing much the same thing, see if that helps.

## Pre-Step 3

While chaining is a great way to go, in terms of readability, it doesn’t really _compose_ well. We can’t build with chained methods like Lego blocks, snapping them together and rearranging as we like. To do that, we need to consider another way of passing data from one function to another.

The pattern of what we’re about to do, in a mathematical sense, might look more like this:

```js
// given functions f, g, and h, and a data point x:  
return f( g( h( x ) ) )
```

We are taking value `x`, pushing it into function `h` (getting “the `h` of `x`”), and then taking the returned value from that and pushing it into `g` (getting “the `g` of `h` of `x`”), and then taking the returned evaluation from that and pushing it into `f` (getting “the `f` of `g` of `h` of `x`”).

It makes sense, but it hurts to think in `f` and `g` and `h` and `x` hurt my little button-head. Let’s make it a bit more concrete.

```js
/***
 * for reference, this was the mathematical idea:
 *
 * return f(
 *         g(
 *           h(
 *             x  
 *           )
 *         )
 *       );
 ***/

// and the concrete example:
return join(
          reverse(
            split(
              string  
            )
          )
       );
```

So that is doing the same thing - it gets the "split of `string`", passes that to get "reverse of (split of `string`), then passes that out to get "join of reverse of split of `string`." Sounds silly worded that way, but it's part of the mental model. Our function is composed of these steps, in this order.


## Step 3

```js
// some utility functions, curried.
const splitOn = (splitString) =>
  (original) =>
    original.split(splitString);
    
const joinWith = (joinString) =>
  (original) =>
    original.join(joinString);

const reverse = (array) => [...array].reverse();


const reverseString = (string) => {
  // partially-applied instances of our utilities
  const split = splitOn('');
  const join = joinWith('')
  
  return join(
           reverse(
             split(
               string
             )
           )
         );
}
```

There is quite a bit more meat to this one, and it will require some explanation to grok fully what is going on.

First, before we do the `reverseString`, we want to turn those `Array` or `String` methods into composable functions. We’ll make some curried functions, because who doesn’t like abstraction?

* `splitOn` is an abstract wrapper for the `String.prototype.split` method, taking as its first parameter the string on which we’ll split.
* `joinWith` is an abstract wrapper for the `Array.protoype.join` method, taking as its first parameter the string we’ll use for our join.
* `reverse` doesn’t take any parameters, but it turns `Array.prototype.reverse` into a composable function in itself.

Now, within our `reverseString`, the first step is to _partially apply_ those two abstract functions. We tell `split` that it is a reference to `splitOn('')`, we tell `join` that it is a reference to `join.with('')`, and then we have all the parts we need to combine three functions into one call.

This is much better, as we can now see each function, and the order in which they are applied. But this reads a little bit differently than the original chained example. That one read in left-to-right order:

```js
// given string, call split, then call reverse, then call join  
return string.split('').reverse().join('');
```

In functional circles, this is considered “pipe” order. The term comes from the Unix/Linux world, and leads down a whole ‘nother rabbit hole.

Our latest code, rather than reading left-to-right, is processed inside-to-outside:

```js
return join(  
  reverse(  
    split(  
      string  
    )  
  )  
);
```

So if we read these in that same left-to-right order, `join`, `reverse`, `split`, we execute them exactly _backwards_ of that. This would be considered “composed” order, and now we’re about to venture into composed-function-land!

## Pre Step 4

This is where things start to get fun. First thing to remember is this: functions in javascript are just another kind of data (and thanks, [Dan Abramov](https://medium.com/u/a3a8af6addc1?source=post_page-----d06df9c0b8a-----------------------------------) for the mental models from [JustJavascript](https://justjavascript.com/)!). In javascript, we can pass ’em, we can store ’em in arrays or objects, we can manipulate them in fun and exciting ways… and we can combine ’em. And that’s just what we’ll do.

In this iteration, we are going to place all our functions in an array, and then we will simply ask that array to perform each function in turn on a given piece of data. The concept is easy to understand, but again — concrete examples are helpful.

## Step 4

```js
// again, the helpers:
const splitOn = (splitString) =>
  (original) =>
    original.split(splitString);
const joinWith= (joinString) =>
  (original) =>
    original.join(joinString);
const reverse = (array) => [...array].reverse();

// with those, we can write this:
const reverseString = (string) => {
  const instructions = [
    splitOn(''),
    reverse,
    joinWith('')
  ];
  
  // let's define our transforming variable
  let workingValue = string;
  
  for(let i=0; i<instructions.length; i++){
    // apply each function and transform our data.
    workingValue = instructions[i](workingValue)
  }

  return workingValue;
}
```

This is nicely abstracted — inside the `reverseString`, we simply create an array of instructions and then we process each one, passing the most recently transformed data in.

If that sounds like a sneaky way of saying we are reducing the array of instructions, you’re either paying attention or reading ahead. 😉

That is exactly where we are going. We are taking an array of instructions, using `workingValue` as the starting “accumulator” of that array, and reducing the `workingValue` to the final evaluation of each of those instructions, applying the `workingValue` each time. This is precisely what `Array.prototype.reduce` is for, and it works a treat. Let’s go there next!

## Step 5

```js
// I'll leave those helper methods as written.
// Imagine we've placed them in a util library.
import { splitOn, reverse, joinWith } from './utils/util.js';

const reverseString = (string) =>{
  const instructions = [
    splitOn(''),
    reverse,
    joinWith('')
  ];
  
  return instructions.reduce(
    (workingValue, instruction) => instruction(workingValue),
    // and use string as the initial workingValue
    string
  )
}
```

Here, we’ve taken that imperative `for` loop and made it a declarative `reduce` statement. We simply tell javascript "reduce the original `workingValue` by applying each `instruction` to it in turn." It is a much more structured way to code, and if we want, we can always add, alter, rearrange the `instructions` without breaking the way that `reduce` function call works. It simply sees instructions, and does instructions. Is a beautiful thing.

But it would be a colossal pain to have to write each function that way. The concept will be much the same any time we want to combine a number of functions — we write the instructions, then we transform some datapoint based on those instructions. Sounds like another candidate for abstraction.

## Pre Step 6

Given that we’re working with the instructions in first-to-last order, we’ll talk about writing a `pipe` function first. It’s an easy step from that to reduce, and in terms of how we think, pipe order may make more sense.

So what we want is a function that takes an array of functions, and applies them to a particular data point. Internally, we know it’ll be a reducer, but how might that look?

```js
const pipe = (...arrayOfInstructions) =>
  (value) =>
    arrayOfInstructions.reduce(
      (workingValue, instruction)=>instruction(workingValue), value
    );

// or, with shorter variable names:
const pipe = (...fns) => (x) => fns.reduce( (acc, fn)=>fn(acc), x)
```

Those two are exactly the same — the first simply has longer variable names to make it easier to see what’s happening.

So we’ve made a curryable function here. By partially applying it (passing in any number of functions), we get back a function that wants a value. When we give it a value, it will apply each of the instructions to that value in turn, transforming the value as it goes along. Each time, the latest iteration of that transformed value will be used for the next step, until we reach the end and return the final transformation.

How might that help us? Remember, we want `returnString` to be a function that takes a value. And we want to give it a series of instructions. So how’s this look?

```js
// again with our utility functions:
import { splitOn, reverse, joinWith } from './utils/util.js';
import { pipe } from './utils/pipe';

const reverseString = pipe(
  splitOn(''),
  reverse,
  joinWith('')
);
```

So we call in our utility functions, and we call in our `pipe` function, and then we’re ready to begin. We partially apply the instructions to `pipe`, which returns a function expecting a value — which is exactly what we want `reverseString` to be! Now, when we call `reverseString` with a string argument, it uses that string as the final argument to the reducer, runs each of those instructions, and gives us a return result!

Look closely, though: our `reverseString` is a function, **defined without a body**! I can't stress enough, this is weird. This is not what we're accustomed to when we write functions. We expect to write a function body, to arrange some instructions, to do some stuff - but that is all happening *for* us. The `pipe` function takes all the function references passed in above, and then returns a function... awaiting a value. We aren't writing a `reverseString` function, we're sitting in the `pipe` function's closure!

Remember above when I explained that we can look at `pipe` as similar to chained order? If you read the above `pipe` call, you can read it in the same order. But when we compose, it is the reverse of pipe — while we might read it left-to-right (or outermost to innermost), it should process from right to left. Let’s write a `compose` function, and compare it to `pipe`.

```js
// remember,this is our pipe function in the compact terms
const pipe = (...fns) =>
  (x) =>
    fns.reduce( (acc, fn) => fn(acc), x);
    
// compose is eerily similar - we just want to reverse-order
const compose = (...fns) =>
  (x) =>
    fns.reduceRight( (acc, fn) => fn(acc), x);
```

If you look at those two functions, the only difference between them is that `pipe` uses `fns.reduce()` while `compose` uses `fns.reduceRight()`. Otherwise, nothing has changed. We could test them easily, if we wanted:

```js
import { splitOn, reverse, joinWith } from './utils/util.js';
import { pipe, compose } from './utils/my_fp_lib.js';

const pipedReverseString = pipe(
  splitOn(''),
  reverse,
  joinWith('')
);

const composedReverseString = compose(
  joinWith(''),
  reverse,
  splitOn('')
);

// let's use them!
console.log(
  pipedReverseString('Hello World')===composedReverseString('Hello World')
);
// logs true
```

Note that this is hardly the best explanation or implementation of pipe and reduce. There are far better, more robust FP libraries out there doing a far better job of implementing this. But what I’m doing here is more about explaining the how of it, for some who might be intimidated by the whole idea of functional composition. It doesn’t have to be intimidating, really. When we break it down to smaller steps, we can see that we already know most of this — it is simply how we combine that knowledge together.

And when I wrote something similar to this some time back, the biggest critique I got was “what’s the point? I’m not gaining anything by writing little functions for every little detail!” There is some truth to that, for the person who made the comment. For me, having that `compose` functionality means that my more complex functions become testable and debuggable quickly and easily, my development becomes more about what I want to do and less about how I’ll do it, my thinking becomes more abstract.

For example, suppose we wanted to add some inline debugging to the pipe version of our `reverseString` function? We could easily add that, without breaking anything:

```js
import {splitOn, reverse, joinWith} from './utils/util.js';
import { pipe } from './utils/my_fp_lib.js';

// this would be a candidate for a useful function to add to util.js
const trace = (message) => {
  (value) => console.log(message, value);
  return value;
}
  
const reverseString = pipe(
  trace('Starting Value'),
  splitOn(''),
  trace('After split'),
  reverse,
  trace('After reverse'),
  joinWith('')
);

console.log(
  reverseString('Hello World')  
);
/***
 * logs out
 * Starting Value Hello World
 *
 * After split [
 *   'H', 'e', 'l', 'l',
 *   'o', ' ', 'W', 'o',
 *   'r', 'l', 'd'
 * ]
 *
 * After reverse [
 *  'd', 'l', 'r', 'o',
 *  'W', ' ', 'o', 'l',
 *  'l', 'e', 'H'
 * ]
 *
 * dlroW olleH
 ***/
```

The only thing we’ve changed here is that we’ve added a `trace` function, something we **_couldn’t_** do with a chained function call or a normal nested series of functions. This is one of the secret superpowers of composition — we can combine things easily that might not be easy or obvious otherwise.

**Edit**: There was a typo in the `trace` function - we want that to `return` the value, so we continue to pass it up (or down) the `pipe` (or `compose`).

## Recap

I hope this helped clear up some, for those (like me) who were initially confused looking at Eric’s `compose` and `pipe` functions. Not because they were poorly written at all, simply because I was still thinking in a linear style and these functions are next-level.

I wanted to take us from the 101-level javascript, and start looking at how we might easily take the knowledge we already have and turn it into something more. First, by exploring two different ways of doing the same things — chained methods or nested function calls. Each does similar things, but the mindset and reading-order behind both are a bit different. Both are equally valid, and both apply to functional composition.

If you got these concepts, you’re already well on your way down the functional programming rabbit-hole. Welcome to the madhouse, have a hat! If you didn’t quite get the concepts yet, it’s not a failing — these are deep and twisty applications of ideas. You get a hat anyway!