top | item 34454184

Pipe Operator (|>) For JavaScript

309 points| nassimsoftware | 3 years ago |github.com | reply

426 comments

order
[+] arp242|3 years ago|reply
Is this:

   Object.keys(envars)
     .map(envar => `${envar}=${envars[envar]}`)
     .join(' ')
     |> `$ ${%}`
     |> chalk.dim(%, 'node', args.join(' '))
     |> console.log(%);
Really better than:

  console.log(chalk.dim(
      `$ ${Object.keys(envars)
        .map(envar => `${envar}=${envars[envar]}`)
        .join(' ')
      }`,
      'node',
      args.join(' ')
  ));
That's the real-world example they have (I reformatted the second one slightly, because it looks better to me). Neither seems very good to me, and the |> version doesn't really seem "less bad".

Can also write it as:

   process.stdout.write(chalk.dim(
     `$ ${Object.keys(envars)
       .map(e => `${envar}=${e[envar]}`)
       .join(' ')
     }`,
   ))
   console.log(chalk.dim('node', args.join(' ')))
Which seems clearer than either because it splits out "print env variables" and "print out node args". And it would be even better with some sort of helper to convert an object to k=v string:

   console.log(chalk.dim(`$ ${dumpObj(envars)}`, 'node', args.join(' ')))
---

I also feel this:

> In the State of JS 2020 survey, the fourth top answer to “What do you feel is currently missing from JavaScript?” was a pipe operator.

Is the wrong way to go about language design. Everyone wants something different, and if you just implement the "top 5 most requested features" you're going to end up with some frankenbeast of a language.

[+] hajile|3 years ago|reply
The F# syntax looks/acts a lot better here (especially paired with lodash). I also feel your code example wasn't done in the way people would actually use pipes.

    import {map, join} from 'lodash/fp' //iterators have better performance

    envars
    |> Object.entries
    |> map(([key, val]) => `${key}=${val}`)
    |> join(' ')
    |> x => chalk.dim('$ ' + x, 'node', join(' ', args))
    |> console.log
Even without lodash, it's still easy to read.

    envars
    |> Object.entries
    |> x => x.map(([key, val]) => `${key}=${val}`)
    |> x => x.join(' ')
    |> x => chalk.dim('$ ' + x, 'node', join(' ', args))
    |> console.log
For sake of completeness, here's the hack-based variant

    envars
    |> Object.entries(%)
    |> %.map(([key, val]) => `${key}=${val}`)
    |> %.join(' ')
    |> chalk.dim('$ ' + %, 'node', join(' ', args))
    |> console.log(%)
[+] dns_snek|3 years ago|reply
I won't speak about the specifics of the chosen syntax (Hack/F#) but in general - absolutely.

With pipes you can visually follow the manipulations and function calls in the order that they happen instead of being forced to scan the code inside-out & outside-in, matching parentheses and function call parameters in your head, while still visualizing intermediate results to get 1 final return value.

I find Elixir code much easier and quicker to understand, in large part thanks to its (admittedly, imperfect) pipe syntax. Code written in this way is also much easier to debug because you can quickly add `console.log`, breakpoints, or equivalent between the pipes.

I find this unnecessarily time-consuming and difficult to parse and I'd likely raise some flags in a code review:

  console.log(chalk.dim(
      `$ ${Object.keys(envars)
        .map(envar => `${envar}=${envars[envar]}`)
        .join(' ')
      }`,
      'node',
      args.join(' ')
  ));

Without pipe syntax, I'd refactor this to:

  const envStr = Object.keys(envars)
        .map(envar => `${envar}=${envars[envar]}`)
        .join(' ');
  const styled = chalk.dim(`$ ${envStr}`, 'node', args.join(' '));
  console.log(styled);
  
But you often find yourself having to add additional logic, e.g. to scrub sensitive values, so it would probably end up closer to:

  const sensitiveEnv = [...];
  const envStr = Object.keys(envars)
        .filter(envar => !sensitiveEnv.includes(envar))
        .map(envar => `${envar}=${envars[envar]}`)
        .join(' ');
  const styled = chalk.dim(`$ ${envStr}`, 'node', args.join(' '));
  console.log(styled);
[+] BigJono|3 years ago|reply
TC39 proposals often have rubbish real world examples that should never see the light of day in a JS codebase.

