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
)
)
);
Becomes
const input = '{"data": 1765}';
const answer = input
|> JSON.parse
|> (x => x.data)
|> Math.sqrt
|> Math.floor
|> String;
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);
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});
Let's break this down.
-
Symbol('pipe')
: We define a unique symbol. We can use this to mutate existing objects without stepping on anyone's toes. -
global.o
: This is an optional convenience. In the browser, this should bewindow.o
. But you may just as wellexport const o
instead and import it where needed, so not to pollute the global scope. I choseo
as the name because it looks a bit like the mathematical composition operator (∘). -
function pipe
: All that the (simple) pipe operator does is to apply a function to its context. -
Object.defineProperty(...)
: We attach ourpipe
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).
Top comments (4)
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...
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:
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.
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!
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
);
Stacktrace and breakpoint?