tc39 / proposal-pipeline-operator

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

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

Open js-choi opened 2 years ago

js-choi commented 2 years ago

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

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

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

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

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

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

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

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

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

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

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

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

Jopie64 commented 2 years ago

@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 commented 2 years ago

@js-choi First of all, thank you for opening the issue of this aspect and I also appreciate your hard work (must be, I guess) for Brief history of the JavaScript pipe operator that I think the information listed is extremely valuable for everyone who concerts the matter.

For the "Impact of Hack pipes on functional programming", I would like to paraphrase "Impact of Hack pipes on algebraic structure in JavaScript".

Reading through your work describing the history, I feel very sorry for this proposal, especially @rbuckton's contribution.

Fundamentally, essentially, this proposal is to introduce a new binary operator to JS, which is the same league of exponentiation operator ** introduced In ES2016 Syntax Math.pow(2, 3) == 2 ** 3 Math.pow(Math.pow(2, 3), 5) == 2 ** 3 ** 5 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Exponentiation

Replacements an expression of OOP style to a binary operator in an algebraic sense make the code concise and readable, easier to grasp the math structure which leads us to avoid mistakes then the code becomes robust. This is a very powerful approach, and perhaps some people observe this manner as FP code. Haskell codes are full of binary operators, and Haskellers basically do just Math/algebra in their code.

A replacement of a syntax f(x) == x |> f g(f(x)) == x |> f |> g is the same league of ** binary operator.

Replacement and replaceable or equivalent itself is a very powerful concept. Why? Because equivalency reduces complexity. The history of software development is a war against complexity.

If we are very careful to treat programming entities kept equal all the time, we safely can avoid the problem of combinatorial explosion. https://en.wikipedia.org/wiki/Combinatorial_explosion

In mathematics, a combinatorial explosion is the rapid growth of the complexity of a problem due to how the combinatorics of the problem is affected by the input, constraints, and bounds of the problem.

So, to keep entities of programming equal or to be replaceable equally all the time is an extremely important concept, and although I don't like to use this term, many call this advantage referential transparency. https://en.wikipedia.org/wiki/Referential_transparency

Referential transparency and referential opacity are properties of parts of computer programs. An expression is called referentially transparent if it can be replaced with its corresponding value (and vice-versa) without changing the program's behavior. This requires that the expression be pure, that is to say, the expression value must be the same for the same inputs and its evaluation must have no side effects. An expression that is not referentially transparent is called referentially opaque.

Obtaining a concise binary operator contributes to reduce the complexity of our codes. Math.pow(Math.pow(2, 3), 5) == 2 ** 3 ** 5 g(f(x)) == x |> f |> g

Of course, the above code is referentially transparent. They are Just math equations, and since things are always equal and replaceable, we are guaranteed to be safe from the complexity of combinatorial explosion.

On the other hand in hack "operator", I mean mathematically operator is "pure" in concept but this one is not "pure", we are not safe any longer.

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

The hack "operator" spoils equivalency and referential transparency or introduces a side effect. The side effect is to introduce a new context variable ^.

This fact has been apprehended with certainty here recently: Question about ^ when a "child pipeline" is present. #208 https://github.com/tc39/proposal-pipeline-operator/issues/208#issuecomment-918539647 @mAAdhaTTah

It seems like the presence of a |> in a child function would give new meaning to the ^ on its RHS until the end of the function's context?

This is correct: the |> operator introduces a new expression scope, so the (^) on line 6 refers to what you expect it to.

The reason programmers avoid side effects is this is the factor of complicity and fundamentally this is mathematically inconsistent. https://en.wikipedia.org/wiki/Referential_transparency

In mathematics all function applications are referentially transparent, by the definition of what constitutes a mathematical function. However, this is not always the case in programming, where the terms procedure and method are used to avoid misleading connotations. In functional programming, only referentially transparent functions are considered. Some programming languages provide means to guarantee referential transparency. Some functional programming languages enforce referential transparency for all functions.

Losing referential transparency, a real-world programmer who has used hack-style-operator for a certain period of time actually has been suffered.

https://github.com/tc39/proposal-pipeline-operator/issues/202#issuecomment-922070966 @kawazoe

