tc39 / proposal-pipeline-operator

A proposal for adding a useful pipe operator to JavaScript.
http://tc39.github.io/proposal-pipeline-operator/
BSD 3-Clause "New" or "Revised" License
7.44k stars 109 forks source link

Impact of Hack pipes on the JS functional-programming ecosystem #217

Open js-choi opened 2 years ago

js-choi commented 2 years ago

Spinning this out of https://github.com/tc39/proposal-pipeline-operator/issues/202#issuecomment-919374937.

Lots of people have expressed concerns that Hack pipes would silo the community of JavaScript tacit-programming (aka point-free style). These fears are understandable. And we are sorry for all the frustration that people who like tacit programming have had—feeling like something was promised but taken away. (See #215 and https://github.com/tc39/proposal-pipeline-operator/issues/206#issuecomment-918609518.)

However, we are hopeful that the opposite of siloing will happen: we are hopeful that Hack pipes would actually help increase interoperability between functional APIs and non-functional APIs. We’re hopeful that this increased interoperability would decrease siloing, rather than increasing it.

(By “functional APIs” I do not mean only tacit programming but libraries that identify as “functional APIs”. Also, as a reminder, tacit programming / point-free style is not the same as functional programming. Tacit programming is only a subset of functional programming. In fact, much functional programming is pointful (e.g., with Haskell do notation). The debate over tacit programming versus pointful style has been a long and controversial one in the FP community for many decades.)

The pipe champion group thinks that keeping userland tacit rx.pipe-style functions within internal code is okay. In fact, that’s what F#’s documentation itself suggests:

F# supports partial application, and thus, various ways to program in a point-free style. This can be beneficial for code reuse within a module or the implementation of something, but it is not something to expose publicly. […]

With little exception, the use of partial application in public APIs can be confusing for consumers. Usually, let-bound values in F# code are values, not function values. Mixing together values and function values can result in saving a few lines of code in exchange for quite a bit of cognitive overhead, especially if combined with operators such as >> to compose functions. […]

In contrast to the previous point, partial application is a wonderful tool for reducing boilerplate inside of an application or the deeper internals of an API. It can be helpful for unit testing the implementation of more complicated APIs, where boilerplate is often a pain to deal with. […] Don’t apply this technique universally to your entire codebase, but it is a good way to reduce boilerplate for complicated internals and unit testing those internals.

In other words, it is true that Hack pipes would keep tacit programming in userland functions and not in the language’s syntax, and that is disappointing to many people who like tacit programming. But we are hopeful that Hack pipes would increase interoperability and decrease siloing between functional libraries and non-functional libraries (and, as F# suggests, tacit programming should be kept internal anyway). I know that not everyone in the community shares this view, and we won’t be pushing for advancement for a while anyway, but hopefully this logic can assuage some fears.

(To emphasize, it is likely than an attempt to switch from Hack pipes to F# pipes will result in TC39 never agreeing to any pipes at all; syntax for partial function application (PFA) is similarly facing an uphill battle in TC39 (see HISTORY.md). I personally think this is unfortunate, and I am willing to fight again for F# pipes and PFA syntax, later—see https://github.com/tc39/proposal-pipeline-operator/issues/202#issuecomment-919374937. But there are quite a few representatives (including browser-engine implementers; see HISTORY.md about this again) outside of the Pipe Champion Group who are against improving tacit programming (and PFA syntax) in general, regardless of Hack pipes.)

In any case, the explainer does not talk about the impact that Hack pipes may have. This is a deficiency of the explainer. We need to fix this sometime.

This issue tracks the fixing of this deficiency in the explainer (lack of documentation regarding projected impact of Hack pipes on the functional-programming ecosystem and decreasing of siloing). Please try to keep the issue on topic (e.g., comments about the importance of tacit programming would be off topic), and please try to follow the code of conduct (and report violations of others’ conduct that violates it to tc39-conduct-reports@googlegroups.com). Please also try to read CONTRIBUTING.md and How to Give Helpful Feedback. Thank you!

jridgewell commented 2 years ago

I do not expect beginners to understand this tradeoff at all. Expectations built up from compiled functional languages will not carry over to ours.

Before and after the pandemic, I've yet seen a beginner I've interacted with see hack-style being more intuitive or "better".

The argument I made was that beginners will not understand the performance penalty they are paying, not that people who are being explicitly taught tacit functional programming will find the tacit syntax more pleasant (of course will). But they will not understand the reason the JIT can't inline the closures, or why their computation is thousands of times slower.

Accordingly, I'm not sure why TC39 members and this proposal champions suggest that F# forces "expectations built up from compiled languages" for merely ensuring unary functions are tacit, the most common, simplest, and quickest means to compose/chain.

Again, this was explicitly about the performance penalty because a compiled language can eliminate the closure allocations, where JS will not.

If you did, what exactly are you confused about?

The same thing Tab is confused about. Tacitness at a single expression does not change the entire language and ecosystems that have been built on the way the language is now.

And frankly, it's only terser if your entire program was built with a functional style (no codebase I've ever worked in was built that way). How am I to use all of the non-data-last-functional functions I've written in a F# pipeline? Wrap them in arrow IIFEs? How is that not an accessibility concern? My point, and Tab's, is that Hack integrates better with the entire language, where F# integrates only with functional programming.

I'm of the firm opinion that hack-style representation of pipelining is not tacit enough for the following audiences:

How are these not solved by https://github.com/js-choi/proposal-function-pipe-flow? You can even hoist this out of your hot-path and reap the performance benefits, something that's not possible with F#.

In general, it seems very clear to me that the champions of this proposal and the committee are at an impasse with the JS ecosystem regarding how faithful to tacitness, a core characteristic of functional programming, new JavaScript functional programming constructs should be.

I've been vocally arguing for https://github.com/tc39/proposal-call-this/ because I value the tacit style it allows. I'm, however, not willing to accept a pipeline operator (F#) which we already know before standardizing must never be used in performance critical code, when we can choose one that can work perfectly (Hack).

Call-this allows functional left-to-right tacit data flow, and it doesn't suffer from the death-by-closures performance penalty. But I don't imagine functional programmers will be happy with that one either.

lozandier commented 2 years ago

The argument I made was that beginners will not understand the performance penalty they are paying, not that people who are being explicitly taught tacit functional programming will find the tacit syntax more pleasant (of course will). But they will not understand the reason the JIT can't inline the closures, or why their computation is thousands of times slower.

@jridgewell Thousands of times slower compared to what? Doing explicit manual composition/chaining function3(function2(function1(x))), using wrapperObject(x).pipe(function 1, function 2, function3)), or something else? Like using for-loops vs. functionally iterating, I believe it should be for developers to choose what's best for their scripts.


The same thing Tab is https://github.com/tc39/proposal-pipeline-operator/issues/238#issuecomment-1180908248 about.

How are these not solved by https://github.com/js-choi/proposal-function-pipe-flow?

I explicitly made this clear with the response I pointed to you several times now that Tab doesn't seem to be confused by; I'm genuinely confused about what you're confused about regarding the following excerpt:

The dev audiences I previously mentioned highly anticipated the pipeline operator alleviating the verbosity and library dependence of tacitly communicating functional composition–particularly chaining. Chaining is the most common form of functional composition in JS today. Hack-style requires (^) in a manner that seems arbitrary, unprecedented for a binary operator in the language for its simplest case, and overall very concerning for such audiences.

Existing binary operators are fundamentally and deliberately tacit for their most basic use cases, but hack-style deviates from this. The most basic binary operation involving functions is a functional composition with unary functions. The pipeline operator with hack-style semantics differs from existing binary operators with the requirement of (^) tokens for even the most basic functional composition expression.

JS's existing binary operators are less verbose alternatives to the expressions they abbreviate. That convention is broken with hack-style's current semantics to express functional composition with the most straightforward functions you can compose that are ubiquitous with the conceptual and mathematical concept of pipelining regardless of discipline: unary functions. As I've demonstrated many times, explicitly functionally composed expressions and class mixins expressions are substantially more verbose when you substitute them with the hack-style version of pipelines. This is unprecedented.

Audiences most negatively impacted by this are people who require assistive technology to read/understand code + power users such as those who use Vim and/or frequently chain. Chaining is very common, and hack-style is more verbose than existing alternatives in the language and what framework/lib authors like @benlesh (who created this issue) have manifested with overwhelmingly positive reception.

Hack-style with its (^) token requirements for any functional composition expression makes such expressions longer to write, longer to announce, and arguably longer to reason about once we account for closed or inlined functions.

With all this said, I believe I sufficiently addressed Function.flow proposed by @js-choi as a welcomed lateral move to the tacitness that framework authors such as @benlesh (he created the issue that this response is part of) have provided to millions of devs for years. It falls short of the level of tacitness that |> should succeed as tokens associated with a dedicated functional composition operator to the same task without the hacks (pun intended) hack-style version of the pipeline operator has to accommodate non-functions.

With the pipeline proposal, many wanted the tacitness that Function.flow/.pipe() be succeeded by |> that replaces () in regular compositions elegantly that beginners and experienced programmers understandably expect from a binary operator. Hack-style is unprecedented in being a binary operator that is more tedious to write than what it's supposed to natively abbreviate (function2(function1(x))).

If hack-style is the last-ditch effort to provide a pipeline operator for now; I think it should be on hiatus for now, similar to what happened to container queries in CSS until compilers and champion/committee opinions changed from later enhancements of the language illuminating what to do it to be adequately implemented.

It's arbitrary to release pipeline operators this unorthodox to the JS ecosystem and the expectations of many who wanted it in the language. It seems premature to be ratified without partial application standard and Function.flow() proposals being approved that are very much related to it. Those proposals are much more focused without nearly as much contentiousness associated with their ratification.


How am I to use all of the non-data-last-functional functions I've written in an F# pipeline? Wrap them in arrow IIFEs?

Partial application for starters (a factor of why I think this proposal is premature without it existing in the language first and Function. flow to alleviate the user study concerns I have) ; the well-regarded alternate I provided you already a link to also showed a reasonable solution to this as well

it's only terser if your entire program was built with a functional style (no codebase I've ever worked in was built that way)

I'd refrain from speaking in absolutes about that being demonstrably false by the many examples I and others have shared for years in many issues in this repo who may likely coincidently code functionally more frequently than you. I'd revisit why you think this way to avoid such an extremely false standpoint be iterated again.

jridgewell commented 2 years ago

Thousands of times slower compared to what? Doing explicit manual composition/chaining function3(function2(function1(x))), using wrapperObject(x).pipe(function 1, function 2, function3)), or something else?

Than Hack style, which can be seen as nested function chaining.

Like using for-loops vs. functionally iterating, I believe it should be for developers to choose what's best for their scripts.

The penalty of using a forEach will pale in comparison the over-allocation of closures promoted by F#.

Hack-style requires (^) in a manner that seems arbitrary, unprecedented for a binary operator in the language for its simplest case, and overall very concerning for such audiences… Existing binary operators are fundamentally and deliberately tacit for their most basic use cases

Three is no binary operator that behaves like either F# or Hack pipeline, so there is no precedent in binary operators to break with. There is precedent from your particular flavor of functional language for F# pipeline, and there's precedent from Hack language Hack pipeline. There is no precedent in JS.

JS's existing binary operators are less verbose alternatives to the expressions they abbreviate.

Hack is less verbose than the nested function calls it'll replace. It's more verbose than F#, but that is not an issue.

Audiences most negatively impacted by this are people who require assistive technology to read/understand code

Both Tab and I do not understand this point. How does Hack harm assistive users?

power users such as those who use Vim and/or frequently chain.

As a user of Vim for a decade, I have no idea what is meant by this. How am I negatively impacted by Hack?

Chaining is very common, and hack-style is more verbose than existing alternatives in the language and what framework/lib authors have manifested with overwhelmingly positive reception.

It's more verbose for functional programming users only. It's less verbose for every other type of expression. Given I've never seen a large scale functional codebase in JS in all my years, I'd prioritize making functional programming less verbose as a non-goal.


It falls short of the level of tacitness that |> should succeed as tokens associated with a dedicated functional composition operator to the same task...

Function.flow literally as terse and tacit as possible, so I'm not sure why we should prioritize syntax for it.

without the hacks (pun intended) hack-style version of the pipeline operator has to accommodate non-functions.

Hack literally helps with every function call, except for data-last curried functions. The overwhelming majority of code, I'd say. You're literally arguing for a worse experience for the majority of the language because you can't see past your niche. Closure-returning unary functions is not so prevalent that we should be ignoring every concern the committee has raised.

Hack-style is unprecedented in being a binary operator that is more tedious to write than what it's supposed to natively abbreviate (function2(function1(x))).

Easier to write? And how about read? Having to maintain a nested parens counter in my head while I parse someone else's is a major pain point in language. Hack's x |> function1(^) |> function2(^) is considerably better.

It's arbitrary to release pipeline operators this unorthodox to the JS ecosystem and the expectations of many who wanted it in the language.

Again, it helps with everything except the niche data-last curried function. It's orthodox with the rest of the ecosystem.

It seems premature to be ratified without partial application standard

As mentioned in my previous comments, the committee is reluctant to add syntax that will have large performance penalties. Partial application is going to have its own uphill battle without even adding the baggage of F# pipeline.


Partial application for starters (a factor of why I think this proposal is premature without it existing in the language first and Function. flow to alleviate the user study concerns I have)

Comparing x |> fn(?, y) partial application and x |> fn(^, y) hack, they look like two syntaxes that achieve the same thing, except that fn(?) has the added baggage of creating closures. It's additional uses create the performance footguns we should be trying to avoid with syntax.

