Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
js-choi opened this issue Sep 18, 2021 · 72 comments
Open

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

js-choi opened this issue Sep 18, 2021 · 72 comments
Labels
documentation Improvements or additions to documentation

Comments

@js-choi
Copy link
Collaborator

js-choi commented Sep 18, 2021

Spinning this out of #202 (comment).

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 #206 (comment).)

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 #202 (comment). 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!

@Jopie64
Copy link

Jopie64 commented Sep 18, 2021

@js-choi

Thanks for trying to emphasize with the people on the F# side even when you are on the hack side. As always which such polarizing issues, people almost never have bad intent, but get very frustrated and upset when they think their arguments are ignored, or misunderstood. But the same people react the same to arguments from the other side due to human nature called confirmation bias.

That said, as a disclaimer, even when I try to stay above my own confirmation bias, and try to weigh arguments on their own merits, I'm still biased toward F#. So read with that in mind 😊

But we are hopeful that Hack pipes would increase interoperability and decrease siloing between functional libraries and non-functional libraries

I share your hope. But I strongly doubt it. Especially I think that people used to the traditional style and libraries wouldn't even consider using a, in their eyes, new and fancy pipe construct when what they were already doing was already good enough. Otherwise they probably would have done so already using current possibilities like userland pipe constructs. And when they currently use method chaining, I'm sure they would feel quite at home with F#, where they simply switch from . to |>, whenever such libs emerge.

and, as F# suggests, tacit programming should be kept internal anyway

You use this argument more often, but I still think it's a misleading one. Either F# makes an exception for |> or doesn't consider this as tacit encouraging programming, because |> is used and encouraged extensively there.
I think it's the latter. (I know I also keep making the same argument:) technically you could consider F# pipes point-free encouraging because you create a function without naming the final 'point'. But semantically the final point is mentioned directly before the |> instead of after.
It is true however that the declaration of the function you are using on RHS must be so that the final parameter can be curried. And here lies the difference in F# and JavaScript, in F# usually all parameters are curried where in JavaScript it is easy to do but, you could argue, unnatural.
Still, I think, the 'tacit encouraging' argument is misleading. I'd say it is currying encouraging.


Another thing is: Of all arguments I keep reading in favor of hack, I am missing an in my eyes quite compelling one that I only read in the HISTORY.md file:
A concern that it might encourage users to create even more performance degrading closures.

