Syntax upgrade to the pipeline operator proposal

Proposal: GitHub - tc39/proposal-pipeline-operator: A proposal for adding a useful pipe operator to JavaScript.

The pipeline proposal is great and all but it comes with it's own syntax budget; Mainly '%' has a special meaning in pipeline expressions, which I think increases the parse time quite a bit.

Another drawback is that pipeline expressions cannot be nested neatly.

foo |> f(, bar |> /* <details> */ ))

details:  What if we want to access the outer '%' in here??

here's another approach:

const superhero = [] as powers
    |> grantPower("nightvision", powers)
    |> make("invincible", powers)
    |> => x.toUpperCase())

But I should say that the readme of the proposal criticises temporary variables a lot. The as binding here behaves more like the parameter of a function, the key difference being that it's reactive. as is just a stand-in, any other operator might well work.


One interesting thought that stuck me is that this method can co-exist and connect F# and Hack pipelines.

// F# version

    |> functionX
    |> functionY
    |> functionZ

// Hacky version😅
foo as arg
    |> functionX(arg)
    |> functionY(arg)
    |> functionZ(arg)

// each `arg` kind of remains reactive like

Back to smart pipelines😊
(this sort of changes the pipeline operator to pipeline construct, hope no one notices this!)

Issues with active proposals are best filed on the proposal itself.

You may be interested in this specific thread on that proposal, about adding the ability to name the topic variable: Named placeholders · Issue #203 · tc39/proposal-pipeline-operator · GitHub

I just wanted to know the advantages and/or criticism of this idea. Just wanted to see the thoughts of the community.

Well, I would like the ability to be able to name the hack token somehow, as it could be nice to provide that name as context for how some of the pipeline steps work.

However, I don't think naming the hack token once for the entire pipeline is as useful. The meaning of the token would normally change between each pipeline step, so you can normally only give it bland names like "data" or "value", at which point, the value in naming the hack token would only be for nested pipelines.

So, my preference would be to have the ability to name an individual pipeline step when I think it's important to provide a little more context to that individual step, or if I know I'm going to be using a nested pipeline with that single step.

Something like this:

const specialGroupAbilities = getUsers()
  |> %.groups
  |> %.filter(g => isSpecial(g)) as specialGroups
  |> getAbilitiesFrom(specialGroups)

Or any of the other syntax suggestions they provided in the thread I linked to.

The reason for that is better seen here as said by @tabatkins
(the last paragraph)
My preference for identifiers over some punctuator like '%' or '^' has also to do with the grammar and parsing.
currently the grammar would be:

<value> (|> <pipeline_expression>)+

notice the <pipeline_expression>, it's additional baggage; considering the named token approach the grammar looks like:

<value> (as <identifier>)? (|> <expression>)+

way easier to parse because it uses a normal expression.

Thanks for providing your thought! @theScottyJam

Are you saying that you would always be required to give a name to the topic variable? It's not an optional thing? So that parsing can be easier?

If it's required, then I would defer to tabatkin's other paragraphs from the same comment you linked to, about how it's not always possible to provide useful names to the topic variable, and he would rather come up with a standard name than having people invent their own meaningless name everywhere ("x", "value", "data", etc). I also don't think engine implementors are afraid of getting their hands dirty and working with a grammar that's a little more complicated, if that's the kind of grammar the JavaScript community wants. I mean, I'm all for a simpler grammar, but I care more about the end-user experience (though, I'm probably biased here because I don't actually have to work with the grammar).

The current draft spec shows that pipe adds the % into PrimaryExpression.

JS parsers will likely use a flag to detect when the token is incorrectly used outside a pipeBody. This would not be particularly difficult.

so something like:
const bar = foo(%);
can be parsed when outside a pipe? And then the parser throws an error?
Ya that works too😅

I suggested that just for the sake of a neat grammar(and slightly for bringing in the F# pipeline, I mean I still like it😅). It could also be opt in and could help in nested pipelines.

1 Like

Not quite that. It's more the parser would simply know whether it's parsing the RHS of a pipe expression in that context and, when it encounters a % as a standalone expression, rejects it if it's not. It won't actually process the final ) or ; in that case.

Depends on the parser. A parser like eslint might continue to parse after an error, so that it can report as many errors as possible. Rather than just the first one.

In TypeScript, a << #field will be accepted by the parser, and then a post-parse step walks the tree and emits an error. It is done this way so that the parser doesn’t have to special case parsing #field in a.

You might be interested in GitHub - tc39-transfer/proposal-function-helpers: A proposal for standardizing some useful, popular helper functions into JavaScript’s Function object. which proposes a pipe function.

// f# style 
return v |> f |> g |> h;
// becomes
return pipe(v, f, g, h);
// or
return v |> pipe(%, f, g, h);

Was speaking from the perspective of an engine parser. But it's also worth noting that ESLint isn't like JSHint - it won't actually lint files with syntax errors. (It defers to Acorn, and it doesn't use Acorn's loose parser.)

1 Like

Note for others: The Function-helpers proposal was rejected by the Committee last month for being too broad. It has been split up into multiple proposals, including a proposal devoted to a pipe function.

1 Like