I'd refrain from speaking in absolutes about that being demonstrably false by the many examples I and others have shared for years in many issues in this repo who may likely coincidently code functionally more frequently than you.

Demonstrably false? Please link to large scale JS codebases that are using functional programming extensively. I would be flabbergasted to find that functional style is anything more than a niche.

lozandier commented 2 years ago

Than Hack style, which can be seen as nested function chaining.

Majority of people using the language today (including beginners from my experience) want merely a tacit functional composition operator; like I said–since hack-style intends to be utterly unorthodox to this–I think it should not be the only pipeline operator in the language, nor be at the expense of such a pipeline operator to substantially make more useful existing functional programming constructs like arrow functions and make a variety of functional programming patterns more prevalent discussed very thoroughly in #238 and #237.

Suppose this is too contentious or impossible to accommodate with the current champions and committee. In that case, serious consideration should be made to have pipeline operator on hiatus till improvements to JS compilers + ecosystem sentiments of related and more focused proposals can be evaluated, such as Function.pipe() and the proposal related to partial application. It's premature for hack-style to be ratified in the language until then, in my opinion.

Container queries in CSS benefited from this approach; pipelining being similarly transformative that dealing with uphill battles of getting consensus on how it should work technically and semantically.

Similarly, in JavaScript history, ES4 constructs like classes and class fields benefited from being postponed.


Hack is less verbose than the nested function calls it'll replace. It's more verbose than F#, but that is not an issue.

This is demonstrably false again, which is something raised extensively in #238 and #235. Another excerpt from the former helpfully presents why this is.

@tabatkins from what I understand @benlesh is referring to .pipe() which is 7 characters. Function.pipe proposed by @js-choi makes the matter worse by adding 8 additional characters needed for a total of 15 characters to alternately communicate method chaining (first-class functional composition) in a tree-shakable-friendly way without objects. This is before the additional 3 characters per function at minimum hack-style requires!

This puts a lot of tax attempting to alternately communicate chaining in a readable matter by arguably fundamentally being directly opposed to that with the additional non-white characters required that adds up in a large code base.

The following code uses 7 non-white-space characters

f(g(x)) 

The following pipeline behavior would require the same amount of non-white-space characters

x |> g |> f

Hack-style currently requires 14 non-white-space characters (double the amount of characters!)

x |> g(^) |> f(^)

This is a serious concern for those who use assistive technologies to input JS that primarily solves problems using JS that involves a lot of data pipelining like myself and others that solves data science problems at enterprise scale in addition to functional programmers + those that want to chain in JS easier (terser) or clearer without objects.

For this reason and other reasons I've extensively raised, I think the bubble of hack-style's verbosity not being an issue should be debunked.


Three is no binary operator that behaves like either F# or Hack pipeline, so there is no precedent in binary operators to break with. There is precedent from your particular flavor of functional language for F# pipeline, and there's precedent from Hack language Hack pipeline. There is no precedent in JS.

Uh… False. I've demonstrably explained this very thoroughly in the links I've already provided you:

JS's existing binary operators are less verbose alternatives to the expressions they abbreviate. That convention is broken with hack-style's current semantics to express functional composition with the simplest functions you can compose with that are ubiquitous with the conceptual and mathematical concept of pipelining regardless of discipline: unary functions. As I've demonstrated many times, explicitly functionally composed expressions and class mixins expressions are substantially more verbose when you substitute them with hack-style's representation of pipelines. This is unprecedented.

As far as I know, functions don't use parenthesis unless invoked anywhere else in JS. Hack-style breaks this, and I don't see the benefit. The style requires (^) tokens or parentheses on the simplest composition expressions–breaks tried and true search/replace conventions and adds cognitive noise that doesn't occur with other syntax involving functions in the language.


Easier to write? And how about read? Having to maintain a nested parens counter in my head while I parse someone else's is a major pain point in language. Hack's x |> function1(^) |> function2(^) is considerably better.

From my experience beginners, data science/ML colleagues, and various other programmers who are familiar with tacit functional composition encouraged by popular functional programming, and myself don't have such an experience.

As I stated many times in our debate, |> more than suffices to communicate what (^) does from my experiences and observations; existing binary operators for primitive values have long exposed developers in JS to F#-style's level of tacitness that is being arbitrarily rejected for pipelining by Hack-style instead for an innate disadvantage functional programming constructs have vs. other paradigm constructs when it comes to performance.

Function.flow literally as terse and tacit as possible, so I'm not sure why we should prioritize syntax for it.

It isn't and #238 and the original comment I provided thoroughly debunks this. What's more terse/tacit than Function.flow is the far more tacit representation of functions via F# pipelining such as input |> func1 |> func2 that matches the non-space characters of composing with () and other userland abstractions. An essential trait of programming functionally is reaching that level of terseness ("point-free" programing). This majority of people that initially propelled the pipeline operator to be this desired in the first place are not accommodated by hack-style.

I encourage you to find time to enlighten yourself about that.


Both Tab and I do not understand this point. How does Hack harm assistive users?

Again the following succinctly answers that for you, in my opinion:

Hack-style with its (^) token requirements for any functional composition expression makes such expressions longer to write, longer to announce, and arguably longer to reason about once we account for closed or inlined functions.

This is further exacerbated by those that leverage braille; this is not unlike the problems of using two spaces versus tabs for those that are non-maliciously ignorant of such caveats. For these reasons, and those that compose a lot like me for data science/ML use cases, tacitness is very much beneficial with introducing new operators as a core characteristic that hack-style pipelining outright omits, unlike other binary operators in the language.

I cannot emphasize enough that JS's existing binary operators are less verbose alternatives to the expressions they abbreviate; hack-style again violates this latently communicating functions should be an exception to such tacitness ("point-free") that a meaningful amount of the JavaScript community disagrees with.


As a user of Vim for a decade, I have no idea what is meant by this. How am I negatively impacted by Hack?

As a decade Vimist myself, there are several issues with hack-style that I again pointed out extensively in #238 about how it impacts power users such as ourselves, in addition to what I just said in the previous rebuttal:


|> Demonstrably false? Please link to large-scale JS codebases that are using functional programming extensively. I would be flabbergasted to find that functional style is anything more than a niche.

From my employer alone, most modern Angular projects used by millions of users leverage reactive functional programming constructs heavily via RxJS; it is very much desired for a more tacit representation of such code via a native functional composition operator the pipeline operator was supposed to be.

That said, I have a suspicion you have a narrow perspective on what functional programming is. In the real world, code rarely is without patterns of multiple paradigms at play. For this reason, I'm confused why people are adamant about describing or identifying themselves as a developer using a specific programming paradigm. I guess the everyday person is used to such tribalism behavior with how people associate themselves with political parties and sports teams.

All that said; similar to quotes vs. backticks and how seamless to switch between the two, I'm adamant a florian's compromise is code such as the following:

// Example in Readme from react/scripts/jest/jest-cli.js with the two pipeline operators utilized
envars
|> Object.keys
|>> ^.map(envar => `${envar}=${envars[envar]}`)
|>> ^.join(' ')
|>> `$ ${^}`
|>> chalk.dim(^, 'node', args.join(' '))
|> console.log;

// |> is explicit pipelining (tacit functional composition/F#-style)
// |>> is implicit pipelining (topic-style/Hack-style)

// Note that ligatures conventionally exist for |> and |>> in typefaces designed 
// for programmers. In performance-critical apps/scripts, |> can be banned 
// by linters similar to native array methods for iteration use cases because the
// intermediate arrays they create without userland variants w/ tranducers. 

Unfortunately, it seems you and other committee members are adamantly against more tacit functional constructs being in the language despite the rapidly increased prevalency of functional programming today that is in need of more tacit means of composing functions that led to the demand of the operator in the first place; I'm very unconvinced about your readability concerns that is not cosigned by a majority whatsoever in #225. You claim such readability familiarity is a niche, but it can be argued you aren't "reading the room" not considering that your limited capability to find tacit functional programming readable is contradictory to the familiarity of such things by modern JavaScript developers today. Accordingly what is going on can be seen as gatekeeping from a superminority position. To your credit, you at least own this:

I disagree, this is exactly what the committee is for. Developers can already do higher order functional programming via userland implementations. We're the ones that have to decide what to put in the the language with limited syntax budgets.

Even the most popular JS framework today (React) is functional to the extent such tacitness is not abnormal, as well as others like Angular (reactive functional programming constructs) that are less functional but very much exposes their developers to tacit functional programming via .pipe and etc by its abstractions and RxJS.

A meaningful amount of people want to tacitly compose with functions with the same level of tacitness primitive values have with binary operators such as +, /, and = (which are by no coincidence functional commands); I find it very unfortunate you're adamantly against that.

As for myself, please refrain reducing me being an advocate for functional programmers in further discussions about this; I've made this very clear in the response I originally provided you to review:

Being part of Google's responsible innovation team, I'm not only attempting to emphasize with the meaningful amount of JS developers that identify as "functional programmers" who may not have always communicated their objections the best way to the champions about this proposal (I emphasize being a minority member of the JS community) or data scientists who increasingly use JS to solve problems associated with artificial intelligence or machine-learning: I'm also advocating for general problem solvers who wish to solve their problems in a pipeline manner in the matter that the pipeline paradigm is ubiquitously understood as being in behavior.

General problem solvers include beginners and various members of academia that very much would like to see a more tacit means of composing functions in the language as well. Hack-style does not accommodate them either regarding that.

jridgewell commented 2 years ago

Majority of people using the language today (including beginners from my experience) want merely a tacit functional composition operator

Based on what? This is not the way the committee is interpreting this. We're proposing an operator that accomplishes left-to-right flow, which we think is the core desire.

nor be at the expense of such a pipeline operator to substantially make more useful existing functional programming constructs like arrow functions and make a variety of functional programming patterns more prevalent

Again, the committee is rejecting this as a non-goal. I reject this, if for no other reason than the massive performance penalty this will incur.

Suppose this is too contentious or impossible to accommodate with the current champions and committee. In that case, serious consideration should be made to have pipeline operator on hiatus…

Throwing the entire proposal out because you don't get everything your way isn't a productive way to run a committee.

…till improvements to JS compilers

The committee has expressly designed for "one JS", meaning we do not want to promote compilers and other build systems as the only way to write effective JS. Having a feature which can only be implemented effectively via a build step is not going to be popular.

This is demonstrably false again, which is something raised extensively in #238 and #235. Another excerpt from the former helpfully presents why this is.

Cool. For any on contrived example (multiple or complicated arguments), code is split into multiple statements:

const gOfX = g(x)
const fOfGOfX = f(gOfX)

So, x |> g(^) |> f(^) is in fact less verbose than the code it will be replacing. But again, terseness is not the ultimate goal.

JS's existing binary operators are less verbose alternatives to the expressions they abbreviate.

Remind me again which binary operator invokes its RHS with the value of its LHS? I think our mental models for how binary operators work is very different. There is no precedent to break.

As far as I know, functions don't use parenthesis unless invoked anywhere else in JS. Hack-style breaks this, and I don't see the benefit.

How is the f(^) in x |> f(^) not seen as a function invocation?

From my experience beginners, data science/ML colleagues, and various other programmers who are familiar with tacit functional composition encouraged by popular functional programming, and myself don't have such an experience.

The code example was comparing function1(function2(x)) vs x |> function1(^) |> function1(^). It's not comparing tacit pipeline. As such, Hack is easier to parse than the original, because we do not need to maintain nested state. Eg,

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

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

I think it's clear which one is easier to parse.

existing binary operators for primitive values have long exposed developers in JS to F#-style's level of tacitness that is being arbitrarily rejected for pipelining by Hack-style

Please provide an example of these binary operators.

What's more terse/tacit than Function.flow is the far more tacit representation of functions via F# pipelining such as input |> func1 |> func2 that matches the non-space characters of composing with () and other userland abstractions.

input |> func1 |> func2 looks pretty similar to pipe(input, func1, func2)… We can even make it "more terse" by renaming to p(input, func1, func2). So again, this is pretty damn terse, and tacit. It's almost like we don't need to privilege this usecase with syntax, because it's sufficiently solvable with userland code today.

This is further exacerbated by those that leverage braille; this is not unlike the problems of using two spaces versus tabs for those that are non-maliciously ignorant of such caveats.

The choice between Hack and F# does not change the rest of the entire language. This is not going to hurt braille users any more than calling f(x) would, which they must already have to do because that's how literally everything is programmed today.

As a decade Vimist myself, there are several issues with hack-style that I again pointed out extensively in #238 about how it impacts power…:

None of these are real issues:

  • Creates substantial noise of what's being invoked

The difference between f(x) and f(^) in the call RHS of a pipeline is not "substantial noise". Compared to f in the RHS of a pipeline, sure it's not as terse, but it's not a substantial change from the langauge we have today.

  • You can't merely do d + w to delete functions.

Are you forgetting about the |> that also needs to be deleted, and isn't covered by the w textobj?

  • It makes intermediate functional functions very much common in JS today (closures and the implicit currying knowledge needed to maximize them in interviews and everyday code in many tech establishments) unnecessary hard to read

Good, we want to actively discourage that kind of performance penalty.

From my employer alone, most modern Angular projects

Angular's static template expressions are not JS. And as far as I'm aware, they don't allow the intermediate function closures that we're trying to avoid. https://github.com/tc39/proposal-pipeline-operator/issues/217#issuecomment-1183915890

used by millions of users leverage reactive functional programming constructs heavily via RxJS

Let's focus on actual product codebases, not libraries. https://github.com/tc39/proposal-pipeline-operator/issues/217#issuecomment-1183915890

Unfortunately, it seems you and other committee members are adamantly against more tacit functional constructs being in the language despite the rapidly increased prevalency of functional programming today that is in need of more tacit means of composing functions that led to the demand of the operator in the first place;

I, specifically, am against the performance penalty that this style of programming will encourage.

Even the most popular JS framework today (React) is functional to the extent such tacitness is not abnormal

Personally, I find the functional programming that redux encourages to be the least understandable part of modern react. Core react has no such crap.

A meaningful amount of people want to tacitly compose with functions with the same level of tacitness primitive values have with binary operators such as +, /, and = (which are by no coincidence functional commands);

You're gonna have to explain this one.

kawazoe commented 2 years ago

From my employer alone, most modern Angular projects

Angular's static template expressions are not JS. And as far as I'm aware, they don't allow the intermediate function closures that we're trying to avoid.

used by millions of users leverage reactive functional programming constructs heavily via RxJS

Let's focus on actual product codebases, not libraries.

Have you ever used Angular? Because this kind of answer makes it looks like you didn't even took the time to look at their sample application... completely disconnected from the reality of people writing actual application code...

Everything in Angular uses Rx Observables instead of Promises. It is designed for you to to use pipe everywhere with all of the different operators RxJs provides; all of them creating closures to produce the unary functions the pipeline mechanism expects.

You want to monitor the changes of a value in your template? That's a pipe. You want to react to the result of a network call? That's a pipe. You want to know which page the router is at? That's a pipe. You want to set a value in your template from a centralized store. That's a pipe. You want to control the progress of an animation? That's a pipe.

Everything in an Angular app gets wrapped up in functional style code, creating more closures every single time. If you're not doing this, you end up with race conditions everywhere in your application. It is meant to be used this way.

I am with @lozandier on this one. My last project was a mobile application built with Ionic 5 + Angular + NgRx. Most modules of our app made heavy use of RxJs because Angular is designed for your to do so and those pipelines could go for hundreds of lines. We had over 250klocs in the project, nothing was lazy loaded, and it worked without a hitch on a 5 years old Android phone.

I am sorry to say this but... with an answer like this, you have lost all credibility to me...

jridgewell commented 2 years ago

Because this kind of answer makes it looks like you didn't even took the time to look at their sample application...

My only thought was of the places that use the tacit style as in https://angular.io/guide/pipes. The dialect is only in the templates, and I should have considered that RxJS is used throughout Angular apps because it's encouraged by the core lib.

ctcpip commented 2 years ago

Gentle reminder that all participants are expected to abide by the TC39 Code of Conduct. In summary:

lozandier commented 2 years ago

Based on what? This is not the way the committee is interpreting this. We’re proposing an operator that accomplishes left-to-right flow, which we think is the core desire.

@jridgewell You missed essential feedback on why the proposal is being desired then. I again explicitly addressed this in #238.

As I stated throughout that issue: Left-to-right functional composition flow is quintessential to any pipeline operator, and very much right after it–facilitated by the fact that functions are first-class values in JavaScript–enabling tacitness that’s impossible by userland abstractions is very much expected and desired to align with the existing binary operators in how tacit it is.


You’re gonna have to explain this one.

For beginners and experienced developers alike (anyone who passed high school algebra, really)–as well as potential/casual JS users well versed in composition via disciplines such as general Math, Statistics, and so on I work frequently with–it’s confusing to them why they can’t abbreviate composing functions as tacit as you can existing binary operators such as * for primary values (i.e. 2 * 2)

The tacit equivalent in math for functions is the functional composition operator (∘); it allows the same level of brevity to represent composition with functions, as demonstrated straightforwardly on wikipedia:

In mathematics, function composition is an operation ∘ that takes two functions f and g, and produces a function h = g ∘ f such that h(x) = g(f(x)).

Accordingly, it can be argued that hack-style breaks the principles of least surprise. You have to use |> and (^) (which may very well now be ($_) by the way, based on the recent developments of #91) with hack-style.

I find it trivial to explain to multidisciplinary beginners and developers that |> is the JavaScript equivalent to the ∘ operator, matching the tacitness of existing binary operators. In comparison, Hack-style is very hacky (pun intended) to a meaningful amount of people; it’s a frequent thing raised by research stakeholders in my org seeing such code at a glance. Hack-style doesn’t embrace that functions are first-class values in the language well.

Again, this includes people who are casual users of JS and will use it on things like Google Sheets’ AppScripts. I immediately picked up such issues when such people were exposed to hack-style in contexts as casual as those. Accordingly, I’m adamant you and other committee members have very noticeable gaps in your understanding of what developers and general people primarily want/expect from the operator.

A significant number of developers want a more fluent, tacit language to use; hack-style is a considerable step back from this expectation, making more verbose widespread expressions in JS and math: Chaining and composition.

Working on data processing/ML pipelines and cloud functions frequently for my job, I’ve greatly benefited from modeling things in a pipeline matter left to right. It’s ubiquitous doing so to solve the problems I solve. The pain points colleagues and I have more have to do with the lack of a more tacit means of expressing such things that coincidently mirrors the pain points that @benlesh pointed out in #238, who already maintains the non-native equivalent to Function.pipe() for his popular library in the JavaScript ecosystem.

Hack-style merely as a way to show processing left to right that is more verbose than composing and chaining with what exists today natively in the language–and what’s available by the existing JavaScript ecosystem–is very much useless to such a demographic as it is for the functional programming ecosystem.


Throwing the entire proposal out because you don’t get everything your way isn’t a productive way to run a committee.

… Isn’t that the reason we have proposal stages for this to happen potentially beneficially for the language progressing with well-scrutinized features? Some proposals are too contentious to be done, which is meant to be discovered during the process as a recognized outcome of the proposal.

This proposal is reaching that point with a pipeline operator fundamentally incompatible and at odds with the current JavaScript ecosystem.

According, it again makes sense to postpone this proposal until the partial application proposal and pipe proposals are ratified that are much more focused, ensure that the operator isn’t intrusive to their designs, and for such proposals to positively influence this proposal that significantly benefits from the findings and reactions of those proposals in the wild before it’s ratified ideally.

Hack-style prematurely adds a placeholder token that should probably be sorted out by the partial application proposal first. The fact that it may [now become two characters to signify just a placeholder]() ($_) is a huge red flag to me.

Any way you look at it, ($_) for every function in a pipeline expression instead of using nothing (|> replacing the need for ()) is a HUGE tax to pay to show composition and chaining left-to-right for developers regardless of their cognitive and physical ability

I’m increasingly convinced it’s harmful to the language to be forced on expressions merely communicating unary functional composition, which is very common for people across many disciplines and industries that want to use or have to use JavaScript for some of their problems that need to be done using a JavaScript runtime.


The committee has expressly designed for “one JS”, meaning we do not want to promote compilers and other build systems as the only way to write effective JS. Having a feature which can only be implemented effectively via a build step is not going to be popular.

I did not suggest a build step whatsoever. You’re arguing a point I did not make; it seems you’ve misconstrued me with my use of “JS compilers.” I’m talking about native JavaScript engines such as V8, Spidermonkey, and JavaScriptCore.

I apologize for confusing you here.


I, specifically, am against the performance penalty that this style of programming will encourage. Good, we want to actively discourage that kind of performance penalty

…Stunting the growth of the functional programming paradigm on the platform as though it will cease to exist if you neglect it enough is not helpful. I firmly believe that an essential aspect of managing a multi-paradigm language is equitably improving the supported paradigms.

There are tons of beneficial things in JS and its ecosystem that are functional or depend on things heavily invested in being coded in such a way. This stance substantively limits these things from getting immeasurably substantially better, dragging the entire Web and device ecosystems down in how good they can be since JS is the only core programming language you can currently use on the Web and the most reliable programming language devs can rely on being available on most devices.

Class-oriented programming paradigm needed transformative features like classes, class fields, and private methods to be significantly more helpful for those invested in coding that way and those who have to deal with that style of code against their wishes. Functional programming needs to be equitably similarly improved for it to be easier to use for people who are committed or stuck with using its constructs in scripts which isn’t uncommon at all whatsoever.

Despite its inherent flaws in performance, functional programming is here to stay. There’s more to good code than pure performance metrics; performance isn’t everything. Functional programming is more prevalent than ever, despite how disproportionately it’s accommodated by committee members such as yourself.

Regardless, the solution I provided you in my previous response is an explicit example of substantially minimizing your performance concerns while eliminating both sides’ contentiousness. The con is merely the burden on implementers that have already overstepped/exploited their influence like what the V8 team did to ES2015 tail calls in the minds of many.

Like the Go compiler implementers giving in to the needs of a meaningful amount of developers to support generics despite its inherent performance issues as a construct and the burden on implementers, the pros of having a tacit functional composition variant of the pipeline being available to the JS ecosystem outweigh the cons–the same thing applies not having a pipeline operator if it means we’re stuck with hack-style indefinitely to respect the current stage of the pipeline operator.

This generation of committee members doesn’t seem ready to do so with the pipeline operator; accordingly, I’m increasingly convinced it’s not a good idea for the pipeline to continue as is when it’s premature to do so when Function.pipe() and Partial application features can ship before to meaningfully guide the eventual ratification of this proposal later in time.

There’s no mistake people want more fluent code being possible in JS; Hack-style is a step back regarding that for everyone being more verbose than what it’s replacing—which may be worse based on recent developments that may suggest the placement placeholder may require two characters such as $_ ) indicated recently by @js-choi.

The community behind a language is vital, and hack-style currently lets many meaningful people down with such semantics in its pursuit to avoid functions being composed as tacit as other binary operators that already exist in the language.

When I read stances like yours, I begin to wonder if people overseeing the pipeline proposal think hack-style will release as is towards suddenly an alternate universe existing in which it’s not incompatible with a meaningful amount of the JS ecosystem and how millions of developers functionally compose in a pipeline manner today.

Those are both red flags that it probably shouldn’t proceed without tweaks.


The difference between f(x) and f(^) in the call RHS of a pipeline is not “substantial noise”.

I’m not sure where you’re getting f(x) from; it’s f vs f(^) (or now f($_) apparently).

For contrived closure functions like add(2)(2); the difference would be

the following code snippet

2 |> add(2) |> console.log  

being compared to the code snippet below:

2 |> add(2)($_) |> console.log($_) // $_ replacing ^ now

Regardless the hack-style representation of pipelining is undesirable, being far more tedious to read, type, announce and understand than just the following code snippet.

console.log(add(2)(2));

This is again unprecedented for a binary operator in the language. Accordingly, I’m of the firm opinion that Hack-style’s semantics for unary functional composition expressions is not helpful for a meaningful amount of users accounting that won’t go more advanced than that with their practical composition needs.


const gOfX = g(x)
const fOfGOfX = f(gOfX)

So, x |> g(^) |> f(^) is in fact, less verbose than the code it will be replacing. But again, terseness is not the ultimate goal.

I don’t think that example is that convincing whatsoever. You showed off hack-style is more tacit than very contrived imperative code (why not explicitly compose the two functions; what do you need two variables for this?) yet more verbose than native represention of that composition (g(f(x))) and userland abstractions that are more tacit than the hack-style representation as well using endofunctor.pipe() and so on.

Most functional code today is more tacit than that example and hack-style, which is now more likely,based on recent developments in #91, the following:

input |> func1($_) |> func2($_)

I’m not convinced this is a direction the language should go whatsoever. The typing complexity associated with that coding example makes my ring and pinky fingers hurt just glancing at this.

I can’t help but think the following thought that’s similarly echoed by beginners–as well as industry colleagues I’ve shared hack-style syntax with–when I see code like this: Is JavaScript trying to be as verbose as Java in an attempt to block point-free programming? It’s a regrettable situation.

I never thought hate for a particular programming style could be so deep to believe such a verbose alternate is OK. I want JavaScript only to be similar to Java with its first four letters, please–for the sake of the ecosystem.

Hack-style as-is is contradictory to this.

Edit: After it was clarified a more independent route of addressing my concerns can be feasible in the future, contrary to what I interpreted from strong stances expressed to me recently, I will bow out of these discussions indefinitely accordingly. My feedback regarding hack-style pipes impact on the functional-programming ecosystem can always be revisited whenever it next makes sense.

arendjr commented 1 year ago

Thanks @lozandier for relentlessly defending against the Hack proposal.

I don’t even consider myself a functional JS developer. I don’t care much about partial application, and I don’t go out of my way to use immutable data structures (though I am looking forward to JS having proper immutable data structures in the future… until then TypeScript’s readonly annotations will have to suffice…)

But I am doing a lot of tacit programmingplain functions. I mostly write TypeScript code with React and Redux and have worked on several codebases over the years. The last time I seriously used classes in JavaScript was right before React introduced hooks. I do still use TypeScript interfaces and every now and then I pass objects with functions, but the class and new keywords have totally fallen from grace. I don’t think anybody hated them, it’s just that we don’t really need them anymore. So we keep it simple.

@jridgewell wrote:

Personally, I find the functional programming that redux encourages to be the least understandable part of modern react. Core react has no such crap.

That may be your opinion, but in my experience, that doesn’t jive with how most JavaScript developers see or use the language. In fact, your use of language here to me suggests you may be severely out-of-touch with the landscape.

As for the Hack-style proposal, I’ve made my protest known before: I agree with @lozandier that I see more harmful effects to the language than I see benefits. I’d rather have no pipe operator at all than Hack.

tabatkins commented 1 year ago

I will once again remind everyone that TC39 has a Code of Conduct, and comments such as the one I've just deleted are obvious violations of such and will not be tolerated.

mAAdhaTTah commented 1 year ago

Personally, I find the functional programming that redux encourages to be the least understandable part of modern react. That may be your opinion, but in my experience, that doesn’t jive with how most JavaScript developers see or use the language.

It jives with my experience, and I'd suggest the development of RTK to simplify a lot of the boilerplate involved in Redux is evidence to support that assertion. RTK is also taking a lot of inspiration from react-query/SWR to simplify that boilerplate, and only uses thunks, rather than any of the more complex/functional approaches to side effects.

I think it's also notable that React core did not integrate any of the purely-functional approaches currently in the JavaScript ecosystem when adopting Hooks. While they're inspired by Algebraic Effects, Hooks are only inspired by them, rather than integrating them whole cloth, and the pain of dealing with higher-order components, render props, etc. is a big part of what pushed them in that direction.

"In that sense, I believe the Hack proposal is very much in spirit of React, in that it brings the substance of FP techniques without the ceremony." - Dan Abramov.

arendjr commented 1 year ago

@mAAdhaTTah I think we’re talking past one another here. As I mentioned I’m not a functional JS developer, so arguments about purity don’t resonate with me. But as I said, I do use a lot of tacit programmingplain functions, which is encouraged by React, and I use some functional programming when it comes to Redux, despite also using thunks for side-effects and happily using Immer where it makes sense.

The thing is though, as I also mentioned when I talked about readability concerns, I don’t think most imperative code examples get improved by Hack at all. And they wouldn’t be improved by F# either, so quoting Dan in that context doesn’t work for me either.

I actually like plain, simple, imperative, tacit functions for most things. No pipes, no hassle.

I was slightly sympathetic towards Minimal/F# exactly because I felt it was limited to well-scoped use cases, but somewhere it appears some convinced themselves that all code should be written as pipes, which is a position I would not embrace regardless of which proposal.

I’m really fine without pipes. And that, I genuinely feel, is the position that almost all React developers I interact with share.

mAAdhaTTah commented 1 year ago

Dan in that comment elaborates on how everything is function calls, and Hooks in particular are not tacit, by and large, so no, I don't think tacit programming is encouraged by React.

arendjr commented 1 year ago

@mAAdhaTTah I think this is splitting hairs, and it’s mostly semantics irrelevant to the pipe proposal, but that’s not what I get from that comment at all. Tacitness is not the same thing as pure functional programming. In my understanding, tacitness relates to using point-free programming and avoiding inner mutation. Those things are encouraged by React, including hooks. Hooks produce side-effects, so they’re not pure, but that’s not relevant to my point. Edit: This was mainly my misunderstanding of tacitness (see below).

mAAdhaTTah commented 1 year ago

In my understanding, tacitness relates to using point-free programming and avoiding inner mutation.

Mostly correct (tacitness isn't related to inner mutation, just point-free style), but hooks are not typically called tacitly. They're called the "standard" way, with parens, and with their arguments/"points" explicit. So no, React itself does not encourage tacit programming.

I'm also not splitting hairs. I'm drawing parallels between React's design philosophy & Hack pipeline's design philosophy. Both are attempting to take inspiration from approaches & techniques outside of JavaScript mainstream (or JS entirely) and modify them to fit more cleanly into the language, given its affordances.

Looping back to where we started:

Personally, I find the functional programming that redux encourages to be the least understandable part of modern react.

That may be your opinion, but in my experience, that doesn’t jive with how most JavaScript developers see or use the language.

If we can accept that the React core team has a much broader view of the ecosystem than we do, then it speaks volumes that they explicitly decided to forgo a more tacit/functional approach when introducing Hooks, which says to me that JavaScript developers do, in fact, "find the functional programming that redux encourages to be the least understandable part of modern react". Redux itself adding hooks while RTK forgoes higher-order components & mapStateToProps & uses immer for immutable updates with mutable code all support this argument.

arendjr commented 1 year ago

@mAAdhaTTah Yikes. It appears I’ve been using the term tacit entirely incorrect then. Somehow “point-free” had been warped into preferring functions over methods (ie. avoiding the dot — Dutch: punt). Apologies for the confusion.

But yeah, what I meant to say was that I primarily use plain functions that are invoked imperatively. Despite being influenced by functional programming (avoiding mutation, the occasional higher-order function), I don’t go all-in on the functional style.

That is the style I have mostly adopted thanks to React and Redux. Hooks are fully compatible with that. As is Immer, because despite using mutation-style syntax, it upholds the immutability at a higher level, which is the part I care about (side note: this is also one of the things I love about Rust, because it gives you mutability guarantees, without needing special immutable data structures and allowing mutable operations at the local level… kinda like Immer without the downsides).

So that’s what the majority of my (and around me, most people’s) coding: plain functions and immutable data.

But that coding style also doesn’t really get improved with a pipe operator. There is the odd exception, of course, but I really wouldn’t want pipes in most of my code. For the exceptions, the F# version would be fine. And because it’s so limited in its scope, that would be the only place where it gets used. The Hack version is so flexible that I frankly would eliminate it entirely through ESLint to avoid the bike shedding over which exception warrants its use and which doesn’t.

mAAdhaTTah commented 1 year ago

Ah, that explains a lot. I will say, it sounds like we code fairly similarly, but I have found that, even if I'm not doing "deep" FP-style code, if I lean on functions a lot, I still end up writing sequences where the intermediate values don't matter. But we've discussed this in depth elsewhere so I won't continue here. The main thing I wanted to emphasize is that the design of React and the design of the Hack pipe draw from a similar underlying philosophy.

temoncher commented 1 year ago

I really like the terseness of F# pipes and don't want to give it up hard, but I can see how hack pipes bring a lot to the table when using non-functional JS features like class constructors, calling non-curried functions.

I don't know whether there were a discussion about that already, but this hack pipe kind of looks like a combination of F# pipe and one specific Scala underscore syntax usage:

// in scala you can do
val prices = Seq(10.00, 23.38, 49.82)
val pricesToInts = prices.map(_.toInt)
assertEquals(pricesToInts, Seq(10, 23, 49))

I was wondering maybe this proposal can be split up into two things:

  1. Some kind of % syntax like
    ['     a  ', 'b', '  c ', '      ']
        .map(%.trim())
        .filter(Boolean);
  2. Actual F# pipe proposal

After that we could achieve something similar to hack pipes with

['     a  ', 'b', '  c ', '      ']
|> %.map(%.trim())
|> %.filter(Boolean)

while still supporting terse syntax

import { filter, map, trim } from 'whatever-fp-lib.js';
['     a  ', 'b', '  c ', '      ']
|> map(trim)
|> filter(Boolean)
temoncher commented 1 year ago

After thinking about it more I can see that this potential % proposal will only work with methods and won't work in any cases mentioned in the README of the pipe proposal 😞

value |> foo(%) for unary function calls, // won't work, is `bar(foo(%))` a `bar((x) => foo(x))` or `(x) => bar(foo(x))`?
value |> foo(1, %) for n-ary function calls, // the same as above
value |> %.foo() for method calls, // actually works
value |> % + 1 for arithmetic, // `% + 1 + 2` -> `(x) => x + 1 + 2` or `((x) => x + 1) + 2 // whatever that is`
value |> [%, 0] for array literals, // `[[%, 0]]` -> `[(x) => [x, 0]]` or `(x) => [[x, 0]]`
// rest have the same problem with nesting
value |> {foo: %} for object literals,
value |> `${%}` for template literals,
value |> new Foo(%) for constructing objects,
value |> await % for awaiting promises,
value |> (yield %) for yielding generator values,
value |> import(%) for calling function-like keywords,

I just find it weird to have a special symbol that only works inside a pipe chain.

'    a' |> %.trim() // I can do that, % behaves like (x) => x.trim()
// but
['    a'].map(%.trim()) // I can't do that, % doesn't exist
tabatkins commented 1 year ago

Yup, you're essentially reinventing the Partial Function Application proposal ^_^ There are unfortunately still several issues with this: it's extremely difficult to generalize past function-calling without some wild grammar contortions, as you've found; it doesn't work with yield or await at all, or any other future function-scoped syntaxes; it still creates and invokes single-use closures all over the place which have a small, but noticeable, perf impact. (See #221 for more details.)

I just find it weird to have a special symbol that only works inside a pipe chain.

If it helps, don't think of it as a "special symbol", but just as a variable name. Variables only have values in contexts that define them; the topic variable is only defined by the pipeline syntax.

lozandier commented 1 year ago

Regarding referencing Dan / React Team and referencing them in context to the impact of the JS functional-programming ecosystem

Ah, that explains a lot. I will say, it sounds like we code fairly similarly, but I have found that, even if I'm not doing "deep" FP-style code, if I lean on functions a lot, I still end up writing sequences where the intermediate values don't matter. But we've discussed this in depth elsewhere so I won't continue here. The main thing I wanted to emphasize is that the design of React and the design of the Hack pipe draw from a similar underlying philosophy.

@mAAdhaTTah It’s not implausible the React Team merely designed the Hooks API around the people that have formed around the people contributing or using their library canonically or they’re inferring in advance. An empathic team of objective and professional engineers typically will not shoehorn a particular paradigm in their API at the expense of their users or the happiness/capabilities of most contributors willing to work on a particular API.

It is dangerous and daresay naive to extrapolate such experiences for or for against a language-wide feature such as this–let alone undermine the reactions of the broader JS ecosystem that is far bigger and far more important than React (to no fault of React; React is an invaluable library for very specific front-end problems).

 React is ultimately a front-end library, its use is very limited in application of a feature that is to be used and can be used to alleviate things such as

Overall, it’s a reach to suggest a library is being averse to a paradigm for decisions that are unlikely paradigm-driven in the first place in the best interest of the community that use or contribute actively to the library.

Such decisions are more natural to be based on the skillset of the contributors that were around at the time core architecture decisions were made around the feature that would be too much cognitive noise and work to pivot dramatically from due to the opportunity costs of deriving more meaningful value elsewhere instead among the many things they have to work on maintaining an open source library being continuously worked on, forked, and used by millions of users.

It’s inappropriate to infer what you’re inferring ultimately without perception checking with the React team or at least directly referencing their explicit stance on what pipeline operator they prefer.

@gaearon (Dan) is a very open-minded, transparent, and empathic open-source contributor; it seemingly would make very much more sense to mention him explicitly and perception check with him your perceptions of the affinity of this potential language feature after all this time rather than run away with your conclusions that impact a standard in a matter that the JS ecosystem and an abundance of experienced, notable JS developers such as @benlesh have made very clear Hack-style is harmful to the language to the extent they would rather have no pipeline operator at all if it was the only pipelining option.

Furthermore, Dan's not the whole React team; you can't infer his opinion is shared across the whole team as it seems you are doing to me.

Note I'm well aware of Dan's stance; as a matter of fact, my well-regarded proposed solution–the most regarded proposal in this standards repo thus far by likes– actually had his stance in mind. I find my proposal alleviated his concerns by making Hack-style have a "backtick string interpolation" role as a pipelining option compared to F#-style as the primary/default one my solution proposed.

I find hack-style a good fit for JS if it was the “backtick quote” role (pipeline templating/literals; readability and writeability of method chaining when your component expressions aren't methods) out of all pipelining options in the language, allowing arbitrary expressions of pipelining that are also easy to lint (require it for all pipelining or outright ban its use for pipelining by entities using JavaScript in a variety of sophisticated ways). This is easily teachable as analogous to the string templating features added to the language using backticks and string template literal functions.

I absolutely don’t think Hack-style should be the only or main form of pipelining in the language.

From a purely academic/educational and multi-industry standpoint (AI and data science) multiple products at an enterprise level while simultaneously working with developing engineers all the time, I’m of the strong opinion F#-style is far more

// F#-Style
class Comment extends Model |> Editable |> Sharable {}

// Hack-style: I've yet to see a Researcher, Engineer, or Intern/Student not find the Hack-style variant odd and unnecessarily arduous. 
class Comment extends Model |> Editable($_) |> Sharable($_) {}

Furthermore, and most important to this thread, F#-style is without a doubt far less obtrusive to the JS functional-programming ecosystem by only alleviating the barrier of using or even needing libraries to represent tacit functional composition pipelining that are among the most used libraries in the entire ecosystem. Aligning with their mature and well-thought-out semantics, It deprecates/eliminates the need for many of them overnight in a style understood and fetched millions of times by JS developers over the years. This would save a decent amount of kilobytes in many Node/direct-to-browser JS projects overnight.

What makes this dragged out is TC39 members who arbitrarily want to limit one way of pipelining in the multi-paradigm language.

Using |> token for F-style (tacit functional composition operator) and using $> or*> tokens for Hack-style (to arbitrarily and explicitly pipe with a piping token) seems more than reasonable with the existing JS ecosystem in mind and has ligature precedence by typefaces designed with programming accommodations such as Pragmatica Pro and Fira Code.

Suggestions to improve how you reference the React Team and other JS library teams to be more meaningful to the progression of these discussions

A more interesting question to ask, similar to what led to generics finally sensibly getting into Go, is how would the React codebase/API change or be improved if F#-style and/or Hack-style existed in the language when they designed React and its recent features?

Kubernetes did this to finally enlighten the Standards Committee around Go to finally give generics in Go more serious consideration. If Standards committee members like you have so much reverence to React to reference them towards your stance on a language feature, perhaps it's worthwhile to consider requesting them to do a case study to help out with this standard?

I find that significantly more convincing than how they're being referenced in this standard repo thus far–especially when standard committee members conflate independent libraries with unknown affinities such as Redux in these discussions.

@tabatkins understandably made it clear there won't be further JS ecosystem-wide research questionnaire surveys pursued towards this spec being ratified. I find case studies from prominent JS library maintainers a distinct and worthwhile alternative to consider (Libraries keen on pipelining in the first place or find its absence in the language a huge pain being prioritized).