It makes me really question the judgement of the people that are working on this language, and it explains how some of the shittier proposals manage to slip in and why the good ones are misused all over the place in every real world codebase when this is the kind of guidance devs have on where to use fancy new features.

In a few years everyone is going to collectively lose their fucking mind once again and decide that ternaries are now the devil and every React component should be chock full of do expressions. I can see that coming clear as day. That might finally be enough to get me to throw in the towel if the also incoming decorator hell doesn't do it.

[+] pharmakom|3 years ago|reply
The Hack proposal is horrible imo because it doesn’t look like JS anymore. The F# proposal is 99% of the benefit whilst being actually approachable.
[+] mschuetz|3 years ago|reply
All of the examples look unreadable and hard to debug to me. Why not something like this rather than one massive nested instruction?

    let keys = Object.keys(envars);
    let text = keys.map(envar => `${envar}=${envars[envar]}`).join(" ");
    console.log(chalk.dim(`$ ${text}`), "node", args.join(' '));
That way you can easily inspect and verify the intermediate values at runtime. Helpful for you to see if your code works as expected, helpful for others to see what the code is doing.
[+] lucideer|3 years ago|reply
The issue with taking examples from real-world code & converting them is that there's no guarantee the real-world code is good. It usually isn't, so you're comparing bad with bad.

A more aggressive reformulation would be to prefix the original code with

  const envOutput = Object.keys(envars).map(envar => `${envar}=${envars[envar]}`).join(' ');
  const argsOutput = args.join(' ');
Leaving the example being converted as simply:

  console.log(chalk.dim(`$ ${envOutput}`, 'node', argsOutput));
vs

  envOutput |> `$ ${%}` |> chalk.dim(%, 'node', argsOutput) |> console.log(%);
This more clearly highlights the obvious limitations of pipelines - piping envOutput but not 'node' nor argsOutput is a jarring syntax-mix here. Though I think it offers some hope for them working well in other scenarios - possibly in curry-heavy applications.
[+] scotty79|3 years ago|reply
> Neither seems very good to me, and the |> version doesn't really seem "less bad".

For me the piped version seems way better, way cleaner, with cleanly separated processing steps.

I don't like nested steps because for nesting multiple arguments functions your expression tends to grow in both directions and parts belonging to the same step tend to end up really far from one another and clearly distingushes them from data that's pushed through the pipeline.

% is short so it keeps parts of the same processing step together.

This syntax also enables you to express which part is the data to be processed, what are the processing steps and what are additional parameters of the processing steps.

[+] dmix|3 years ago|reply
Just because pipe's aren't the ideal solution for that convoluted example they decided to list doesn't mean they aren't still extremely useful for a consistent set of problems. Like anything they can be abused to make code less readable.

I also have a feeling this doc lists every sort of usecase, not because they are advertising it as "always the better version", but because it's a design doc that needs to factor in edge cases, such as using a pipe following chained function calls.

[+] thdc|3 years ago|reply
I do prefer the piped version mainly because it removes annoying nesting but that's not high on my prioritized list of discomforts. The issue of deeply nested function calls is a thing, but I just use some temp variables to avoid it when it really starts looking ugly.

Then again, this is my typical opinion to a lot of the proposals - I see what this is useful for but I haven't experienced enough pain to really argue for it. However, if it does make it into the spec then I will use it probably because it's there.

I also recognize my stance as one that not many people like in other contexts because it can be interpreted as me not looking to improve my situation. But on the flip side I think cluttering the language specification with a lot of superficial syntactic sugar is a mistake.

[+] progx|3 years ago|reply
For me not. In the Pipe example you must read first that something is done with the object and that it will be output at the end.

If i read a code and i want to now what it will do, i wanna first read that it will output something. If that is what i search for, i read the nested code.

With the pipe style i waste more time, even if the code looks clearer.

I not overused example could look like this:

  console.log(
    Object.keys(envars)
      .map(envar => `${envar}=${envars[envar]}`)
      .join(' ')
    |> `$ ${%}`
    |> chalk.dim(%, 'node', args.join(' '))
  )