As a dev who used reactive extensions a lot in my career (whether it's rxjs or rx.net), I've noticed the same problem arise in nearly all the teams that try to use it. They all end up leaking state from the pipeline. In Rx pipelines, this leads to race-conditions and horrible issues with code readability on top of making the code completely impossible to reuse. In the end, there is no way to prevent this, and the pipeline operator style will not influence how people uses Rx that much. The problem isn't with Rx though. It is within limitations of the pipeline mechanism that it uses. As it is heavily based on lambdas, most of the code in Rx captures the scope outside the pipeline and thus depends entirely on the developer to use that captured scope correctly. Any language feature which would discourage this behavior would have saved me a very large amount of wasted time debugging those issues.

Do we really want to choose a pipeline operator that also has this problem by default? With hack style, you can easily extract the state of the pipeline at any point in an external variable. Aka, this code seems valid: value |> (value = ^) |> log(^). By requiring expressions for every step of the pipeline, you end up with a clear path to extract the pipeline state. You might say "who in their right mind would write code like this?" and yet this is the kind of stuff I have seen done in Rx multiple times.

JavaScript is originally developed by Brendan Eich, a functional programmer who had been Scheme enthusiast. That's why JavaScript has a core feature of functional language such as functions are first-class objects.

Now, we have a pipeline "operator" that should have been considered as a very basic binary operator of function application that is a very basic algebra operation in the mathematical sense but actually polluted by a side-effect.

Impact of Hack pipes on algebraic structure in JavaScript

With Hack pipes, we've lost JavaScript as a functional language. Perhaps that's why many are upset. It's not a matter of "interoperability", and "silent majorities" will care about this matter a lot.

mAAdhaTTah commented 2 years ago

The side effect is to introduce a new context variable ^`.

Introducing a context variable is not a "side effect." A side effect is a modification outside the local environment. Hack pipe introduces the value into the local environment the same way a parameter to a function does, but it does not modify anything outside of it. The Hack pipe is thus side-effect free.

What's more, it doesn't break referential transparency either. If you took x |> f(^) and replaced it with f(x) (or the value returned by f(x)), the result is the same, which is what referential transparency is; namely, the ability to replace a function with the result of that function without changing the behavior of the application. This is closely related to it being side-effect free. Hack pipe doesn't lose referential transparency.

Hack doesn't break math, nor does it lose JavaScript as a functional language.

ken-okabe commented 2 years ago

Introducing a context variable is not a "side effect."

I disagree  

A side effect is a modification outside the local environment.

Only half correct.

With hack style, you can easily extract the state of the pipeline at any point in an external variable. Aka, this code seems valid: value |> (value = ^) |> log(^). By requiring expressions for every step of the pipeline, you end up with a clear path to extract the pipeline state. You might say "who in their right mind would write code like this?" and yet this is the kind of stuff I have seen done in Rx multiple times.

Extracting the context, state from outside is also "side-effect".

Providing a connection or opening an entry-hole to the outside is also a modification. From the outside, we can see the open hole and freely enter.

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, and that never happens in math function that is referential transparent in the definition.

mAAdhaTTah commented 2 years ago

What does matter is we can extract internal variables with the operation of hack-pipe

If you're referring specifically to this quoted line:

value |> (value = ^) |> log(^)

This is equally possible in F#: value |> x => (value = x) |> log. There isn't anything specific about Hack that makes it possible to "extract internal variables" that F# prevents.

kawazoe commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

@kawazoe

Thanks, I should have quote more:

https://github.com/tc39/proposal-pipeline-operator/issues/202#issuecomment-922070966

F# style doesn't prevent this, it at least discourage it by taxing it heavily when compared to calling an FP friendly function: value |> x => (value = x) |> log. This style doesn't expect an expression with access to the pipeline' state by default. You have to declare it.

The variable is declared independently, so this is not a leak. In any way, I've been coding F# style pipeline operator |> and lambda for a long time, and I know it's just a replacement of f(x) and referential transparent operator.

kawazoe commented 2 years ago

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 commented 2 years ago

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

kawazoe commented 2 years ago

@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: https://github.com/tc39/proposal-pipeline-operator/issues/167#issuecomment-829636277 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 commented 2 years ago

@kawazoe

Thank you for your input. May I have another question: It seems the code is valid in hack:

1 |> 5
  |> console.log(#) //5

|> is supposed to be a binary operation of f(x), correct?

5 is not a function, and I don't know what's going on, or I've never seen this stuff in Math etc. Can you explain to me because I'm an inexperienced coder of the hack operator? Thank you.

kawazoe commented 2 years ago

@stken2050 this is off topic, but my understanding is that Hack style does not behave like a binary operator. Rather, it is a syntactic sugar that gets from pipelined to implicit code behind the scene. See the "Real world example, continued" for an idea of what it is doing here: https://github.com/tc39/proposal-pipeline-operator#temporary-variables-are-often-tedious. It explains why you can just put a 5 in the middle and still have valid code.

mAAdhaTTah commented 2 years ago

1 |> 5 is invalid because the topic token isn't used on the RHS, so this would be a syntax error.

ken-okabe commented 2 years ago

@kawazoe Thanks. In fact, I'm sure this is on-topic: Impact of Hack pipes on functional programming. and I think it's not adequate to produce too many sub-issues.

@mAAdhaTTah

What's more, it doesn't break referential transparency either. If you took x |> f(^) and replaced it with f(x) (or the value returned by f(x)), the result is the same, which is what referential transparency is; namely, the ability to replace a function with the result of that function without changing the behavior of the application.

 1 |> 5
  |> console.log(#) //5

This seems a valid code of hack, 5 is not a function. Can you replace to f(x) syntax from this code? To me, the hack pipe obviously breaks referential transparency, or am I wrong. Please explain.

If you're referring specifically to this quoted line: value |> (value = ^) |> log(^)

(value = ^) is some value, so I thought I could replace it 5 which is also a value in the principle of referential transparency.

ken-okabe commented 2 years ago

Please note: in the current JavaScript, I could replace any (x = y) to z (referentially transparent), because the operator = means the left and right equal from now on and returns the value. Does hack pipe Implicitly overrides the =??

kawazoe commented 2 years ago

@stken2050 Well... based on what @mAAdhaTTah just said, maybe 1 |> (^ = ^) |> log(^) would be valid? Or I guess it cannot be assigned either? That code definitely looks very happy 🤣

More seriously, you could always do 1 |> true ? 5 : ^ |> log(^) to "bypass" the condition. It's not a requirement that the value gets used, just that the marker is present.

mAAdhaTTah commented 2 years ago

This seems a valid code of hack, 5 is not a function.

No, it isn't. It's a SyntaxError.

maybe 1 |> (^ = ^) |> log(^) would be valid?

Cute, but no, you can't assign to ^. It's immutable for the scope of the RHS it's injected into.

ken-okabe commented 2 years ago

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

ken-okabe commented 2 years ago
  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 commented 2 years ago

@kawazoe

that Hack style does not behave like a binary operator.

For the very basic math operation of function application f(x), the famous pipeline operator |> does not behave as the binary operator in JS. This is scary.

Hack doesn't break math, nor does it lose JavaScript as a functional language.

Impact of Hack pipes on functional programming should be unacceptable to silent majorities.

kawazoe commented 2 years ago

@stken2050 No, they don't override =. The pipeline operator only requires that the placeholder ^ is present somewhere in the expression. Its absence is the SyntaxError, not the use of 5. This is why value |> 5 || ^ |> log(^) is valid, even though functionally equivalent to your snippet. In a way, An other way to see it is as if (x, y) => x was a SyntaxError because y is not used.

I agree that, this requirement is a bit strange considering how easy it is to bypass. I think the proposal could have done without it from a user's point of view. There might be some obscure complexities introduced by the compiler that makes this care very hard to handle. Either way, it is still something "weird" to do in a pipeline.

mAAdhaTTah commented 2 years ago
  1. Writing to a variable outside the scope of the lambda (F#) or expression (Hack) is neither referentially transparent nor side effect free in either proposal.
  2. Both pipe operator proposals are binary operators. Both are applications of their LHS to their RHS; the semantics of what and how they're applying is what is different.
  3. I really have no idea what any this has to do with overriding the =, which neither proposal does.
  4. We are way off topic, because none of this prevents you from using Hack as a functional pipe. This argument isn't convincing or compelling to me so I'm going to decline to engage with it further.
js-choi commented 2 years ago

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 https://github.com/tc39/proposal-pipeline-operator/issues/215#issuecomment-922259832. 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 commented 2 years ago

@js-choi First of all, thank you for opening the issue of this aspect and I also appreciate your hard work (must be, I guess) for Brief history of the JavaScript pipe operator that I think the information listed is extremely valuable for everyone who concerts the matter.

For the "Impact of Hack pipes on functional programming", I would like to paraphrase "Impact of Hack pipes on algebraic structure in JavaScript".

@stken2050: Thanks for your comments, and I am glad you found HISTORY.md to be informative.

To be honest, I kind of find the following comments about algebra confusing. The Hack pipe is a pure binary operator with special evaluation rules (just like how => is a binary operator with special evaluation rules). The Hack pipe, just like =>, is by itself referentially transparent, but it (again, like => and every other JavaScript operator) may contain an expression that itself performs side effects. the Hack pipe do not introduce more side effects; the Hack pipe is a pure operator; it binds a constant free variable, which is a pure operation, just like => and the lambda calculus). The Hack pipe does not change the meaning of =—but (just like => and every other JavaScript operator), the Hack pipe may contain an assignment operator or a function call that performs a side effect.

Hopefully that makes sense. But this is all off topic. This thread is about the impact of the Hack pipe on the JavaScript functional-programming ecosystem. My apologies for marking your comments as off topic—I’m marking this comment as off topic too.

If you’re still concerned about side effects or referential transparency, then I would encourage you to create new issues devoted to this. 😄

js-choi commented 2 years ago

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.

@kawazoe: This is a well-reasoned comment.

Standardizing F# pipes alone would encourage quite different patterns from what Hack pipes (even with F# pipes) would encourage. They would encourage many curried functions.

Lots of people have strong opinions about which approach is better to encourage. It is certainly true that, in many statically typed functional programming languages, automatic currying might encourage certain good habits. (I myself am a huge fan of F#, and I think several aspects of Dan Syme et al.’s design are ingenious.) But JavaScript differs from them in many fundamental aspects (https://github.com/tc39/proposal-pipeline-operator/issues/215#issuecomment-922361733 has some good points about some important impedance mismatches between statically typed FPL and JavaScript).

In any case, as you know, several TC39 representatives (not only inside but also outside of the pipe champion group) unfortunately have strong concerns about encouraging F# (see HISTORY.md, #221, and https://github.com/tc39/proposal-pipeline-operator/issues/215#issuecomment-922178037).

Many of these concerns (particularly from engine implementers) include memory performance. But many of these concerns also include ecosystem siloing/forking.

We the pipe champion team 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.

But we understand that many people used to tacit pipe functions may be skeptical of this prediction. And I accept that this may indeed be partially motivated reasoning reached after F# pipes and PFA syntax ran into their walls (#221)—because in the end we cannot predict for sure what will happen. (However, I hope to improve examples of interoperability between curried functional APIs and traditional/web APIs—see #218).

In any case, we still need to document these discussion points in our explainer. Thank you for the well-reasoned comment.

mohaalak commented 2 years ago

I think all of this discussion can be reduced if we just accept the smart mix option the new hack pipe will be accepted the js functional programming ecosystem does not change anything and people won't use currying so much when there is hack type. @js-choi is there any way that the smart mix option will be discussed one more time?

js-choi commented 2 years ago

@mohaalak: This is somewhat covered in #221 and https://github.com/tc39/proposal-pipeline-operator/issues/202#issuecomment-918595019. Like #221 talks about, F# pipes and smart-mix pipes have run into a lot of deep concerns from TC39 representatives outside of the pipe champion group since 2017. There is almost no way that smart-mix pipes (or F# pipes) would pass through TC39 in the foreseeable future (or—hopefully not—ever). I personally plan to fight for F# pipes and PFA syntax in the future, but trying to add them now would probably kill any prospect of TC39’s representatives agreeing about any pipe operator.

This is off topic, though, so I’d like to direct you to #221 and #202. I’ll mark my own comment as off topic, too. (My apologies!)

ken-okabe commented 2 years ago

@js-choi

I thought I was invited to express my opinion in your new threads from the previous thread. That is what you told us.

It turns out you guys still keep closing around posts as offtopic. I took my time to post my opinion so as yours, and ok, where is the adequate on-topic for https://github.com/tc39/proposal-pipeline-operator/issues/217#issuecomment-922358825 ?

If there is not, I will start a new one for this, Is it fine?

lozandier commented 2 years ago

@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.

I think it only makes sense for that to be done for more informed decisions to be made than the whataboutisms and ancedotal experiences masked as facts from various sides of the direction this standard should go. I remain unconvinced hack-style with no notable traction in comparison to F#-style before, during, and after its introduction towards advancing in the proposal process is the way to go for use cases like chaining with its current semantics.

Simultaneously, anecdotal accounts on my part, the current champions, and others need to be deprioritized with actual quantitative data from the JS community.

//cc @mAAdhaTTah @tabatkins @rbuckton

tabatkins commented 2 years ago

We've gotten a lot of feedback from various sources, including those very surveys.

Note that surveys are not remotely "quantitative data", however. Without significant effort to understand the surveyed audience's demographics and account for them, they're good for vibes only; the audience is often heavily biased toward particular communities (often ones that explicitly encourage their followers to take the survey!). We use them to help surface concerns that might not be obvious to committee members who mostly write C++ or Rust instead of JS; we very explicitly do not attempt to do design-by-survey, in this committee or any other spec committee I've ever worked in.

The closest thing you can get to reasonably quantitative data in this field is performing user studies. These are pretty difficult to do well, tho, and expensive! We don't perform them lightly (or at all, usually), and we definitely have never and likely will never gate a proposal's progress behind such a study. That said, the one study in this space that has been done (by Yulia and Moz, thanks!) didn't find strong preferences either way.

So, no, we won't be performing additional studies to guide the design of this proposal. That would be a novel and significant burden that the committee has never before imposed, and there doesn't appear to be a strong motivating reason to change that precedent.

aadamsx commented 2 years ago

@tabatkins I think his point was more about exposing what most of us already know:

Hack pipes is a solution looking for a problem and its current syntax is confusing and hard to reason about.

What most devs want and think about with pipes is the F# style or function based pipes.

If you opened this up to the broader dev community, it would become clear we are on the wrong track with hack pipes. But I get why some would not want to do this as they'd be embarrassed by the results.

tabatkins commented 2 years ago

@aadamsx You've been warned about being disruptive/insulting in this repo before. Do not do it again.

kawazoe commented 2 years ago

@aadamsx As someone who is and has been vocal against hack pipes and would much rather have F# style and curried functions, I disagree with your statement.

Hack pipes is a solution looking for a problem...

Hack pipes is a compromise between what ideal pipes are meant to be and what the current JS language and implementations are capable of. I personally wouldn't call it confusing or hard to reason about, but I did call it verbose and riskier than an ideal implementation in the past.

...its current syntax is confusing and hard to reason about.

There is nothing hard about the idea of an implicitly available variable. In fact, we already have this which also works in a similar way. It is an implicitly provided first argument to every function.

If anything, I have a bigger disagreement with the current choice symbol for hack style, but I also have nothing better to suggest so... eh.

What most devs want and think about with pipes is the F# style or function based pipes.

Yes, but if you really think about it, you'll notice that this solution is way, way more confusing and painful to use than Hack pipes in JS. Since we do not have partial function application (yet), most functions aren't going to be unary out of the box like in F#. Most of the time, you will have to make them unary yourself if you want to use them in pipes, either through a lambda at the pipe level, or through some manual or auto-currying magic like in ramdajs at the function level.

In the first case, you only end up with a more verbose version of the hack pipe since you have to declare the lambda every single time. In the second case, you might curry a large amount of functions that will never get used in pipes, and might generate a large amount of closures doing so; hitting the same memory pressure problem that TC39 was afraid of in adding currying in the language. Plus, you also have to do this for every function you plan to use in pipes, which is even more verbose than the lambda solution.

Now you might suggest that currying could only happen inside of pipes, but then you end up with different behaviors depending on where you use the code, which is definitely a confusing nightmare.

In the end, hack style is just a compromise where you get the whole F# feature set, plus await and yield, except for automatically calling unary functions. In exchange, you gain not having to write that lambda declaration for the 95% of pipe you will use, and where there could be some compiler optimization specifically for these closures, making them more lightweight.

If you opened this up to the broader dev community, it would become clear we are on the wrong track with hack pipes. But I get why some would not want to do this as they'd be embarrassed by the results.

There's nothing to be embarrassed about here... Everyone already knows that hack pipes are not the ideal solution. This is not news to these people. It's just, unfortunately, the best that we can get in this moment.

The way I understand this, this leaves us with two options:

  1. Either go forward with hack style and get a pipe operator in the language now,
  2. Or wait for someone to figure out the currying issue so that we can get an ideal pipe operator later.

The only bad thing with the hack pipe proposal is that it cannot be retrofitted with support for currying in mind. Looking at it in its current state, I'm not sure of how it could be tweaked to open a path for currying in the future and I'm afraid this might get used as a reason for not leaving currying behind forever. I just cannot find a way to properly introduce partial function application in code like this without breaking it:

const foo = (x) => () => x * 2;
const bar = value |> foo(%) |> %() + 1; //< %() will not port nicely
tabatkins commented 2 years ago

There's nothing to be embarrassed about here... Everyone already knows that hack pipes are not the ideal solution. This is not news to these people. It's just, unfortunately, the best that we can get in this moment.

I disagree with this statement. ^_^ I do think topic-style is the ideal solution for Javascript, which is a multi-paradigm language that allows for functional programming in a useful way, but which has many features that make heavily FP-style programming a la Haskell somewhat frustrating. JS has many non-functional operators, including some that are incompatible with wrapper functions (await, yield); it has two separate calling conventions (methods and functions); it lacks automatic currying, and is generally designed in a way that's hostile to such a feature due to variadic and optional arguments (and as a result, functions are usually written with the most important arguments first; the entire web platform is written in that style). All of these together make tacit-style pipelines somewhat awkward of a fit for JS, while topic-style handles all of it elegantly.

In a different language, the environment and constraints could be different, and the conclusions can swap. But we work with the JS we have.

I'm not intending to get in a more general discussion here. I'm just pointing out that topic-style might be a compromise for some people who have different preferences (this is definitely true of some of the champions!), but that is not generally a shared opinion.

lozandier commented 2 years ago

I disagree with this statement. ^_^ I do think topic-style is the ideal solution for Javascript, which is a multi-paradigm language that allows for functional programming in a useful way, but which has many features that make heavily FP-style programming a la Haskell somewhat frustrating.

As you know, I see things differently. :) From the standpoint of occasionally having to use JS for data science/machine learning and cloud computing problems, I find hack-style inadequate and tedious to solve the issues peers and I would primarily want to use a functional composition operator for as explicitly described here

It seems to me that the standard got infatuated by cleverly accounting for edge use cases and suddenly decided to make those easier to write with than the most typical use cases!

As @kawazoe suggested, it seems partial application, Function.pipe, and many other things should exist before this standard is ratified.

Hack-style makes functions behave less as first-class values because you can't do basic function multiplication (composition with unary functions) with an equal amount or fewer characters than composing manually function2(function1(x)); basic multiplication in JS with primitive values don't need (^) (i.e. 2 * 4) yet you need to do this suddenly for functions (i.e. input |> func1(^) |> func2(^)) with hack-styles current semantics. The semantics of hack-style are incredibly confusing to me and others I've exposed the syntax to.

Hack-style counter-intuitively makes chaining more tedious to communicate normally and less straightforward than existing alternatives. As you requested, I've analyzed further newer problems I've raised about the difficulties functionally composing with hack-style in issue #238.

I'm unsure how hack-style better accommodates JS as a multi-paradigm language than F#-style. Arithmetic operators are usually functional and tend to uphold an essential aspect of functional programming hack-style outright rejects: tacitness. Hack-style is contradictory to how the existing multiplication operator in JS behaves (composition is functional multiplication after all) with the token tax it requires for the simplest of functions that can transform input (unary functions)–contradictory to how composition operators are typically represented in math interacting with functions in general:

16913751097cd9b97358bad6e92c4275bd768a7d

What’s currently being done all seems rationalized by a respected super minority in an uncomfortable way for me compared to what most have wanted from an operator that could make writing things in JS functionally substantially easier, making the functional paradigm severely more pleasant/more accessible/useful to resort to in JS similar to what ES2015 classes did for those who are writing programs that are very much invested in writing in an object-oriented or procedural way.


If the hack-style pipeline operator is ratified as-is, I’d still long for an operator to make it easier to compose/chain functions with; I'd still have to use .pipe() abstractions still dreaming for a means to more tersely compose/multiply/chain functions (and Functions.pipe() isn’t it, it just means I would import one less thing aliasing it to a terse variable name wondering why the heck the hack-style pipeline operator didn’t resolve this before being ratified; any ratified pipeline operator makes the functional composition more accessible in controlled environments like programming interviews much more manageable I guess).

I’d frankly be less inclined to use JS for such problems considering how common data pipelining is with what I do working on a scale that its existence will likely be required or its usage will become contentious very fast compared to the use of pipe and library extensions it would sorta replace in controlled/team environments but not optimal at all.

I think the hack-style should refrain from listing chaining/composition for its use cases since it's explicitly designed not to embrace benefiting/appeasing those that wanted an operator to make it easier to communicate and write chaining/composition expressions.

I also think many others, including what notable library authors in this repo have already expressed, will have a similar sentiment. I find that an odd place for an operator that is consistently seen as one of the most desired new standards people want in the language and at this stage distancing itself from one of the top reasons why it became popular as a proposal in the first place: To tacitly communicate functional composition (chaining/multiplication) than what userland can enable.

aadamsx commented 2 years ago

@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.

ljharb commented 2 years ago

@aadamsx i'm pretty sure all expressed views here have been shared with the entirety of TC39, specifically, prior to the Hack decision being made.

aadamsx commented 2 years ago

@ljharb I remeber there were some on the board that actually perferred F# style over hack back in the day -- but were not as vocal or assertive in their stance. @lozandier could add weight & gravitas to that shared view.

Also, since TC39, as you say, has heard all the "views from the F# side" and still decided to go with Hack, IMO it's time for someone to join that has strong views and excellent analysis on this topic in particular to be added. I think @lozandier would be a great addition don't you?

ljharb commented 2 years ago

@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.

aadamsx commented 2 years ago

@ljharb The only reason there's zero possibility at this point is due to who's on the TC39 board. There's no technical reason why we cannot get F# style synax in along with the partial application proposal. With someone like @lozandier on the board F# style with partial applciation could become a possiblity again (or at least non-zero). If this means just doing away with Hack style, I'm all for that -- better to have nothing than Hack IMO.

ljharb commented 2 years ago

@aadamsx sure, but if there exists a single person who objects to F#, it won't happen, and there are far more than one of those. We operate on consensus, which means that the addition of somebody who prefers F# is irrelevant unless there are no people who oppose it.

Additionally, "better to have nothing than Hack" is not an opinion shared by TC39, or else it wouldn't be stage 2. This is decided, there's no point discussing it further, and it will not change.

jridgewell commented 2 years ago

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.

aadamsx commented 1 year ago

@jridgewell thanks for the explination. So with F# Style, this tends to lead to more functions being built, and therefore the compiler has troulbe optimizing for functions?

ljharb commented 1 year ago

Yes, exactly that.

js-choi commented 1 year ago

@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 commented 1 year ago

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 commented 1 year ago

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! :smiley:

jridgewell commented 1 year ago

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 commented 1 year ago

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:

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](https://github.com/tc39/proposal-pipeline-operator/issues/225#issuecomment-927175516)? 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.