Charles Loder

Posted on

# Piping in JS, or what Elm taught me about partial application

There has been some recent talk 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.

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

Or, as I prefer, writing:

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

We could write:

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

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:

List.map square [1, 2, 3]

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

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:

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:

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:

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:

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:

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:

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

const multiplyPartial10 = multiplyPartial(10);

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

multiplyPartial10(4)
// 40

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

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:

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

Both of them give you:

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:

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

And running the first gives:

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:

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.