Piping in JS, or what Elm taught me about partial application
Tags: javascript, elm, functional
Published: 2022-05-14 01:35:32

There has been some [recent talk](https://2ality.com/2022/01/pipe-operator.html) about the pipe operator coming to JS. I'm excited about this proposal but only now that I've struggled a bit learning functional patterns in Elm.

## What is a pipe operator?

A pipe operator "pipes" the output of one function into another.

So instead of writing

```js
const result = c(b(a(x)));
```

Or, as I prefer, writing:

```js
const one = a(x);
const two = b(one);
const result = c(two);
```

We could write:

```js
const result = a(x) |> b |> c;
```

JavaScript has something similar with chaining methods like `.map()`, `.filter()`, and `.reduce()`.

For that reason, I'll be using `.map()` as a stand in for exploring piping in JS and what I learned from Elm.

## Mapping in JS and Elm

Let's start with a basic `.map()` example:

```js
const square = (n) => n ** 2;
console.log([1, 2, 3].map(square));
// [1, 4, 9]
```

What this does is apply the `square(n)` function to every item in the array, and returns a new array with those squared values.

This is similar to the way things are done in Elm:

```elm
List.map square [1, 2, 3]
```

There is another way to write our code above in JS using an anonymous arrow function:

```js
console.log([1, 2, 3].map(n => square(n)));
```

At first, these two may seem similar, but they're slightly different.

The `.map()` syntax is like this:

```js
Array.map(<function>)
```

In the first way, we're saying apply the `square(n)` function to every item in the array.

The second way, we're saying apply this anonymous `<function>` which returns the result of the `square(n)` function to every item in the array.

The first syntax is common in functional languages; the second is not. We'll explore why in the next section.

## Partial application

Before getting right into partial application, let's create another function, this time for multiplying:

```js
const multiply = (a, b) => a * b;
```

Unlike out `square(n)` function, this function takes _two_ parameters.

Let's try to multiply our array by 10. Using the first syntax, it would look like this:

```js
console.log([1, 2, 3].map(multiply(10)));
// TypeError: NaN is not a function
```
That's frustrating! Because `multiply()` takes two arguments, we can't use that first syntax.

We can. however, use the second style syntax:

```js
console.log([1, 2, 3].map(n => multiply(10, n)));
// [ 10, 20, 30 ]
```

And, we can even combine these two arithmetic functions together using both syntaxes:

```js
console.log([1, 2, 3].map(square).map(n => multiply(10, n)));
// [ 10, 40, 90 ]
```

But if we wanted/needed to use that first syntax (like in Elm). Then we have to use **Partial Application**.

Let's refactor our `multiply()` function to employ partial application:

```js
const multiplyPartial = (a) => (b) => a * b;
```

If you're a simple JavaScript developer like myself, that probably hurt your brain and caused you to shudder a little.


Instead of two parameters, `multiplyPartial` is like two functions. The first function returns another function which returns the product of the two inputs.

With partial application, you can write a function like this

```js
const multiplyPartial10 = multiplyPartial(10);
```

The `multiplyPartial10` function can now take the `b` argument, which returns the product of the two:

```js
multiplyPartial10(4)
// 40
```

Returning to that error we got, using partial application we can do:

```js
console.log([1, 2, 3].map(multiplyPartial(10)));
// [10, 20, 30]

// or even
console.log([1, 2, 3].map(multiplyPartial10));
// [10, 20, 30]
```

Again, the function `multiplyPartial(10)` returns a function, and that function is applied to each element of the array.

## Mixing Types

In JavaScript, a function where the parameters are two different types is perfectly ok:

```js
const mixedTypesOne = (a, b) => a.toUpperCase() + " " + (b * 10);
const mixedTypesTwo = (a, b) => b.toUpperCase() + " " + (a * 10);
```

Both of them give you:

```js
console.log([1, 2, 3].map(n => mixedTypesOne("This number multiplied by 10 is", n)));
console.log([1, 2, 3].map(n => mixedTypesTwo(n, "This number multiplied by 10 is")));
// [
//     'THIS NUMBER MULTIPLIED BY 10 IS 10',
//     'THIS NUMBER MULTIPLIED BY 10 IS 20',
//     'THIS NUMBER MULTIPLIED BY 10 IS 30'
// ]
```

Regardless of which type comes first in the `mixedTypes` function, using the arrow syntax in `map()` we can pass in the correct argument.

Now let's refactor them using partial application:

```js
const mixedTypesPartialOne = (a) => (b) => a.toUpperCase() + " " + (b * 10);
const mixedTypesPartialTwo = (a) => (b) => b.toUpperCase() + " " + (a * 10);
```

And running the first gives:

```js
console.log([1, 2, 3].map(mixedTypesPartialOne("This number multiplied by 10 is")));
// [
//     'THIS NUMBER MULTIPLIED BY 10 IS 10',
//     'THIS NUMBER MULTIPLIED BY 10 IS 20',
//     'THIS NUMBER MULTIPLIED BY 10 IS 30'
// ]
```

But the second:

```js
console.log([1, 2, 3].map(mixedTypesPartialTwo("This number multiplied by 10 is")));
// TypeError: b.toUpperCase is not a function
```

In `mixedTypesPartialTwo`, the the argument passed in as `b` is a number, not a string.

## So what?

As the above example demonstrated, piping and partial application don't always play well with some common JavaScript practices — namely, functions with two parameters.

In Elm, functions only take one argument,[^1] and partial application does the rest.

I'm excited for the pipe operator, but it does mean having to think a little differently about how to write code. I struggled with this concept a bit, so hopefully this can help others.

[^1]: [So conceptually, every function accepts one argument.](https://guide.elm-lang.org/appendix/function_types.html)