DEV Community

Cover image for A Fallback for the JavaScript Pipeline Operator
Aldwin Vlasblom
Aldwin Vlasblom

Posted on • Updated on

A Fallback for the JavaScript Pipeline Operator

Background

As I've also mentioned in my Introduction to Fluture, there's a tc39 proposal for inclusion of a "pipeline operator" into the JavaScript language.

This is great news for functionally-minded libraries such as Ramda, Sanctuary, Fluture, and many more. It also makes some vanilla JavaScript nicer, for example:

const input = '{"data": 1765}';

const answer = String(
  Math.floor(
    Math.sqrt(
      JSON.parse(input).data
    )
  )
);
Enter fullscreen mode Exit fullscreen mode

Becomes

const input = '{"data": 1765}';

const answer = input
  |> JSON.parse
  |> (x => x.data)
  |> Math.sqrt
  |> Math.floor
  |> String;
Enter fullscreen mode Exit fullscreen mode

Pretty neat, right? Well, sadly not everyone agrees. And it is because of this disagreement that the operator has still not made it into the language. What's worse, chances are that when it does make it -- if it does -- it will be in a form that is not half as useful for functional programming. I have argued as to why here, here, here in more depth, and very extensively over here.

The Fallback

That brings me to a small idea I had that allows us to write code much like the above, but in today's JavaScript. You can use it now, while waiting for a consensus to be reached, and in the future, in case the committee decides against the functional variant of the operator. This is what it looks like:

const input = '{"data": 1765}';

const answer = input
  [o] (JSON.parse)
  [o] (x => x.data)
  [o] (Math.sqrt)
  [o] (Math.floor)
  [o] (String);
Enter fullscreen mode Exit fullscreen mode

Very similar to the pipeline example from above! :)

This is how it can be achieved:

const key = Symbol('pipe');

global.o = key;

function pipe(f){ return f(this) };

Object.defineProperty(Object.prototype, key, {value: pipe});
Enter fullscreen mode Exit fullscreen mode

Let's break this down.

  1. Symbol('pipe'): We define a unique symbol. We can use this to mutate existing objects without stepping on anyone's toes.
  2. global.o: This is an optional convenience. In the browser, this should be window.o. But you may just as well export const o instead and import it where needed, so not to pollute the global scope. I chose o as the name because it looks a bit like the mathematical composition operator (∘).
  3. function pipe: All that the (simple) pipe operator does is to apply a function to its context.
  4. Object.defineProperty(...): We attach our pipe method to the Object prototype using our symbol as a key. This immediately retrofits almost all possible values (all the ones that inherit from Object) with our pipeline capabilities.

Alright, well, that's all there is to it. Let's hope that we won't have to use this trick (much longer).

Oldest comments (4)

Collapse
 
well1791 profile image
Well

That's awesome! I really like the simplicity of the implementation!
However, it's sad to see people not aware of the benefits of functional programming, I guess they don't even try FP enough to see the difference. Do you know if there is at least a kind of reasonable point against FP?

Great article man! Keep it up!

Collapse
 
iquardt profile image
Iven Marquardt • Edited

This discussion doesn't surprise my since JS is hijacked by imperative paradigm disciples for a long time (sorry for being so blunt on this).

Anyway, I think a flat application syntax is the wrong reason to fight for, b/c we can always resort to...

const app_ = x => f => f(x);

const appn = fs => x =>
  arrFold(app_) (x) (fs);
Enter fullscreen mode Exit fullscreen mode

If you really don't care about mathematical functions you can even replace the array with rest syntax.

Anyway, there are three features you cannot properly introduce in userland:

  • do-notation (flat monadic chaining)
  • let expressions
  • pattern matching

You can use generators for deterministic monads and default-arguments to mimic let expressions and church-encoding to get pattern matching, but honestly, these workarounds suck.

If I had these tools at my disposal, I'd never complain again. Okay, maybe a type system based on parametric polymorphism would be nice too.

Collapse
 
logs profile image
0x78logs • Edited

Help me understand what I'm missing here. why not just use compose/pipe much cleaner and simpler

pipe(
JSON.parse,
prop("data"),
sqrt,
floor,
String
);

Collapse
 
thestoicdeveloper profile image
The Stoic Developer

Stacktrace and breakpoint?