If i don't care about console.log, i don't have to read the nested code.
[+] Aldipower|3 years ago|reply
Both examples are not good. They are both very unreadable and an example of slapping things together without to look on readability.
[+] runarberg|3 years ago|reply
During the 2020 survey there were two versions of this operator on the table smart mix and F#. A few months later the committee advanced a third option Hack, which is kind of like smart-mix but always requires the placeholder token.

I suspect that many developers expressed their desire for this operator under the believe that it would make their style of programming easier, which F# indeed does for many code bases, particularly those that use libraries such as fp-ts or rxjs.

However the advanced version fails to deliver that

[+] scotty79|3 years ago|reply
I'll bite:

    let _= Object.keys(envars).map(envar => `${envar}=${envars[envar]}`).join(' ')
    _= `$ ${_}`
    _= chalk.dim(_, 'node', args.join(' '))
    _= console.log(_);
This is possible in current JS syntax.

You can also cram it into one line with semicolon:

    let _= Object.keys(envars).map(envar => `${envar}=${envars[envar]}`).join(' ') ;_= `$ ${_}` ;_= chalk.dim(_, 'node', args.join(' ')) ;_= console.log(_);

So it's basically Hack syntax for pipes with just ;_= instead of |> and _ instead of %. And you need to 'mark' the beginning of the pipeline with `let _=`

Additional 'benefit' is that until you leave the scope you can access output of the last pipe through _.

You can always use ;_= for consistency and pre-experess you intent to use the piping in the current scope by doing `let _;` ahead of time:

    let _;

    ;_= Object.keys(envars).map(envar => `${envar}=${envars[envar]}`).join(' ')
    ;_= `$ ${_}`
    ;_= chalk.dim(_, 'node', args.join(' '))
    ;_= console.log(_);
Full disclosure, I hate all of the above but I love Hack syntax with |> and %.

To better confer the direction of the pipe you might even use the letter that is oriented to the right:

    let D;
    ;D= Object.keys(envars).map(envar => `${envar}=${envars[envar]}`).join(' ')
    ;D= `$ ${D}`
    ;D= chalk.dim(D, 'node', args.join(' '))
    ;D= console.log(D);
And if you want to use your pipe as an expression or return it from the function , instead of ; might be better:

    let D;
    return D= take(D) ,D= bake(D) ,D= serve(D);
Surprisingly semicolon auto-insertion doesn't interfere with this:

    let D;
    return D= take(D) 
    ,D= bake(D) 
    ,D= serve(D);
[+] aabbcc1241|3 years ago|reply
Within the current js syntax, you can make the code looks readable with a chain or pipe with a simple function https://github.com/beenotung/tslib/blob/master/src/pipe.ts

You can even use an array if you don't need to peek the value in the middle of a chain of operations

Example:

    createChain(
      Object.entries(envars)
      .map(([key, value]) => `${key}=${value}`)
      .join(' ')
    )
      .map(keys => `$ ${keys}`)
      .map(pattern => chalk.dim(%, 'node', args.join(' ')))
      .use(line => console.log(line))

In each step, you can name the intermediate result accordingly to it's meaning, should be more readable than calling it %
[+] jakear|3 years ago|reply
> Everyone wants something different, and if you just implement the "top 5 most requested features" you're going to end up with some frankenbeast of a language.

Very much this. They need a new question on the survey "does JavaScript need new syntax or can we just leave it alone and work on perf/tooling/etc. of the existing stuff without throwing a bunch of new junk in"?

[+] ravenstine|3 years ago|reply
The flaw with that approach to language development is that there's no limiting factor.

Can you imagine a survey where everyone says they're satisfied with the language as-is? Of course not. That's statistically impossible. Even if 99% of the needs of developers are satisfied by a language, people are going to eventually answer that they'd like something that the language doesn't have, and that's always the case.

Let's say JavaScript implemented nearly every single language feature that's ever been invented. That is except the goto statement. Upon being surveyed on what they think is currently missing from JavaScript, a significant number of developers will have to respond with goto. Does that mean JavaScript is actually "missing" goto and that it was a mistake that it was never implemented in the first place? Of course not.

[+] NhanH|3 years ago|reply
Method chaining is better, but you need the library/class/ code to support it. If you got a bunch of function it can’t help you. Pipe operator is useful for when you use other people’s code.

On the other hand, pipe is less useful when you don’t have partial application of function built-in. So yeah I think this is not worth it.