And although I think they really have a point there, I also think that (although I'm not a compiler expert) this can be optimized later. E.g. when such a function is only used in |>, and the curried 'intermediate' function does nothing but returning a function requiring the last argument, I'd say that creating a closure can be optimized away by doing as if the function is not curried, since |> immediately calls the just created closure anyway.

@ken-okabe

This comment has been minimized.

@mAAdhaTTah

This comment has been minimized.

@ken-okabe

This comment has been minimized.

@mAAdhaTTah

This comment has been minimized.

@kawazoe
Copy link

kawazoe commented Sep 18, 2021

Extracting the context, state from outside is also "side-effect" and the definition of the term is not an essence here.
What does matter is we can extract internal variables with the operation of hack-pipe [...]

To be clear, this is still possible with F# style: value |> x => (value = x) |> log

The difference is that in hack style, this solution is visible by default while in F# style, it requires a lambda. My initial argument was not that it is possible with Hack style, as I believe this problem is impossible to fix in the current language. It was rather that F# style will discourage this behavior by making it more obscure, and that this is caused by inciting currying over multiple argument functions and lambdas.

In other words, it's impossible to prevent side effects here because lambdas in javascript cannot be prevented from capturing their scopes, and thus influencing it. On the other hand, F# style appears to me as an opportunity to discourage this behavior.

EDIT: I see we're on the same line @mAAdhaTTah :)

@mAAdhaTTah
Copy link
Collaborator

mAAdhaTTah commented Sep 18, 2021

It was rather that F# style will discourage this behavior by making it more obscure, and that this is caused by inciting currying over multiple argument functions and lambdas.

I don't see why, having seen people do this with lambdas in RxJS, your expectation is that they wouldn't do this with lambdas in F#. I don't think the properties of either proposal makes this more or less likely.

To expand: at the point which a developer decides they need to make a given variable available outside the context, they're going to drop into the syntax that allows them to do that, for better or worse. In Hack, that's value = ^; in F#, that's x => (value = x). That's a mistake a developer using either proposal will make.

@ken-okabe

This comment has been minimized.

@kawazoe
Copy link

kawazoe commented Sep 18, 2021

My opinion on this is based on an idea that I've seen floating around in other thread about the "tax" that comes with each style. People will often take the easier path and by taxing every call equally with some variant of (^), hack style doesn't incite toward extracting the state or not. On the other had, based on our previous discussions, F# style should incite or at least might incite more people to use curried functions. These functions aren't taxed as much as multi arguments functions in F# because you do not need the x => part at all.

Basically, over time, you would expect to see a lot more code like this: value |> a(1) |> b(2) |> c(3) than like this: value |> x => a(1, x) |> x => b(2, x) |> x => c(3, x). This makes lambdas, and thus opportunity to extract the state, a lot more visible in F# style. At least, assuming a world where currying takes on. If it doesn't, then this whole thing is not going to matter.

@ken-okabe
Copy link

@kawazoe
Which style do you feel secure in to write a robust code with fewer mistakes?

@kawazoe
Copy link

kawazoe commented Sep 18, 2021

@stken2050 That is a very hard question to answer.

What I bring is my experience working with multiple junior devs on a team and the kind of mistake they often do when dealing with FP style code like RxJs.

I cannot tell if Hack or F# style would make it better or worse. I have seen stuff in this proposal that could very well make things worse. Examples like this one: #167 (comment) which to me is a good representation of the kind of code people will write when they don't understand the reason why the pipeline operator exists. Thing is... I don't think it would be any different with either proposals. This is not a Hack style problem, this is an understanding problem.

I feel like the F# style, purely based on the assumption that it seems to encourage currying more than the other, is the better choice, but I can't say for sure. Not without trying it in a large scale project with other people. I feel like F# style would be easier to explain to people.

@ken-okabe

This comment has been minimized.

@kawazoe

This comment has been minimized.

@mAAdhaTTah

This comment has been minimized.

@ken-okabe

This comment has been minimized.

@ken-okabe

This comment has been minimized.

@kawazoe

This comment has been minimized.

@mAAdhaTTah

This comment has been minimized.

@ken-okabe
Copy link

I mean do they override the JS operator functionality of =: (x = y) replaceable to z with hack?

@ken-okabe
Copy link

ken-okabe commented Sep 18, 2021

  1. If hack operator does NOT override = operation functionality,
    value |> (value = ^)
    to
    value |> (5)
    should be valid.

  2. If value |> (5) is syntax error,
    which means they override =
    (x = y) has been safely replaceable to any z until now in any places in JS code,
    hack-operator broke the referential transparency.

@ken-okabe

This comment has been minimized.

@kawazoe

This comment has been minimized.

@mAAdhaTTah

This comment has been minimized.

@js-choi
Copy link
Collaborator Author

js-choi commented Sep 19, 2021

Thanks for trying to emphasize with the people on the F# side even when you are on the hack side. As always which such polarizing issues, people almost never have bad intent, but get very frustrated and upset when they think their arguments are ignored, or misunderstood. But the same people react the same to arguments from the other side due to human nature called confirmation bias.

That said, as a disclaimer, even when I try to stay above my own confirmation bias, and try to weigh arguments on their own merits, I'm still biased toward F#. So read with that in mind 😊

@Jopie64: Thanks! Of course, you are correct here. Everyone, myself included, comes with our own subconscious cognitive biases, and we all have to watch out for them. And it is understandable for people to be frustrated when they feel like they’ve lost something promised.

But we are hopeful that Hack pipes would increase interoperability and decrease siloing between functional libraries and non-functional libraries

I share your hope. But I strongly doubt it. Especially I think that people used to the traditional style and libraries wouldn't even consider using a, in their eyes, new and fancy pipe construct when what they were already doing was already good enough. Otherwise they probably would have done so already using current possibilities like userland pipe constructs. And when they currently use method chaining, I'm sure they would feel quite at home with F#, where they simply switch from . to |>, whenever such libs emerge.

@Jopie64: Just to clarify…Are you positing that programmers using non-functional programming styles would not find Hack pipes useful for flattening their deeply nested expressions?

Hack pipes are designed to be (hopefully) universally useful for all programming styles, including traditional styles—because all programming styles, including traditional styles, involve deeply nested expressions. (Indeed, all of the real-world examples in the explainer are currently based on real-world traditional APIs that are not focused on higher-order functional programming, except for the examples from Ramda’s API (cf. #218).

and, as F# suggests, tacit programming should be kept internal anyway

You use this argument more often, but I still think it's a misleading one. Either F# makes an exception for |> or doesn't consider this as tacit encouraging programming, because |> is used and encouraged extensively there.
I think it's the latter. (I know I also keep making the same argument:) technically you could consider F# pipes point-free encouraging because you create a function without naming the final 'point'. But semantically the final point is mentioned directly before the |> instead of after.
It is true however that the declaration of the function you are using on RHS must be so that the final parameter can be curried. And here lies the difference in F# and JavaScript, in F# usually all parameters are curried where in JavaScript it is easy to do but, you could argue, unnatural.
Still, I think, the 'tacit encouraging' argument is misleading. I'd say it is currying encouraging.

@Jopie64: This is fair enough, and this argument is well taken. It is true that, although pervasive tacit programming is discouraged by F#’s documentation, |> is pervasive in F#. As you say, it’s seen simply as an inverted function-application operator that can be chained.

The difference is, as you point out, the fact that all functions are automatically curried in F#. And automatic function currying is not built into the JavaScript language, and most functions in JavaScript code are not curried. In JavaScript, curried functions are yet another color of function that have to be used in a special way…presumably with a yellow color 🍛. Although some people like using this color, there are many representatives on TC39 (including outside this pipe champion group) who have pushed back against encouraging currying and partial application.

I would amend (and hopefully strengthen) my argument thus: although F#’s documentation does not discourage the pervasive use of its |> operator, F#’s documentation does discourage pervasive use of partial application, and it brings up several reasons why (like cognitive load and debugging). And I think that these reasons against pervasive partial application (cognitive load and debugging) still do apply to JavaScript, insofar that F# pipes would encourage the declaration of many curried or otherwise partially applied functions.

(Though note again that I am in favor of a partial-function-application syntax and an F#-style pipe in addition to Hack pipes. And I plan to try to fight for a PFA syntax and F# pipes after Hack pipes. But it has always been an uphill battle against many other representatives’ reasonable challenges. I have hope that someday we will get PFA syntax, but the hope is small.)

Another thing is: Of all arguments I keep reading in favor of hack, I am missing an in my eyes quite compelling one that I only read in the HISTORY.md file:
A concern that it might encourage users to create even more performance degrading closures.

And although I think they really have a point there, I also think that (although I'm not a compiler expert) this can be optimized later. E.g. when such a function is only used in |>, and the curried 'intermediate' function does nothing but returning a function requiring the last argument, I'd say that creating a closure can be optimized away by doing as if the function is not curried, since |> immediately calls the just created closure anyway.

@Joepie: There are several strong concerns that TC39 representatives outside of the champion group have brought. Performance is indeed one of them, although it is not the only one. The browser implementers have said repeatedly to the group that people generally overestimate how much optimization they can do, and they feel that it probably applies to this case (e.g., if a curried function is declared separately—also observable error stack traces getting in the way of inlining). But I think this performance discussion is kind of off topic for this issue. It probably should move to #221.

Edit: Oh, wait, you already brought it up in #215 (comment). That’s fine; it’s sort of related. 😄

Anyways, thank you for your erudite and intelligent comment. It also will definitely be important for improving the explainer, too.

@js-choi

This comment has been minimized.

@js-choi js-choi changed the title Impact of Hack pipes on functional programming Impact of Hack pipes on the JS functional-programming ecosystem Sep 19, 2021
@js-choi
Copy link
Collaborator Author

js-choi commented Jul 11, 2022

@js-choi Is there a reason why the champions don't leverage the State of JS survey and surveys at the most popular Web platform conferences to explicitly elect feedback from the community regarding the pipeline? TC39 members have done informal JS surveys that consistently has pipeline without a doubt one of the most in-demand features.

The State of JS survey was in fact responsible for jump-starting the proposal again in January 2021, which had reached a standstill (see HISTORY.md’s 2021 section). The surveys have been used as indirect evidence that there was community desire for some pipe operator, about which some browser vendors had expressed skepticism.

Having said that, surveys of community popularity (probably fortunately) have but a limited role in the decision-making process of the Committee. Popularity of some library or pattern might indicate that there is something worthwhile to investigate, but TC39 representatives generally do not consider it an important factor when considering the best choice for the language. Of course, popularity surveys are also notoriously unreliable and prone to selection bias. Attempting to survey any population as heterogenous as JavaScript developers will be fraught with severe confounders and probably give little confident or useful data, no matter what their results may be.

Thus, for better or for worse, the Committee relies on representatives’ own expertise from engine implementations, PL design, day-to-day development, and so on.

We already have an issue about this #222, and we still should make this an FAQ sometime. (Also related: the FAQ about usability studies, in #216.)


Beyond the opinions you're seeing here, another reason we're guaranteed to never have F# semantics is because the implementers do not believe it is possible to optimize.

This is also true. Over the past five years, engine implementers have repeatedly pushed back on the pipe champion group, with concerns about static analyzability and optimizability of code that relies on F# pipes and PFA. We’ve already recorded this to some extent in #221 and in HISTORY.md, but we still should also make this an FAQ too sometime.


(I’ve also been lax about updating the official changes thread (#232), but I plan to present Function.pipe and flow/compose for Stage 1 to the plenary next week. I plan to properly update #232 with the results afterwards.)

@lozandier
Copy link

lozandier commented Jul 11, 2022

Beyond the opinions you're seeing here, another reason we're guaranteed to never have F# semantics is because the implementers do not believe it is possible to optimize. We didn't just choose Hack because of a stylistic opinion of a few delegates, but came to a 100% consensus that this is the right approach for the language we have.

@jridgewell @ljharb Similar reasoning blocked ES2015 tail call optimizations from being implemented as the spec mandated by the browsers vendors that use those engines, right? If so, that's a disappointing shared thought impeding a more equitably accommodating pipeline operator for developers to use moving forward.

Regardless, functional programming paradigm trade-offs are usually performance-related. That's always a trade-off deciding to solve a problem primarily with functional paradigm constructs rather than using the constructs of other paradigms.

Similarly, there are many trade-offs with other paradigms supported by the language being applied to the same problems–it's just that the pros of increasingly applying functional concepts to a problem instead of concepts from other paradigms aren't going to be consistently associated with performance-related benefits.

Such paradigm trade-offs should be up to developers to decide what's best for the problem at hand, not the committee or engine implementer teams such as V8's. The language has many constructs for solving the same problems with a variety of paradigms accounted for with varying amounts of performance trade-offs; the robust dev tools we have for developers to use today enable them to make the right choices for their particular situations more than ever before.

For example, procedurally iterating things can be meaningfully faster than tackling the same problem more functionally (compared to'Array.map()`; elevated a bit by the use of tranducers, endofunctors, etc). Nonetheless, it's practical and overwhelmingly beneficial to the language you can handle the same problem more functionally instead. A meaningful amount of people prefer doing this to solve iteration problems that aren't performance-critical.

Prevalent and well-regarded functional-programming-oriented libraries, compile-to-JS languages, and the developers using such things are fully aware of the performance trade-offs of using the functional paradigm to solve problems. These trade-offs are invaluably worth it for many developers–even if their constructs were all natively possible.

Why can't such trade-offs be allowed to accommodate multiple paradigms at the compiler level? It's a state of political-purgatory for tacit-related functional programming constructs.

I'm of the strong opinion a more ideology-equitable committee governing a multi-paradigm programming language and representatives of browser compiler team implementing the specs would more transformatively improve the constructs of the multiple paradigms it supports (& even new ones!) without resorting to shutting them down for intrinsically known faults of the paradigm compared to others in performance by having performance standards more equitable of supporting multiple paradigms.

This proposal, ES2015 tail-call optimization, Web Assembly Tail-call optimizations, and other proposals intended to transformatively improve the language have been plagued by that not being the case with TC39, as well as representatives of compiler teams after the fact pushing back against ratified functional paradigm constructs strongly desired by those who prefer or want to solve their problems functionally and recursively in the language (what happened to ES2015 tail call optimization in the minds of many). What is going on with the standard process today with many functional programming proposals in recent years has been argued as being effectively thinly-veiled paradigm-construct-segregation. I don't necessarily agree with such perspectives, but I'm increasingly emphatic of those that adamantly believe this with recent developments of this proposal not helping matters.

This is disproportional to how transformative improvements to other paradigms–such as class-oriented programming–have been handled to accommodate programmers more invested in those paradigms than others with minimal or no benefit of programmers more invested in other paradigms. An example is ES2015 classes.

This imbalance accordingly negatively impacts the ability of the functional programming paradigm to be in more use in JS to the annoyance of millions of developers towards proposals like this being consistently stalled.

It is common knowledge that pipeline-operator is one of the most common features developers want in the language; do the champions think most of such people aren't functionally programming in a matter they claim is in the minority? If the current champions do, I beg to differ.

Arguably the most celebrated ES2015 features have been ones to transformatively improve the ability to program using the language functionally (i.e., arrow functions).

The latent reason why functional-paradigm-oriented proposals are in demand is because the paradigm isn't good enough as it stands today to use it more effectively and more commonly.

A meaningful amount of developers want to program more functionally and tacitly, but features such as this implemented as they expect aren't in the language. Accordingly, this is why I'm consistently pointing out @tabatkin's stance on specific styles of functional programming being "maybe always in the minority" is missing a couple of important nuances on why that's a problematic premise on making the operator more tacit.

This is also why I think hack-style version of a pipline operator is detrimental to the JS functional-programming ecosystem.

The elephant in the room is it's overwhelmingly clear devs who willingly or have no choice of using functional constructs today as they are today in the language do not prefer hack-style. Prominent maintainers of functional-programming libraries tacitly composing functions today like @benlesh have been clear they would rather there be no pipeline operator at all if hack-style's current semantics is the only option.

It doesn't add up to me a functional-programming construct is designed in a way to improve JS's multi-paradigm support has such severe issues with devs who program with functional programming paradigm constructs the most (such people will be the primary people that would use this feature regardless of what is ratified) and the very mature and popular functional programming ecosystem in the language. I cannot emphasize enough pipelining is ubiquitously an intermediate/advanced functional composition construct for those very committed to writing things using functional programming patterns. This is again similar to ES2015 classes being primarily for developers very committed to coding scripts in a class-oriented manner.

Accordingly, this is why I'm adamant that hack-style impact on JS functional-programming ecosystem is severely negative. It very detrimental impact to the JavaScript functional programming ecosystem has been minimized by figuratively handwaving members of that ecosystem being a minority underscoring the fact that

  1. Tacit functional programming constructs intrinsically will always have a perf disadvantage to the constructs of other paradigms that's not circumventable

  2. The conduct of the committee and current pipeline champions historically with functional programming constructs related to tacitness have made such devs code resort to non-functional programming alternatives to solving a problem more then they would like for the lack of better options.

  3. The lack of a tacit functional composition operator (and tail call optimization) severely limits/halts the utility of existing functional constructs in the same category such as arrow functions."

Native Browser API compatibility (IIRC by @tabatkins?) has also been a common rebuttal that also is ignoring a crucial nuance: Many browser APIs/functions have outputs that are not intended to be composable/chained–nor are they used as part of an output towards a single result you expect from established and ubiquitous pipeline patterns.

The use case of most browser APIs is for imperative writing of code that a Web creative or team of them can apply various patterns like the Fascade pattern to more adapt them to their programming paradigm of choice.

In addition to the issues for existing power users and devs that depend on a11y/ergonomic technology I outlined here, I again cannot emphasize enough that the hack-style form of representing pipelining is very detrimental to the language if it shipped the way it is designed today.

I'm of the firm opinion it's premature for the pipeline-operator standard to go forward without Function.pipe() and Function.flow() shipping first for the language to be at least on par with the level of tacitness functional programmers are stuck with today. Once ratified, both methods can be a latent user study of sorts making that level of tacitly composing functions more accessible to the masses and unblocking those in controlled environments finally have that level of terseness communicating functional composition natively (i.e., interviews, academic settings, and contributing to a performance-critical application where importing libraries for such tasks must be minimal) .

From there, I think partial application spec should be ratified first. After those three things, I can then maybe see it making sense to revisit this proposal.


@aadamsx the only outcome of that that would be different is pipeline getting killed entirely, forever. There is zero possibility that F# style will gain consensus, as there's already many people who aren't enthusiastic about new syntax here at all.

@jlharb @js-choi That doesn't seem to be accurate; a pipeline operator just indefinitely won't happen with the current make-up of the TC39 committee (and representatives of browser vendors who can unfailingly impact the fate of a ratified proposal like what happened with the tail-call optimization spec). As far as I know, there isn't anything in the standards process that prohibits a standard from being revisited after being denied initially–sorta how the US Supreme Court operates, for the lack of a better example.

It isn't unprecedented that proposals are revisited and made into the language after later attempts. ES4 features like classes are now part of the language after their initial iterations failed to be part of the language (some like class fields are well on track of having the same outcome). I think the process of implementing a pipeline operator in JS can benefit of having a similar story with the current proposal being tabled and the idea being revisited in the future.

If Hack-style is rejected, alternate pipeline operator proposals would be political purgatory until it can be revisited; in this case, I'm of the opinion the pipeline operator with hack-style semantics without improvement is harmful to the language more than the language not having any pipeline operator, as I've outlined here.

If it takes years; at this point I've accepted that. I plan to be survived by kids. If they decide to be JavaScript developers, hopefully they can enjoy a pipeline operator better designed and more accommodating of the ecosystem than the hack-style pipeline operator is today. The latter is what JS overall great despite its many warts and flaws as a language; hack-style again doesn't do them any favors.

If my kids happen to prefer or better understanding solving problems functionally and recursively like me, I hope they really enjoy that in my place–even if I die before that happens without using Babel and Safari/iOS Web View apps like I have to do in the present because of the politics surrounding tacit functional programming standards today (V8's infamous decision to block implementing tail call optimizations as originally specced by ES2015 blocks recursive code being written in Node).

Proposals that dramatically improve the tacitness of programming functionally in JS are increasingly predictable not being ratified with the current make-up of the committee and the defiance of specific compiler teams implementing them after the fact as well.

I'm aware Hack-style proposed by the current champions of the proposal is understandably a simultaneously fair pursuit of attempting to being accommodating of other paradigms navigating the obvious red tape of transcending the tacitness of expressing functional code the committee is very adament of pushing back against. However, it shouldn't be at the expense of its primary audience–functional programmers–and their ecosystems that made the proposal relevant as it is in the first place.

Proposals in such a state with such constituents, I'm content with not being in the language–as frustrating it will be for my young self that the pipeline operator variants most want is in political purgatory with other tacit-related constructs. It's no longer a surprise to me anymore.


@lozandier Is there any way you can get on the TC39 board to help give a fresh perspective on this? If not, can you become a co-champion of this proposal in order to be able to submit your views and be heard by TC39? Your perspective is spot on IMO and shared by so many.

@aadamsx To be transparent, I was actually reached out to by a previous champion of this proposal during the pandemic, but it was at a time when I was dealing with a family tragedy. I did not discuss it further with them needing to understandably look out for my mental health and support my family during these wild and unpredictable times.

Today, I'm still sorting out how committed I can be to a variety of things with things finally becoming normal for me again that I imagine most are going through this year.

That said, it seems the pipeline operator has reached a point that the changes I would want to sell would be n/a for the stage this particular version of the proposal or a severely uphill battle because of the reasons I pointed out above.

I ultimately prefer prioritizing being increasingly involved with other Web standard bodies that are much more ideological-equitable, diverse (higher priority should be for more women to be involved regardless of their views, IMO), and more democratic (i.e. more emphatic of the desires of the language's ecosystem) in how they're governed than I think TC39 now is for me to be comfortable doing so. This has become apparent to me with how TC39 is handling the pipeline operator standard and how compiler teams have outright deviated and stalled their ratified specs being implemented to the determinant of millions of developers like what happened with ES2015 tail-call optimizations

@js-choi
Copy link
Collaborator Author

js-choi commented Jul 11, 2022

My apologies to all participants, but this conversation is getting a little heated and difficult to follow. This repository is not the right venue to air general grievances about TC39’s general process—even if this proposal is a manifestation of that process.

(And, as mentioned before, we already have threads about formal usability studies and popularity surveys: #216 and #222. We also have #221 about engine implementors’ concerns about F# pipes, and concerns about the power that engine implementors have on the process probably better belong in #221.)

Additionally, in response to this statement:

I ultimately prefer prioritizing being increasingly involved with other Web standard bodies that are much more ideological-equitable, diverse (higher priority should be for more women to be involved regardless of their views, IMO), and more democratic (i.e. more emphatic of the desires of the language's ecosystem) in how they're governed than I think TC39 now is for me to be comfortable doing so. This has become apparent to me with how TC39 is handling the pipeline operator standard and how compiler teams have outright deviated and stalled their ratified specs being implemented to the determinant of millions of developers like what happened with ES2015 tail-call optimizations

Although—as mentioned before—this is very off-topic, I’d like to point out that Committee’s members are aware that it could improve in its inclusivity of women and other peoples, and its Inclusion Group is actively working on improving it. @lozandier: For your broader concerns, we invite you to reach out to the TC39 Inclusion Group on Matrix at #tc39-inclusion:matrix.org.

There is also a Code of Conduct that focuses on inclusivity. Anyone can confidentially contact the CoC Committee if they think that there has been a specific violation.

This repository is simply not the right place to air general grievances about TC39’s general process—even if this proposal is a manifestation of that process. For specific questions about usability studies, popularity surveys, or engine-implementor concerns, feel free to leave comments on #216, #222, and #221 respectively. Thanks! 😃

@jridgewell
Copy link
Member

jridgewell commented Jul 12, 2022

Such paradigm trade-offs should be up to developers to decide what's best for the problem at hand, not the committee or engine implementer teams such as V8's.

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. When looking at F# vs Hack, Hack has a clear performance advantage, supports more use cases, and doesn't prevent functional style (it's just a bit more verbose). Why would we choose a dud?

Prevalent and well-regarded functional-programming-oriented libraries, compile-to-JS languages, and the developers using such things are fully aware of the performance trade-offs of using the functional paradigm to solve problems.

[My own emphasis] I think we're designing the language for 2 very different sets of developers. I do not expect beginners to understand this tradeoff at all. Expectations built up from compiled functional languages will not carry over to ours.

It is common knowledge that pipeline-operator is one of the most common features developers want in the language; do the champions think most of such people aren't functionally programming in a matter they claim is in the minority? If the current champions do, I beg to differ.

We're attempting to add a pipeline operator, it just doesn't work in the way you're arguing for. I think the developers who want pipeline will be well served with Hack semantics.

The elephant in the room is it's overwhelmingly clear devs who willingly or have no choice of using functional constructs today as they are today in the language do not prefer hack-style.
Prominent maintainers of functional-programming libraries tacitly composing functions today have been clear they would rather there be no pipeline operator at all if hack-style's current semantics is the only option.

People who have strongly held opinions tend to yell them loudly. That does not mean a majority of users would rather we never add a useful new feature. Deadlock like this is not constructive, we would be stuck with ES3 with this kind of behavior.

In addition to the issues for existing power users and devs that depend on a11y/ergonomic technology I outlined here, I again cannot emphasize enough that the hack-style form of representing pipelining is very detrimental to the language if it shipped the way it is designed today.

I struggle to understand why assistive users will have an issue with Hack. Pre-created flow will look just like regular function calls, and basic operations will not require a function wrapper at all. For every usecase that isn't data-last curried functions, the code is measurably improved.

@lozandier
Copy link

lozandier commented Jul 12, 2022

I think we're designing the language for 2 very different sets of developers. I do not expect beginners to understand this tradeoff at all. Expectations built up from compiled functional languages will not carry over to ours.

@jridgewell I have beginners in mind; I've had beginners in mind with my responses from the very start, including one that's one of the most well-regarded comments in this proposal rep.

I've shared many responses and perspectives from beginners throughout my responses in this repo over the years towards why I'm adamant that hack-style needs improvements.

As someone who interacts with beginners or contributed to works directly for beginners in platforms/entities such as Code School, Treehouse, Khan Academy, Web.dev, General Assembly, and so on, I respectfully strongly disagree with you.

I've yet seen a beginner I've interacted with see hack-style being more intuitive or "better". Unsurprisingly, beginners I've exposed the syntax to get hung up on how redundant and more tedious it is to refactor their existing chaining and composition code to use it instead because of the required (^) tokens neeeded. Even interns I consider outliers of a typical beginner–because of how probably abnormal and rigorous my employer's interview process is to select them–have the same issue.

To beginners I've interacted with, it is more than sufficient that |> indicates the current or eventual value(s) on the left side will be passed to the right side. Hack-style's (^) token requirement is unorthodox to what they expect a high-order binary operator for functions to do: Tacitly communicate the chaining/composition intent with an equal amount or less code than before. The fact that it requires so much more to write than basic chaining/composition is a huge turn-off. This mirrors the experience of data science colleagues that would consider themselves merely casual JS developers; ; hack-style being prevalent in JS codebases if ratified would only make them want to use JS less for their problems, not more.

As far as a beginner concept: I cannot emphasize enough that Pipeline is ubiquitously understood conceptually in data science and computer science subject matters as a series of tasks that are passed in the result of the previous task that preceded it or an initial value. The simplest way most languages represent a series of logic blocks/tasks/subroutines that can simultaneously take in a result of a previous series of logic blocks/tasks/subroutines as starting input is a unary function.

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. It's a chicken and egg problem why it's not more commonly done: Increased prevalence of such programming in mainstream JS is bottlenecked by the lack of a tacit functional composition operator.

I struggle to understand why assistive users will have an issue with Hack.

It seems you didn't read the contents of the link I provided, nor probably the main argument about this I wrote that makes my stance very clear.

If you did, what exactly are you confused about?

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.

This problem is reaching a climax with how this proposal has developed with this highly regarded functional programming construct by those who want to functionally program more, faster, and better.

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

  • Existing JS devs who are deeply invested in solving their problems functionally

  • Existing and new devs that will have no choice (because of company style guides common in companies as large as mine)

  • Existing and new dev who'd consider pursuing coding functionally with it in the future

This is regardless of what languages such groups were previously exposed to. I don't think the champions nor the committee are thinking enough of the middle audience. "Beginners" are very hypothetical of an audience compared to all three that ironically would need the most quantitative data to support all audiences. The champions of this proposal have made it clear they don't want to do that being very unprecedented to do. I would like such an audience to be referenced less in such discussions accordingly.

I believe that a proposal shouldn't weigh too much on the opinions of the masses, but there is a diminishing return of going that direction that this proposal has reached with hack-style in my opinion. I'm merely doing what I think is the right thing to let the champions and committee know this very explicitly.

That said, I wonder: Has a variant that only works on arrow functions been proposed? As a creative suggestion, perhaps that can be a path towards a compromise? I'm not convinced hack-style is the right one.

Alternatively, what do you think of the compromise I explained last year](#225 (comment))? I think it should seriously be considered; it's very simple to understand non-functions would use |>> ("implicit pipelining") while use with synchronous functions would remain tacit with |> ("explicit pipelining") I think an overwhelming majority of programmers and non-programmers expect/appreciate.

In comparison, Hack-style's unorthodox handling of pipelining is contrary to how pipelining is understood conceptually by most, creating a lot of unnecessary cognitive noise–that's incompatible with the majority of the JS functional-programming ecosystem today. I'm of the opinion it's too much of a hack.

@jridgewell
Copy link
Member

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
Copy link

lozandier commented Jul 12, 2022

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 #238 (comment) 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
Copy link
Member

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
Copy link

lozandier commented Jul 13, 2022

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:

  • Creates substantial noise of what's being invoked or not for the simplest of expressions

  • Management of pipeline expressions compared to the more tacit userlandObject.pipe()/Function.flow is substantially bloated accounting for (^) in the simplest of expressions. You can't merely do d + w to delete functions.

  • 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 such as x |> foo(bar(^))(^) (hack-style) instead of just x |> foo(bar(^)) (tacit) and so on.


|> 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
Copy link
Member

jridgewell commented Jul 14, 2022

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. #217 (comment)

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

Let's focus on actual product codebases, not libraries. #217 (comment)

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
Copy link

kawazoe commented Jul 14, 2022

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
Copy link
Member

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
Copy link
Member

ctcpip commented Jul 14, 2022

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

  • Be respectful
  • Be friendly and patient
  • Be inclusive
  • Be considerate
  • Be careful in the words that you choose
  • When we disagree, try to understand why

@lozandier
Copy link

lozandier commented Jul 14, 2022

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
Copy link

arendjr commented Nov 22, 2022

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.

@tc39 tc39 deleted a comment from aadamsx Nov 23, 2022
@tabatkins
Copy link
Collaborator

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
Copy link
Collaborator

mAAdhaTTah commented Nov 28, 2022

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
Copy link

arendjr commented Nov 28, 2022

@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
Copy link
Collaborator

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
Copy link

arendjr commented Nov 28, 2022

@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
Copy link
Collaborator

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
Copy link

arendjr commented Nov 28, 2022

@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
Copy link
Collaborator

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
Copy link

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
Copy link

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
Copy link
Collaborator

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
Copy link

lozandier commented Nov 30, 2022

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

  • Data pipelining (what I primarily intend to use this feature for that I strongly find hack-style not a great fit for nor my peers in data science/AI orgs) 


  • Composition of functions and classes (I find Hack-style again a significantly poor and confusing fit for basic class mixing with the cognitive noise of using ($_))

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

  • Easier to understand/teach (it aligns with existing representations of composition in math and data pipelining in both engineering and non-engineering disciplines)

  • Adaptable in existing libraries that exist and have yet to be made people wanted to do

  • Meaningfully faster for parse than Hack-style as a tacit functional composition operator

  • Enables clearer non-contentious tacit expressions of fairly common frequency in intermediate JavaSript Hack-style is not a great fit for such as class mixins:

// 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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests