
ES7 Proposal: The Pipeline Operator - clessg
https://github.com/mindeavor/es-pipeline-operator
======
renekooi
I like this a lot. It's similar to the proposed function bind syntax[1], but a
bit more friendly to not-`this`-related use:

    
    
        function bindMap(fn) { return this.map(fn) }
        [ 1, 2, 3 ]::bindMap(n => n + 1)
        // standalone use: bindMap.call(arr, fn)
    
        function pipelineMap(fn) { return arr => arr.map(fn) }
        [ 1, 2, 3 ] |> pipelineMap(n => n + 1)
        // standalone use: pipelineMap(fn)(arr)
    

(Also nice that it works in a very similar way to decorators, although they
won't be interchangeable in most cases, with decorators working on class
constructors and property descriptors, instead of just any value.)

Presumably, at most one of these proposals will make it in :)

[1]: [https://github.com/zenparsing/es-function-
bind](https://github.com/zenparsing/es-function-bind)

~~~
wycats
I agree that a "functional" operator that uses `this` as the first argument is
weird.

The main goal of all of these proposals is "left to right" composition when
working with functions, and functions take parameters.

------
RodgerTheGreat
In concatenative languages, this style of composition is the norm, and it does
indeed work well for many tasks:

[http://concatenative.org/wiki/view/Pipeline%20style](http://concatenative.org/wiki/view/Pipeline%20style)

~~~
lisivka
It is available in JS too:

    
    
        function double(str) {
          return str+', '+str;
        }
        function ucase(str) {
          return str.slice(0,1).toUpperCase()+ str.slice(1);
        }
        function exclamation(str) {
          return str+"!";
        }
        
        var _="hello";
        _ = double(_);
        _ = ucase(_);
        // _ = foo(_);
        _ = exclamation(_);
        
        console.log(_); // Hello, hello!
        
        function pipe() {
          var functions=[];
          for(var i=0; i<arguments.length; i++) {
            functions[i]=arguments[i];
          }
          return function pipe_call(arg) {
            for(var i=0; i<functions.length; i++) {
              arg=functions[i](arg);
            }
            return arg;
          }
        }
        
        var _="hello";
        _ =pipe(
          double,
          ucase,
          // foo,
          exclamation
        )(_);
        console.log(_); // Hello, hello!

------
dustingetz
Can we just put more focus on sweet.js so we can do this kind of in a library
with standard build tooling? [http://sweetjs.org/](http://sweetjs.org/)

There's a lot more operators than |> that you would want. Clojure commonly
uses ->, ->>, as->, etc. It should be done in a library if it is done at all.

Also, going full-on functional in javascript is going to end badly, javascript
simply wasn't designed for it. If you go down this path in real projects,
sooner or later you inevitably end up at clojurescript or whatever other real
FP language suits.

~~~
wycats
Can you elaborate on the rough seams between JavaScript and "full on
functional"?

I'm not a believer in the FP vs. OO religion, and have historically been
skeptical of the bind operator. That said, I think that both composition via
objects and composition via functions have their place.

The pipeline operator attempts to make composition via functions more readable
(left-to-right instead of right-to-left) and unless you think composition via
functions is a bad idea in JS in all cases, I'm not sure what "full-on
functional" has to do with it.

~~~
dustingetz
js objects are mutable; utliliy libraries like underscore, lodash, jquery, do
not provide purity guarantees, they export impure functions that mutate
objects (e.g. [https://lodash.com/docs#merge](https://lodash.com/docs#merge)).
When we start leaning heavily on function composition to build our
abstractions, we simply must have purity guarantees for everyday utilities or
everything falls apart. So to go "full-on functional" in javascript means we
need to fork and rewrite the entire ecosystem. And if you're going to do that,
there isn't much value in keeping javascript.

~~~
lisivka
So we need an (a) method to freeze object and it children, (b) to indicate
that function will accept and/or return only read-only objects. Reserved
"const" keyword is perfect for that.

~~~
dustingetz
That's unfortunately not what es6 const does - const is about re-assignment,
not about immutability. (Though you can implement immutability in terms of
non-re-assinment if it is const-all-the-way-down, which javascript objects
aren't.) And you still need to re-write all the ecosystem to work with
immutable objects.

------
Animats
The single-argument case is elegant, and the multiple-argument case is awful.

    
    
        var newScore = add(7, validateScore( double(person.score) ))
    

becomes

    
    
        var newScore = person.score
          |> double
          |> score => add(7, score)
          |> validateScore
    

This is very Forth-like. Operands get pushed on the stack, and operators take
from the stack and push results back. In Forth, there's no operator precedence
and no parentheses; it's pure reverse Polish.

Why not go all the way to pure RPN?

    
    
         7 person score . double ValidateScore add newScore = 
    

See how it simplifies the syntax? Of course, you have to know how many
arguments each function takes.

Do we really want to go down this route? It's all syntax; it doesn't add any
capability. As an extension to an existing language, it's likely to produce a
mess.

Mixing functional and imperative notations is a problem. LISP is all nested
parentheses, and that bothered a lot of people. Forth eliminates all the
parentheses, which bothered different people. Python was pure imperative,
("lambda" went in over major objections) and is gradually getting more
functional features, but they don't fit the syntax well.

~~~
simplify
It's not just syntax; it actually opens new ways for API design. See abreza's
comment:
[https://news.ycombinator.com/item?id=10686596](https://news.ycombinator.com/item?id=10686596)

------
donatj
I feel like that just confuses things for no good reason. It's no fewer
characters than parens but provides as I see it less clarity.

~~~
efdee
I think the clarity comes from the reading order. str |> method1 |> method2 |>
method3 vs method3(method2(method1(str))).

~~~
mbreese
That's readable in the simple case, (arg1 is a string, returns a string). What
about the more complex cases? Different method signatures, required extra
arguments, returning objects or arrays? This will either a) break in those
cases, or b) not be applicable. In which case, it's extra complexity for very
little gain.

Seriously, this just seems like syntax sugar that just isn't needed. I get
that you might not think it's elegant, but is anyone seriously troubled or
confused by method3(method2(method1(str)))? How would the pipeline syntax help
here?

------
wnfewnlkew
Please no.

Javascript's beauty lies in its versatile simplicity. Can we just leave these
sorts of things to systems programming languages like C++? I came to
javascript because of its lack of cruft, but if libraries start adopting this
then I'll be forced to put it in my code as well. If that happens then I'll
probably leave for nim or clojure, though that's not preferable.

~~~
edgyswingset
Can't speak for nim, but Clojure has a near-exact feature which is used all
over the place, the thread-first and thread-last macros.

Example:

    
    
        (defn do-stuff [params-map]
           (->
              (build-object params-map)
              (do-thing-with-object)
              (extract-thing)
              (format-thing)))
    

I'm not sure why you'd choose Clojure to _get away_ from things like |>.

~~~
bpicolo
Elixir has this exact feature. I'm sure it's not the first language to either.

~~~
bitwalker
F#/OCaml too, probably all the ML variants actually.

------
pskocik
What's so painful about just doing `exclaim(capitalize(doubleSay("hello")));`?
Why introduce needless syntactic sugar that'll only confuse people about how
UNIX pipes work?

~~~
thescriptkiddie
(because (there (is (such (a (thing (as (too (many parens)))))))))

~~~
pskocik
I bet Lispers would beg to differ. :D

Nested function do one thing on a chunk of data, then another, then another.

*nix pipelines apply all the filters at once and control the scheduling and lifetime of the filters.

Those are quite different concepts, and I think it's confusing to conflate the
two. Plus every decent editor can handle paired parentheses.

~~~
danneu

        > I bet Lispers would beg to differ. :D
    

Would they?

I wrote and read threading macros all the time when I used Clojure. Turns out
it cleans up code.

------
k__
I liked this feature in LiveScript rather much.

Especially that it worked in both directions.

    
    
        my-list = filter <| sort <| get-data

------
mateuspv
If is to be more functional like e prefer add functions: compose and curry:

1 - // With pipe: var result = "hello" |> doubleSay |> capitalize |> exclaim;

    
    
      With compose:
      var greet = compose(exclaim, capitalize, doubleSay);
      greet("hello");
      // or compose(exclaim, capitalize, doubleSay)("hello");
    

2 - var person = { score: 25 };

    
    
      // With pipe:
      var newScore = person.score
        |> double
        |> score => add(7, score)
        |> validateScore
    
      // With compose and curry:
      var newScore = compose(validateScore, curry(add(7), double);
      newScore(person.score);
    

3 - Mixin paradigms

    
    
      // With compose and curry :
      var newScore = Function.compose(validateScore, add.curry(7), double);
      newScore(person.score);

------
jordz
As an F# user, I like this a lot, very similar although it can confuse people.
It's very much a trait of functional programming and it's easy to read once
you get the hang of it :)

------
radicalbyte
That's beautiful, the linear pipeline better matches your intuition about the
computation than the nested functions.

Can we have this in C# 7? :)

~~~
danbruc
This essentially exists - extension methods.

~~~
radicalbyte
Extension methods only apply to instances and they must be declared
explicitly.

This syntax can be used anywhere as long as the types signatures of the
functions are conducive to the chaining.

~~~
danbruc
Not sure what you mean with applying only to instances, in which case couldn't
you use an extension method? If you own the code making a function an
extension method seems not a big deal. If you want to invoke a static function
you don't own the code for then you could have a generic wrapper extension
method which admittedly would probably look rather clumsy and nullify the
desired gain in readability.

~~~
mateuszf
But you have to write them before using. It's not needed in case of the
discussed feature.

------
wycats
If we're going to do this operator, I would prefer a regular arrow:

    
    
        import { sortBy, filter, map } from 'array-like';
    
        let names = document.querySelectorAll('.entry')
          -> sortBy('title')
          -> filter(entry => entry.getAttribute('data-url') in whitelist)
          -> map(entry => entry.getAttribute('name');
    

I personally find this more readable than either the bind proposal (which
looks too much like a property access, and not enough like a function call, to
me) and the proposed pipeline operator, which doesn't have an immediate
meaning to my eyes.

~~~
pygy_
_> I would prefer a regular arrow_

And I want it painted green ;-)

 _> ... which doesn't have an immediate meaning to my eyes._

It's a matter of background. F#, Julia and Livescript have identical
operators, likely among others.

[https://msdn.microsoft.com/en-
us/library/dd233229.aspx#Ancho...](https://msdn.microsoft.com/en-
us/library/dd233229.aspx#Anchor_11)

[http://docs.julialang.org/en/release-0.4/stdlib/base/?highli...](http://docs.julialang.org/en/release-0.4/stdlib/base/?highlight=|%3E#Base.|%3E)

[http://livescript.net/#piping](http://livescript.net/#piping)

~~~
wycats
Fair enough.

I personally consider compound operators (asymmetrical) to be pretty risky
(the do, indeed, depend on background), and I like '->' because it has a clear
meaning outside of programming.

But you're right, this is a bikeshed.

I'd prefer either version of the pipeline operator to bind.

------
Sonata
After having used this operator in Elixir and Elm, I would love to see it in
JS. I know it seems like a superficial issue, but I think the left-to-right
chaining flow is one of the main things people miss as they move from an OO
style to a more functional one.

~~~
gotchange
It is not superficial or trivial at all. This proposal helps to ease the
cognitive load required when reading or debugging code at least for the
straightforward cases and makes writing code even more interesting and
intuitive.

------
pygy_
@dang, was this manually penalized? Or is it a false positive to the flamewar
detector?

It looked like a fine submission.

52 points, an hour ago, and it lies on the third page...

------
1971genocide
Doesn't LiveScript already have it ?

~~~
chc
As noted in the OP, yes, as do F#, Elixir, Elm and Julia.

------
XorNot
I don't entirely understand where this gets me that promises don't.

~~~
danneu
Promises are an abstraction for managing asynchronous operations.

This proposal is about function application.