[+] mkl|3 years ago|reply
JavaScript has been a frankenbeast of a language right from its inception, by design.

However, the 5th most requested feature in that poll is somehow "functions", and "sanity" also appears in the results, so this particular source may not be a good one.

[+] jameshart|3 years ago|reply
The thing I dislike about it most is the constantly-rebound % variable. It means something different in each line. In this case they have elected to keep it as a string throughout the pipe, but this ‘more pipey’ version of the code has it start out as an array then turn into a string halfway through the pipe, which feels dangerous (and is presumably why they didn’t take the example this far):

    Object.keys(envars)
      |> %.map(envar => `${envar}=${envars[envar]}`)
      |> %.join(' ')
      |> `$ ${%}`
      |> chalk.dim(%, 'node', args.join(' '))
      |> console.log(%);
[+] tgv|3 years ago|reply
So their argument in favor of this fugly new operator is that some people write unreadable code? That's not the language's fault. As they say, you can write FORTRAN in any language. This just gives them a new tool to make things even worse.

     chalk.dim(Object.keys(envars)
       .map(envar => `${envar}=${envars[envar]}`)
       .join(' ')
       |> `$ ${%}`, 'node', args.join(' '))
     |> console.log(%);
And it doesn't even work on objects. Lame. I know the reason: JS has no typing. Still is lame.
[+] masswerk|3 years ago|reply

  > I also feel this:
  >> In the State of JS 2020 survey, the fourth top answer to “What do you feel is currently missing from JavaScript?” was a pipe operator.
  > Is the wrong way to go about language design.
Let's call it Signor Rossi language design…

"Signor Rossi cosa vuoi? … E poi, e poi, e poi" (Viva la felicità by Franco Godi [1])

[1] https://www.youtube.com/watch?v=UrKKMtjNWCI

[+] Joker_vD|3 years ago|reply
Some people, it seems, just love to write things in A-normal form but dislike naming temporary variables. Solution: pipe operator.
[+] rubyist5eva|3 years ago|reply
As somone that use to write a lot of functional-style code (in ruby), and generally prefers functional style, and have created many many many "pipelines" like that in ruby code - I've actually started to "regress" to "status-quo" (as the article puts it) mostly because I work with developers that don't understand functional style and it just becomes point of contention in review that I just don't care about getting into anymore. I can see this same kind of thing happening with this operator in JS-land (I may be wrong, haven't written any significant javascript in years, but people tend to be stubborn).

I just write things as stupidly as possible now, and just do the second one even though there are nicer "ruby-ways" to do them - maybe this is "bad" but I find it easy to read and grok...and it doesn't cause arguments during review <.<

[+] kriz9|3 years ago|reply
Temporary variables are often tedious? I have found that well named temporary variables are the only clear way to comment code without actually writing the comment. The version with temporary variables is much easier to understand without having to read the rest of the code.
[+] no_wizard|3 years ago|reply
I hope Records & Tuples[0] land before this does. It would have meaningful and far reaching positive effects for the language, without much controversy. Like most of these things, it takes about 5-7 years for it to permeate through enough of the engines to be meaningfully useful in the day to day of web developers (node / deno typically 12-18 months tops). It would drastically speed up existing code once wide adoption is gained though.

I don't think the Pipe Operator would be as useful in comparison.

I really hate how long the Records & Tuple proposal has been languishing at stage 2. It could've shipped years ago if not for a syntax debate that dragged on and on without being fruitful[1]

EDIT: there is a class based version of this, that is stage one, for adding structs[2]. They behave similarly.

[0]: https://github.com/tc39/proposal-record-tuple

[1]: https://github.com/tc39/proposal-record-tuple/issues/10

[2]: https://github.com/tc39/proposal-structs

[+] dgb23|3 years ago|reply
Saw a talk with Douglas Crockford[0] years ago. He said something like: Before JS classes got introduced he asked why they didn't just implement macros for the language. Classes are in fact just syntactic sugar. Just like async/await, and now this proposal.

In hindsight he was right. JS would be better off if it did have macros. Much of the whole babel/webpack/react/ts stuff would be just a bunch of macros instead of idiosyncratic build tools and so on. And we would have had much less compatibility churn.

In fact this proposal here, is trivial to implement with macros. Clojure has the same thing (threading operator) and it's just a macro.

[0] https://en.wikipedia.org/wiki/Douglas_Crockford

[+] hajile|3 years ago|reply
Mozilla created SweetJS over a decade ago[0]. It added hygenic macros to JS and I'm sure everyone on the TC39 committee is familiar with it.

There's a lot to like about it, but macros in such a complicated language as JS are hard to get right. They'd also potentially lead to huge fracturing in the JS ecosystem with different factions writing their own, incompatible macro-based languages.

Look at JSX for an example. It's actually a subset of a real standard (E4X -- actually implemented in Firefox for a long time), but just one relatively small syntax addition has added complexity elsewhere.

For example, `const foo = <T>(x:T) => x` is valid Typescript for a generic arrow function, but is an error if your file is using JSX.

I like the idea of macros, but I suspect they made the right call here.

[0] https://www.sweetjs.org/

[+] mattgreenrocks|3 years ago|reply
It's pretty sad that you have to advocate for macros on a site called Hacker News nowadays. They aren't even terribly different from frameworks (which everyone loves): incorrect usage generates a stack trace you need to decipher.
[+] ketzu|3 years ago|reply
> Much of the whole babel/webpack/react/ts stuff would be just a bunch of macros instead of idiosyncratic build tools and so on. And we would have had much less compatibility churn.

Wouldn't that just trade compatibility churn against running the transpilers on client side in javascript, making it even slower to execute? Moving this part of the execution on the developer side seems like a good choice to me.

[+] gorjusborg|3 years ago|reply
First, syntactic macros are great, and I've often wished for them to exist in javascript (and other languages).

Second, I only trust macros to people who are disciplined to use them wisely.

Third, I've met only a handful of developers I would consider disciplined in this way.

[+] madeofpalk|3 years ago|reply
What would macros for JavaScript look like? Would you ship them to the client for browsers to execute?
[+] captainmuon|3 years ago|reply
I don't understand why you can't just use temporary variables. The article mentions mutation is bad, but what actually happens is that the name gets reassigned. No value is mutated.

That brings me to something I really want in JS, actual unmutable values. If you use `const x = new SomeClass()`, you cannot reassign it, but you can change fields. The first time I encountered `const`, I thought it did the opposite. It would be cool if you could declare something (object, array) to be an immutable value.

If you really want to introduce new operators, how about operator overloading? For example vector and matrix calculations become a lot clearer and less error-prone with infix operators. It should be technically easy to add them to typescript - rewrite the expression to a function call depending on the types of the operands - but the TS devs refuse to implement this on philosophical grounds unless it is implemented in JS. I guess in JS it would require runtime dispatch, but maybe that is not such a big penalty given that it usually uses a JIT anyway.

Oh, and while we are at it, fix `with`. The JS with statement is ridiculous and deprecated anyway. It makes all fields of an object available in it's scope. Contrast with VB6's `with`, which requires you to use a leading dot and is much more readable:

    with (elem.style) {
        .fontFamily = 'Arial';
        .color = 'red';
        console.log(.borderWidth);
        // in actual JS this would just be
        // console.log(borderWidth);
    }
[+] matlin|3 years ago|reply
Can't wait for this. Pipes are awesome in Elixir and bringing them to JS/TS will be great.

To me this is both concise and readable:

    const weather = `https://api.weather.gov/gridpoints/TOP/31,80/forecast`
        |> await fetch(%)
        |> await %.json()
        |> %.properties.periods[0]
[+] snow_mac|3 years ago|reply
I *HATE* pipes. For example from Elixir School (https://elixirschool.com/en/lessons/basics/pipe_operator):

``` foo(bar(baz(new_function(other_function())))) ```

They offer this example of an improvement:

``` other_function() |> new_function() |> baz() |> bar() |> foo() ```

While yes, pipes improve readability, how do they deal with errors? How do they deal with understanding what each thing is supposed to return?

I would prefer something like this, (descriptive variable names):

``` var userData = other_function();

var userDetails = new_function(userData);

var userComments = baz(userDetails);

var userPosts = bar(userComments);

var finalUserDetails = foo(userPosts);

return finalUserDetails; ```

Then I can easily debug each step, I can easily understand what each call is supposed to do, if I'm using type script, I can assign types to each variable.

I strongly oppose clean code for the sake of looking pretty, or being quick to type. Code is meant to be run and read more then written, it should be descriptive, it should describe what it's doing not a nasty chain of gibberish. Hence why most people hate REGEX.

[+] vorotato|3 years ago|reply
These hack pipes are a trojan horse. People wanted elixir/F#/ocaml aka function pipes, and what we got was unreadable line noise. I argued against it until I was blue in the face, decided it was bad for my general wellness to keep it up. I genuinely would prefer no pipes over this. I couldn't find a single example where I preferred it. The token they chose already has a meaning in Javascript! The arrogance and willful disregard for readable code was astonishing. The only tangible reason I could pull as to why they picked the least popular implementation despite all the outrage was "someone at google didn't like the function pipes". Even if you think we should avoid it because some google employee doesn't want it, that doesn't mean you should ram in an even worse implementation. I had to block the TC39 discussion because I was just going to get argumentative because they weren't listening at all, and they were dismissing actual concerns without any explanation.
[+] jsf01|3 years ago|reply
This strikes me as something better left to libraries. If you want to write in a functional style then Ramda, Lodash, Underscore, and plenty of others have pipe and compose functions.

    pipe(one, two, three)
Easy to read. No new syntax. Extendable with arrow functions.

Yes, there are some limitations in comparison to Hack Pipes. But those are far outweighed by not messing yet again with the language’s syntax.

[+] Someone1234|3 years ago|reply
Can someone remind me again why there's never been movement to add a second modern language to web-browsers? JavaScript was created in a weekend and then stuff tacked on for the last 28 years.

We know so much more about how to create programming languages today than we did then, and the whole "Year of the Linux Desktop" has become a WebAssembly meme now every year since its introduction six years ago, with it getting popular always next year, next version, with feature XYZ. Seemingly creating unmaintainable/debuggable mess from external languages with no true 1:1 into WebAssembly isn't as big of a hit as the originators expected.

Yet every time someone asks why there hasn't been movement here it is "Year of WebAssembly is next year!!!" WebAssembly has managed to slow actual progression towards something good. With the browser monoculture you'd think it would be easier now than ever to start a fully integrated second language with WebAssembly compilation for backwards compatibility.

[+] zelphirkalt|3 years ago|reply
For languages, which do not have built-in the power to change themselves, in many cases it might be better to stick to their feature set, instead of introducing even more language concepts. Look at how much work is involved to get something as simple as pipelines. As if they will ever find the right syntax for everyone.

If we used a normal function we might have to include a library or a dependency or heck, just take 5 minutes and write a pipeline function oneself. Sure, it will not be syntactically as minimal as a _change of the language itself_ to allow pipelining, but at least it will not introduce even more language features and everyone can easily look at the definition change it or include it in their own projects.

Ideally we would strive for a minimalistic set of features, which in turn can implement anything we want. Adding more and more features, without introducing a way for the user to modify the language without a commitee and a lengthy process, seems short-sighted.

If you want to give the user more power over syntax, introduce a well engineered macro mechanism (plenty of examples in other languages) and let people develop standards and libraries as macros and libraries. Later on decide what to take into the standard language. Similar to how jQuery influenced modern JS standard functions like querySelectorAll. Even if you don't take something into the standard language, users are still free to include it in their project. No harm done and everyone gets their lunch and can scratch their itches.

[+] eknkc|3 years ago|reply
I'm pretty sure this'd be a welcome addition to the language but to be honest, the syntax looks shit.
[+] ravenstine|3 years ago|reply
As someone who actually loves JavaScript, I think this is a really bad idea.

Maybe it has a place in other languages. I really don't want to see it in JS. We don't need more ways to do things implicitly or bass-ackwards from how they're actually behaving underneath. Syntactic sugar rarely makes code more readable. This operator only makes code seem more concise and as if it's executing in a way that it actually isn't.

I can absolutely see junior developers running wild with this kind of thing.

JS should be kept simple. This operator is not simple. It now makes understanding expressions more complicated. JS has its quirks and rough edges, but its power is in its simplicity. Please do not import the mistakes of other languages into JavaScript. If someone wants this operator, they should be forced to use a Babel transform or to compile their own JS interpreter.

OR just compile your favorite language runtime with the pipe operator to WASM.

[+] __ryan__|3 years ago|reply
An alternative is to make the pipe operator a simple function application and provide syntax for creating simple pipeline functions.

For example:

    left |> right
Would semantically translate to:

    right(left)
And you could define a pipeline function like so, where the following:

    const myPipeline = @[
        one(@),
        @.two(),
        @ + three,
        `${@} four`
    ]
Would translate to:

    const myPipeline = (value) => {
        const _1 = one(value);
        const _2 = _1.two();
        const _3 = _2 + three;
        const _4 = `${_3} four`;
        return _4
    }
Or:

    const myPipeline = (value) => `${one(value).two() + three} four`;
And you could define the placeholder value name (which would allow nesting):

    const myPipeline = @it [
        one(@it),
        @it.two(),
        @it + three,
        `${@it} four`,
    ]
You'd combine the two syntaxes to get immediately-invoked pipeline functions:

    // Using a modified example from the proposal:
    envars |> @ [
        Object.keys(@),
        @.map(envar => `${envar}=${envars[envar]}`),
        @.join(' '),
        `$ ${@}`,
        chalk.dim(@, 'node', args.join(' ')),
        console.log(@),
    ]
This is better, in my opinion, than building the '%' placeholder syntax into the pipe operator.
[+] AirMax98|3 years ago|reply
That % syntax is just completely unlike anything else I have seen in JS.

As a multi paradigm language, JS typically suffers from whatever programming style is on trend when these features are implemented. We are apparently on the other side of the pendulum now, but I can’t remember the last time I worked with a class and felt like that was right either.

[+] asciimov|3 years ago|reply
This just complicates things if you have any kind of complex nesting.

Something "simple" like:

   a(b(),c(),d(e(),f(g())))
Turns into the following:

   value |> b() |> a( %, c() , v2 |> e() |> d(%, v1 |> g() |> f(%) ))
[+] zackmorris|3 years ago|reply
Cool stuff, but I miss the "it" variable from HyperTalk (the language used by HyperCard) which contained the result of prompts to the user. Just search for "The it variable":

http://www.jaedworks.com/hypercard/HT-Masters/scripting.html

  ask "How many minutes do you want to play?"
  put it * 60 into timeToPlay  -- convert it into seconds
Today we could have a reserved keyword that holds the result of the last statement executed. A practical example adapted from the article using "it" might look like:

  Object.keys(envars)
  it.map(envar => `${envar}=${envars[envar]}`)
  it.join(' ')
  `$ ${it}`
  chalk.dim(it, 'node', args.join(' '))
  console.log(it);
A better name for "it" today might be "_", "result" or perhaps '$' in a shell-inspired language like php. Most shells support "$?" so that could work too:

  # prints 0
  true ; echo $?
  
  # prints 1
  false ; echo $?
[+] jsnelgro|3 years ago|reply
Kotlin uses "it" as the name of the implicit arg in lambdas. Makes things very readable imo:

``` listOf(1, 3, 5).map { it * 100 } ```

[+] ljharb|3 years ago|reply
it, _, result, and $ are all valid identifiers already; the only choices would have to be syntax errors currently.
[+] mg|3 years ago|reply
I wonder if "%" placeholder is the right approach. It makes the code longer in most cases.

Without pipes:

    a = d(c(b))
With pipes in the proposed form:

    a = b|>c(%)|>d(%)
My first approach to design it would be:

    a = b~>c~>d
So the rule is that on the right side of the pipe operator (~>) there is always a function. We don't need parenthesis to indicate that.

If the function takes more than one argument, it can be defined by another value on the left of it.

Without pipes:

    a = d(c(b,7))
With pipes in the proposed form:

    a = b|>c(%,7)|>d(%)
With the ~> approach:

    a = b,7~>c~>d
[+] charles_f|3 years ago|reply
> three(two(one(value)))

const oned = one(value);

const twoed = two(oned);

const threed = three(twoed);

This proposition goes out of its way to find problems with code that is written in a confusing and uncommon way in the first place.

[+] jibberjabb3r|3 years ago|reply
The proposed pipe operator could only be efficiently implemented via a transpiler pass to lower it to "regular" JS varaibles. I wouldn't want this feature in a JS engine due to the overhead it would require. Sometimes it's better just to say no to new features that yield questionable utility and don't reduce code size or complexity by much.