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.51k stars 108 forks source link

Separate (complementary) F# pipeline proposal? #202

Open mmkal opened 2 years ago

mmkal commented 2 years ago

As someone who was firmly in the F# and partial application camp, I'm very interested in the teeny section at the end of the readme called Tacit unary function application. Between that and +> I think bases would be really well covered. I don't feel that way about Hack alone, but still think it's fantastic that some form advanced to Stage 2.

Is the plan to include |>> in this same proposal? As a separate one with the same champion(s)? Is there anything that community members can do to maximise its chances (ideally at the same time, so implementers can handle both together)?

kiprasmel commented 2 years ago

What you're referring to is the Proposal 3 - Split mix - it is the better option to the Proposal 2 - the current Hack proposal.

It is, however, still flawed, because there is an even better option:

F# + The partial application proposal:

With it (partial application + F# pipes), you get the same functionality as you'd get with Hack, but 1) without the need of an additional operator |>>, and 2) the partial application functionality would work outside the scope of pipeline operators (meaning the whole language), which is great, as opposed to the special token of Hack that only works in the context of pipeline operators, 3) all other benefits of F# over Hack.

example - F# + partial application:

const multiply = (x, factor) => x * factor;

[1,2,3]
 |> multiply(?, 2)

// which just de-sugars to:

[1,2,3]
 |> (temp1) => multiply(temp1, 2)

it looks the same as Hack, and indeed is the same as Hack! What it does is curries out the argument marked with ?, and the F#'s pipeline operator calls that curried function.

But, while being the same, this proposal has an added benefit that the partial application operator would work anywhere in the language:

const multiply = (x, factor) => x * factor;

// can use directly - just like Hack:
[1,2,3]
 |> multiply(?, 2)

// can create a curried function
const multiplyBy2 = multiply(?, 2);

// and can use it the FP way, without needing an extra operator `|>>`:
[1,2,3]
 |> multiplyBy2

[1,2,3]
 .map(multiplyBy2)

// everyone is happy!

which is, as already mentioned, considerably better than the Hack + |>> proposal.

Why was it not considered? I don't know. In my view, this is the best that can happen with pipeline operators, meanwhile what we currently have in Stage 2 is the worst - even worse than no pipeline operators at all.

Why was Hack + |>> not considered (the proposal 3)? See [1]. Apparently it's because it's 2 operators and no browser vendors want to implement it so nobody championed it, which seems ridiculous because with Hack we're adding 2 operators too, and 3 if we also include |>>.

This is why I feel the whole thing is rushed. One part of why TypeScript ended up so good is because the guy who designed C# also participated in designing TypeScript. With pipeline operators in JS, it becomes more and more obvious to me that there were simply not enough FP-competent people in the TC39 committee, especially from different languages outside JS like Haskell, F# etc. - if there were, there's no way we would've ended up choosing the Hack proposal, especially without |>>.

And some are saying that JS is not necessarily an FP language. I disagree. History repeats itself. The same thing has happened many years ago, when NodeJS chose to use callbacks instead of promises. This is one of the regrets of Ryan Dall, the creator of Node. The arguments were the same as we're having right now, the reasons behind it were the same too. Let's not make the same mistake again.

And even then, with the F# + partial application proposal, nobody's forcing FP down your throat, because you get to choose yourself. This is not the case with the currently Hack proposal, and it is the case with Hack + |>>, though worse because of reasons alreay mentioned.

[1] https://github.com/tc39/proposal-pipeline-operator#tacit-unary-function-application

mmkal commented 2 years ago

Yes, I'm essentially talking about reviving Split Mix, now that Hack pipes has achieved consensus as a direction from the committee.

The line:

Requires browser vendors to agree to implement two similar pipe operators, so nobody is currently backing this proposal

Was written quite a while ago, and I'm hoping things have changed sufficiently that it can be revisited. Specifically, without consensus on one operator, I can see why it would seem risky to put all eggs in a two-operator solution basket. Now that Hack pipes have consensus, hopefully the door is open to at least trying to advance another operator. If it fails, or is delayed, Hack could advance ahead of it - that would be a shame but still better than nothing.

@kiprasmel I don't want to get into debate about Hack vs F# here either (partly because I personally also would have preferred F#). The premise of this particular GitHub issue is acceptance of Hack as the "winner", the topic is whether F# pipes should be a separate proposal or not. As I suggested in the other thread, maybe you should make a new issue in which you can try to make the argument for abandoning Hack altogether. It's off-topic here.

kiprasmel commented 2 years ago

@mmkal thank you, good point, I will make a separate issue.

Though, currently it seems like the trade-offs are being discussed in a gist outside this repo, by a TC39 member who is opinionated towards Hack - I'm not a fan of it being outside this repo:

https://gist.github.com/tabatkins/1261b108b9e6cdab5ad5df4b8021bcb5

Update: see #205

peey commented 2 years ago

It would be good to have separate places for discussion of hack and F#-style proposals.

I believe that earlier hack-style was being discussed at https://github.com/js-choi/proposal-hack-pipes/

Should F#-style proposal related discussions happen at https://github.com/valtech-nyc/proposal-fsharp-pipelines instead? Why is there no link to that in the readme anymore?

Is there no one who's championing the F#-style proposal?

mmkal commented 2 years ago

@peey interesting, I didn't know about that fork. It looks like it hasn't been updated in a while, and if it's to be made complementary to this proposal (which is, at time of writing, Hack), it should remove all special cases for await and change the operator to |>>. It might be easier to write a new one because |>> is so simple - everything messy can be handled by |>. It looks like @mAAdhaTTah was involved in it - any thoughts?

mAAdhaTTah commented 2 years ago

@mmkal If a separate F# proposal is desired, you should create a new repo for it. That repo was for the competing F# syntax before Hack advanced to Stage 2.

mmkal commented 2 years ago

@rbuckton I'd like to respond to your comment in https://github.com/tc39/proposal-pipeline-operator/issues/91#issuecomment-917645179 since it directly relates to the topic of this issue:

I have been a staunch advocate for F#-style pipes since the beginning, but it's been difficult to argue against the groundswell in support of Hack-style pipes due to some of the limitations of F#-style pipes. If we kept debating on F# vs. Hack, neither proposal would advance, so my position as Co-champion is "tentative agreement" with Hack-style. Both sides have advantages and disadvantages. F#-style feels to be a better fit for JS to me, especially given the existing ecosystem, and meshes better with data-last/unary producing libraries like Ramda and RxJS. F#+PFA works well for data-first libraries like underscore/lodash. However, F#-style is harder to use with yield, await, and methods where the topic is the receiver (i.e., ^.method()), which is an advantage for Hack-style.

So we were stuck at impasse. I don't think a solution that mixes both is tenable, and I'd venture to guess that having multiple pipe operators (one for each) may not make it through committee. As what feels like the lone dissenter against Hack-style on the committee (or at least, the only vocal dissenter specifically in favor of F#-style), I'd rather not stand in the way of the feature advancing at all, despite my reservations, because I think it's a valuable addition regardless of approach.

If we could find a way to get two pipe operators through committee, I would support that. JS wouldn't be the first language with multiple pipe-like operators. I've even mentioned up-thread that I'd support a Hack-style "topic pipe" like ^> or ||> or -> along side a F#-style function pipe like |>. That's a separate issue, however, and maybe we can discuss that outside of this issue as something to bring before committee.

Are there specific people (on the committee, or browser implementors) who are strongly against multiple operators? If so, maybe they could be invited to speak up here, since there is clearly huge interest in F#-style pipes (documented in #205) - so an offhand dismissal of multiple operators would be very disappointing: the use cases are real, clear, very popular and there's well-established precedent in other languages.

(A note about popularity - I agree that it would be a bad idea to just make decisions based on GitHub upvotes, but the volume, passion, and popularity of arguments in favour of F# is significant and shouldn't be ignored. Which is why even if there's some resistance to more operators, it's worth pushing IMO. The suggestion isn't one that's made capriciously.)

To make this more of a practical and direct question - @rbuckton as a committee member, do you think a new complementary proposal repo should be created so that this one can focus on Hack? CC @tabatkins @js-choi and @ljharb - would be interested to hear your thoughts on this too.

tabatkins commented 2 years ago

A number of people on the committee are just barely on board with pipeline in general, and specifically found smart-mix to be too complex of a mental model to be worth supporting. I suspect that presenting the same two-version syntax via a pair of operators would bring up the same complaints. (This was never seriously advanced as an option during committee meetings, however, so I'm not 100% sure; this is my intuition only.)

In general, tho, speaking as a language designer with a decade+ of experience, having two distinct features that are only very slightly different is almost always a mistake. You usually want clear, bright lines separating features, so people know when it's appropriate to use each feature. Subtle differences tend to bring confusion instead; it's almost always the right choice to instead just choose one variant and let the other use-cases be slightly inconvenienced. (Or, sometimes, not doable at all; you want to avoid locking out use-cases as much as possible, but good design sometimes has to accept that as a tradeoff.)

Sometimes that's not the best choice, if there are multiple use-cases that are roughly equally vital and the inconvenience of doing one in the other's version of the feature is significant. But that's usually not the case, and definitely not here - as has been argued a number of times, even if you have a library built on returning unary functions, using them in a Hack-style pipeline just requires appending (^) to the end of the function.

So, as a champion I would be pretty strongly against a two-operator solution, where the two are precisely identical in powers, but just call their functions slightly differently.

tabatkins commented 2 years ago

That said, if anyone wants to pursue any of these ideas on their own, I have neither the power nor the desire to stop you. ^_^

js-choi commented 2 years ago

I am personally pro-eventually-adding-a-split-mix tacit-unary-function-call |>>. I agree with @rbuckton (https://github.com/tc39/proposal-pipeline-operator/issues/91#issuecomment-917645179) that |>> is a reasonable goal.

JS wouldn't be the first language with multiple pipe-like operators.

However, @tabatkins is right in that the committee was barely supporting any pipe operator (there are still some delegates that argue for no pipe operator at all). That was much of the reason why any pipe operator was stalled for such a long time (before the State of JS 2021 results showed that lots of people requested a pipe operator). (I plan to add a history.md timeline to this repository within the next few days that talks about this in more detail.)

I’ll copy and paste some parts from what I said here: https://github.com/tc39/proposal-hack-pipes/issues/18#issuecomment-912661139

Whether to reintroduce split mix (|>>) and whether to do it now or later

The second thing to address is whether and when we should try to reintroduce what we have called “split mix”: a separate pipe operator |>> for tacit unary function calls. This would allow people using data-last / curried-function styles to omit (^).

I think @tabatkins has said before that also having |>> might be a hard sell to the Committee, because from JavaScript’s perspective x |> f(^) is equivalent to x |>> f under this paradigm […]. But |>> is something that I would be happy to work on…

…It’s just that the Committee may well balk if we do too much at once. It’s already having a hard time swallowing any pipe operator. It generally prefers to do things piecemeal, while keeping forward compatibility with future extensions. And Hack pipes are forward compatible with |>>.

My inclination (@tabatkins, @ljharb, @mAAdhaTTah, others are free to insert their own opinions) therefore is to keep this first pipe proposal focused on Hack |>, and to leave |>> to a later proposal (if |> ever even gets accepted, as we hope).

(Aside: I would like to push back at “these FP libraries represent the majority of the pipe function usage that the pipeline operator is supposed to replace”. Although unary function calls are a significant use case, from this proposal’s perspective, unary function calls are not the most common use case. The pipe operator is not supposed to replace only unary function calls; it is supposed to replace deeply nested expressions in general, which occur in all APIs. In the worst case, people using Ramda/RxJS/etc. can keep using pipe functions for curried-function calls…while still finding Hack pipes useful for interoperation with other APIs’ data-first function calls, function calls with trailing option objects, Web APIs, and so on.)

Anyways, I’m personally enthusiastic about |>> for tacit unary function calls and, in fact, I’d be eager to eventually write a proposal for |>> myself. (Clojure is one of my homes, and Clojure has a ton of threading macros, so I’d be used to having two pipe operators.) It’s just that the Committee may recoil from too big a bolus of syntax at once…It’s already been hesitant at even one pipe operator. If there’s enough of a use case for |>> (e.g., working with TypeScript’s limited type unification before microsoft/TypeScript#30134 lands), then it can fight for it on its own merits. I would be happy to try to make that fight for it, later.


I will say that partial function application may definitely coexist with Hack pipes—either in the form originally proposed by @rbuckton (which has eagerly evaluated partial arguments, once before any function calls) or as Hack pipe functions (which are like arrow functions in that the partial arguments are evaluated lazily, at each function call).

I would be happy to fight for a partial-function-application syntax later, too.

mmkal commented 2 years ago

@tabatkins @js-choi thank you - that's very helpful context.


A number of people on the committee are just barely on board with pipeline in general

Hmm. I'd be interested to learn more about this - are there any specific meetings that it'd be worth reading to understand this better in https://github.com/tc39/notes?

In general, tho, speaking as a language designer with a decade+ of experience, having two distinct features that are only very slightly different is almost always a mistake

There are many counterexamples to this in successful and popular languages, including F# which has |>, >>, <|, <<, as well as the ability to call functions like add 1 or add(1).


Also, I don't think this is true:

EDIT: THIS EXAMPLE IS WRONG > even if you have a library built on returning unary functions, using them in a Hack-style pipeline just requires appending (^) to the end of the function ```ts const {memoize} = require('lodash') const track = value => value |> ^.toLowerCase() |> memoize(trackEvent)(^) ``` Behaves differently from this: ```ts const {memoize} = require('lodash') const track = value => value |> ^.toLowerCase() |>> memoize(trackEvent) ``` In the first `track('FOO'); track('foo');` will lead to `trackEvent` being called twice. In the second, once. In the case of `memoize`, it's probably the second one that I want. In other situations, it might be the opposite. Of course, there are workarounds, but it's a bit of a footgun. And the workarounds aren't always appropriate (above, it might not be desirable to add a `const memoizedTrack = memoize(trackEvent)` to the scope). This is much more than a three-character tax, the actual runtime semantics are different.

Another case, where it is just tax, but it's more significant than (^) at the end:

value
  |> ^.toLowerCase()
  |>> JSON.parse
  |>> ({ x, y, z }) => {
    const api = new SideEffectyAPI(x)
    api.initialize(y)
    return api.foo(z)
  }

vs

value
  |> ^.toLowerCase()
  |> JSON.parse(^)
  |> (({ x, y, z }) => {
    const api = new SideEffectyAPI(x)
    api.initialize(y)
    return api.foo(z)
  })(^)

Needing to wrap the arrow function in parens is a heavy, confusing, multi-line tax.

Granted, SideEffectyAPI here is probably not well designed, but it might be an external library that I can't control.

Note: do-expressions might mitigate this problem somewhat.

Lastly, even under "normal" circumstances the tax is sometimes just... lame. And there will always be some data-last unary functions. How weird and wrong the following looks will be a disincentive to using language-level syntax for pipelines and will lead to deep schisms, instead of encouraging almost all JS users to write in a similar way. Weird and wrong-looking example:

getUser()
  |> Either.fold(
    err => `Something went wrong getting your user info. ${err.message}`,
    user => `Hello ${user.name}`
  )(^)
  |> console.log(^)

I suspect FP users will just use their user-defined pipe functions, and other users won't use FP libraries' utilities, when it doesn't have to be that way.


That said, if anyone wants to pursue any of these ideas on their own, I have neither the power nor the desire to stop you. ^_^

Err... aren't you a committee member? Doesn't that mean you do have the power, since stage-advancement requires 100% consensus? Good to know that you don't have the desire, in any case!


@js-choi good to hear! It'd be great to know what you think the right timing would be.

sandren commented 2 years ago

In general, tho, speaking as a language designer with a decade+ of experience, having two distinct features that are only very slightly different is almost always a mistake.

@tabatkins Does that mean the adoption of Hack-style would decrease the chance of partial application reaching Stage 4? If so then I feel that the Hack-style proposal should be evaluated against both F# and partial application rather than F# alone. I know that's not the norm for the TC39 process, but this proposal has been anything but ordinary.

Is there any chance that the committee would consider evaluating the pros/cons of advancing Hack-style against advancing both the F# and partial application proposals before any reach Stage 3? I think it would be helpful to consider if advancing both F# and partial application to Stage 2 simultaneously may be better for the JavaScript language than either pipeline proposal alone.

I also think doing so in earnest would help alleviate some of the contention around the pipeline operator in general regardless of the outcome. 😀

ljharb commented 2 years ago

@sandren no, partial application has a lot of obstacles to work through on its own, and pipeline advancing as F# wouldn't help it much.

js-choi commented 2 years ago

good to hear! It'd be great to know what you think the right timing would be.

Does that mean the adoption of Hack-style would decrease the chance of partial application reaching Stage 4? If so then I feel that the Hack-style proposal should be evaluated against both F# and partial application rather than F# alone. I know that's not the norm for the TC39 process, but this proposal has been anything but ordinary.

And at the cost of a pipeline operator that will provide something new, and I assume at the cost of the partial application proposal.

I personally would support a syntax for partial function application.

I don’t think that the adoption of Hack pipes would decrease the chance of a PFA syntax. They can coexist.

However, I also think any syntax for partial function application (which I personally do support) would continue to run against pushback at the committee, regardless of what the pipe operator happens to be. Like @ljharb said, F# pipes advancing actually wouldn’t help PFA syntax that much.

So, although I’m somewhat hopeful for the future of PFA, I also have tempered expectations. This has little to do with Hack-vs.-F# pipes and more to do with other TC39 delegates’ concerns about PFA in general (like performance and aesthetics).

I’ll paste what I said in https://github.com/tc39/proposal-pipeline-operator/issues/207#issuecomment-918596951:

For what it’s worth, I think that partial function application could coexist with Hack pipes—either in the form originally proposed by @rbuckton (which has eagerly evaluated partial arguments, once before any function calls) or as Hack pipe functions (which are like arrow functions in that the partial arguments are evaluated lazily, at each function call).

I’d be happy to fight with @rbuckton for a partial-function-application syntax later. But it might be an uphill battle (and not because of Hack pipes). From what I recall, some people on the committee have always been against partial-application syntax (independently of pipe). I’m not one of them, but…

So, pushing for partial function application should probably wait, until some pipe operator would get further. The Committee can stomach only so much new syntax at once.

lightmare commented 2 years ago

@mmkal

const {memoize} = require('lodash')

const track = value =>
  value
    |> ^.toLowerCase()
    |>> memoize(trackEvent)
track('FOO'); track('foo');

Could you please explain how this would end up calling trackEvent only once? I can't see it.

kiprasmel commented 2 years ago

why are we even bothering to discuss this? Tab will lock this out, just like they did with #205.

There's no point on focusing on the proposal itself right now - let's first solve the corruption issue maybe?

https://github.com/tc39/how-we-work/issues/96

mmkal commented 2 years ago

EDIT: THIS IS WRONG

@mmkal

const {memoize} = require('lodash')

const track = value =>
  value
    |> ^.toLowerCase()
    |>> memoize(trackEvent)
track('FOO'); track('foo');

Could you please explain how this would end up calling trackEvent only once? I can't see it.

@lightmare my apologies, you are totally right. Maybe this example makes more sense.

const logSlowResponses = threshold => value => {
  if (Date.now() > threshold) {
    console.log('slow response!', value)
  }
  return value
}

value
  |> fetchFromSlowAPI(^)
  |> logSlowResponses(Date.now() + 3000)(^)

👆 will never log whereas using |>> we can make sure it throws when the API was too slow, as expected:

value
  |>> fetchFromSlowAPI
  |>> logSlowResponses(Date.now() + 3000)
lightmare commented 2 years ago

@mmkal I still don't get it. For the second example to log "slow response!", you need Date.now called before fetchFromSlowAPI.

lightmare commented 2 years ago

Is this kind of what you're trying to express?

const pipe = (input, ...funcs) => funcs.reduce((x, f) => f(x), input);

pipe(
  value,
  x => fetchFromSlowAPI(x, xyz),
  logSlowResponses(Date.now() + 3000),
);
mmkal commented 2 years ago

EDIT: THIS IS WRONG

@lightmare yes, that's basically it. I think I got my example wrong again. I've updated my example to consistently use |>>. That is, I changed |> fetchFromSlowAPI(^, xyz) to |>> fetchFromSlowAPI (since the xyz was made up anyway).

So, the updated example is

value
  |>> fetchFromSlowAPI
  |>> logSlowResponses(Date.now() + 3000)

Since |>> is just an operator which is operating on functions, logSlowResponses(...) is called before fetchFromSlowAPI(...), and the curried function return value logSlowResponses(...)(...) is called after.~

lightmare commented 2 years ago

@mmkal Ok thanks, now I understand. It won't work that way, though:

Since |>> is just an operator which is operating on functions, logSlowResponses(...) is called before fetchFromSlowAPI(...), and the curried function return value logSlowResponses(...)(...) is called after.

Every binary operator in JS evaluates its operands left-to-right.

The F# pipeline operator has to be left-associative. x |>> f |>> g is equivalent to (x |>> f) |>> g, but x |>> (f |>> g) is something completely different.

Going back to your example:

value
  |>> fetchFromSlowAPI
  |>> logSlowResponses(Date.now() + 3000)

The order of evaluation is:

topic = value
topic = fetchFromSlowAPI(topic)
topic = logSlowResponses(Date.now() + 3000)(topic)
mmis1000 commented 2 years ago
value
  |> ^.toLowerCase()
  |> JSON.parse(^)
  |> (({ x, y, z }) => {
    const api = new SideEffectyAPI(x)
    api.initialize(y)
    return api.foo(z)
  })(^)

I through this is probably better handled by do expression proposal instead. Juts like F# pipe + partial function Hack pipe + do expression is probably another good combo at preserve proper reading order

value
  |> ^.toLowerCase()
  |> JSON.parse(^)
  |> do {
    let { x, y, z } = ^
    const api = new SideEffectyAPI(x)
    api.initialize(y)
    api.foo(z)
  }

BTW.

The kotlin actually allow similar usage I stated above (mixing statement and expression in a long chain). The above can actually be written in valid kotlin with minor change.

value
  .let { it.toLowerCase() }
  .let { JSON.parse(it) }
  .let { (x, y, z)->
    val api = SideEffectyAPI(x)
    api.initialize(y)
    api.foo(z)
  }

(I am not saying it is a good or bad practice to write wildly long chain. Just say there are already some languages allow so)

js-choi commented 2 years ago

I through this is probably better handled by do expression proposal instead.

See also tc39/proposal-hack-pipes#4 (which led to me rewording some stuff in the explainer a few months ago).

tabatkins commented 2 years ago

Yup, a few people have addressed it now, but I'll stress again that val |> foo(^) |> bar(^) (Hack-style) and val |> foo |> bar (F#-style) are precisely identical in every observable aspect of their execution - timing, ordering, etc.. You need to examine the AST to tell they're different.

The confusion is likely stemming from the fact that both are different from val.pipe(foo, bar), because functions evaluate all of their arguments before beginning execution. So in this case, the foo and bar expressions are both evaluated before you start piping to either of them, while in F#-style the foo expression is evaluated, then piped to, then when it returns the bar expression is evaluated.

This is the same as foo() + bar() + baz() - foo() and bar() are evaluated, then they're added together (potentially invoking their .valueOf() or .toString() methods, which can be observable and/or slow), then baz() is evaluated and added to the result. Operators work a little bit differently than functions, is all.

tabatkins commented 2 years ago

An example showing off the above:

function foo(v) { 
    console.log("call " + v); 
    return {
        valueOf: ()=>{
            console.log("value of " + v); 
            return v;
        }
    }
}

foo(1) + foo(2) + foo(3)

// logs:
call 1
call 2
value of 1
value of 2
call 3
value of 3
mmkal commented 2 years ago

Thanks @lightmare and @tabatkins - I actually didn't know that about +, and it's definitely a good thing that operators work that way. Sorry for the confusion/bogus example. I've annotated my comments.

Still, I think the "tax" is significant, since it looks wrong enough that it will put people off using it, leading to siloing of the community (rambda/rxjs/fp-ts users preferring their own pipe implementations). Which, as we've seen with how heated this repo's issues have gotten, is harmful and counter-productive.

js-choi commented 2 years ago

@mmkal: Yeah, those concerns about siloing the JavaScript tacit-programming community are understandable. I’m sorry for all the frustration that people who like tacit programming have had—feeling like something was promised but taken away. (See https://github.com/tc39/proposal-pipeline-operator/issues/206#issuecomment-918609518.)

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.

And we think 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.

And, in the future, although it faces many barriers, I might try to propose a tacit F# pipe operator too and/or help the partial-function-application syntax (https://github.com/tc39/proposal-pipeline-operator/issues/202#issuecomment-918595019). However, I know that some implementors and others on TC39 are skeptical of widespread tacit programming, and this would be an uphill battle—but it would be even with F# pipes; those concerns are separate from Hack pipes.

sandren commented 2 years ago

Something just feels very wrong about this whole situation.

The FP JS/TS community has been eagerly awaiting and advocating for the pipeline operator for years, but the official TC39 position seems to be:

"the pipeline operator isn't for you; please use your pipe() function"

I don't understand. What does it even mean for a pipeline operator proposal to not facilitate functional programming?

Think about if the opposite situation were true and instead there was a Hack-style proposal for years with little interest and a new F# proposal emerged with a groundswell of community support, wouldn't it be the F# proposal that advances?

But in reality it feels like the original intent and enthusiasm for the pipeline operator is being used to advance an alternate proposal that wouldn't reach Stage 2 on its own merit.

voronoipotato commented 2 years ago

Yeah I was super upset because it felt like I had been bait and switched, which I am confident was not their intent but when it was proposed as Pipes; I was thinking javascript pipes, function pipes. I didn't even know hack existed before this proposal. I'm sure your language is awesome, and maybe I'll give it a try after all this, but if a javscript dev is excited about pipes, they are probably referring to the thing they are currently using called pipe in javascript and not a different thing. It's been genuinely a confusing and upsetting week but I'm trying to keep my chin up.

mmkal commented 2 years ago

@js-choi yeah, I do agree that Hack-pipes will decrease siloing somewhat, I'm personally not in any way suggesting they Hack pipes are bad (and anyone who is should use a different issue to say it). It's just a concern that they don't go far enough. Luckily the current form is totally forwards-compatible with |>> (and +>). They will work together really nicely!

My prediction: Hack pipes alone will increase interoperability a bit, but there will still be big silos full of the people who are objecting so forcefully to Hack, and those who agree with them but aren't into GitHub flamewars. Basically it's not just a tax of three characters on each step (more than three if a multi-character topic is chosen), it's the harm that will be done by subsets of the community avoiding that tax.

tabatkins commented 2 years ago

Reminder that speculating on people's motives is off-topic for any thread in this repository.

voronoipotato commented 2 years ago

Reminder that speculating on people's motives is off-topic for any thread in this repository.

Could you elaborate which parts of the posts you hid are speculating on people's motives? I thought I was well within the lines, even being charitable.

We're trying to help represent a large community who will unambiguously feel betrayed when this gets released regardless of your intent. You have a proposal that means the world to functional js people, and then you tweak what that proposal means to appeal to everyone, but not everyone is passionate about pipes and you're removing and explicitly discouraging what is important to us. The ones who by my understanding were the ones who actually promoted it. Maybe the hack pipe should be |>> . It's genuinely hurtful when you delete/hide posts for being politely acknowledging the situation as it is. We already feel bowled over, disregarded, like in hitchhikers guide when they said they're going to tear down his house to build a bypass and it has been in the committee for 3 months so it's too late to speak up because everything has already been decided.

ken-okabe commented 2 years ago

@js-choi I really appreciate your comments which are very informative to me, and I'm very sorry to be late to join this discussion. In fact, I'm in the same position as @voronoipotato

when it was proposed as Pipes; I was thinking javascript pipes, function pipes. I didn't even know hack existed before this proposal.

According to

StateOfJS 2020: What do you feel is currently missing from JavaScript? https://2020.stateofjs.com/en-US/opinions/missing_from_js

image image

I understand the expectation to pipeline-operator has been huge among JS community, and the most are silent majorities who even don't know what's actually coming is hack style pipeline-operator.

We, I can say "we" because I think myself is one of the silent majorities, we understood that there has been Minimal proposal for a long time that is x |> f === f(x) I am pretty sure the majorities of JS community still believe this minimal-style is the base of whatever style that will be chosen because it seems everyone has been adopted to Minimal proposal. Pipeline operator in JS: support the most minimal proposal

F# style surely includes the Minimal proposal but it doesn't matter to me because as long as the minimal is satisfied, the rest of the factor doesn't mean much, so I haven't looked closer. Perhaps I've seen the word "hack" and I just thought it's just another version on top of the minimal proposal.

Which is why I did not join the discussion until now, and I was very surprised to have found actually we will never be allowed to treat x |> f === f(x).

After this surprise, I've been reading threads, and seen an opinion hack-style is identical to F# style, and no, firstly anyone can see hack-style is something very different from the minimal proposal, and the way to write in minimal-style is actually prohibited.

I would say, to be precise it's minimal-style not F#-style. Functional programmers including me have been assuming minimal-style pipe as a matter of course because it's a natural and minimal concept of the pipeline-operator which should be mathematically consistent.

Functional programmers or silent majorities in JS community want just a minimal style pipeline-operator, and surely, some syntax sugar on top of the minimal would be nice as long as the minimal specification is satisfied. F# would be the one but F# doesn't have to be. The silent majority would never expected the minimal specification is not satisfied, and I think this is because they share implicit understanding violating the Minimal proposal means violating mathematical consistency, that would never happen we expected.

Case-1

Thanks for letting me know: https://github.com/tc39/proposal-hack-pipes/issues/18 Concerns regarding type inference and data-last functions #18

Unlike the F#-style, the Hack-style is incompatible with the majority of pipeable APIs that already exist—data-last functions in widely used libraries such as RxJS and fp-ts.

I see @OliverJAsh is one of the FP experts in JS community, and he opened this new issue just 2 weeks ago. I think he must be one of the silent majorities and had not noticed the inconsistency of hack-style until now.

You kindly replied:

I’m hopeful that TypeScript will improve its type unification so that it retains free types (microsoft/TypeScript#30134), but of course this means that we can’t do map(user => user.name)(userOption) today, and we therefore can’t do userOption |> map(user => user.name)(^) today. I’m sorry that Hack pipes don’t give a good answer to this now—other than to manually annotate types as needed (which TypeScript developers often already have to do) and hope for microsoft/TypeScript#30134 to land.

In fact, this is not an issue of inference ability of TypeScript.

The |>> operator ("tacit unary function application") does not suffer from these problems

The |>> operator ("tacit unary function application") has mathematical consistency and it is surely consistent in the current TypeScript inference logic.

The problem has not existed until the Hack-style is tried,

Case-2

Another problem emerged a few days ago https://github.com/tc39/proposal-pipeline-operator/issues/206 Should enabling point-free programming/APIs be a goal of the Pipeline Operator? #206

@mmkal: Yeah, those concerns about siloing the JavaScript tacit-programming community are understandable. I’m sorry for all the frustration that people who like tacit programming have had—feeling like something was promised but taken away. (See #206 (comment).)

The miminal-style has been consistent with Point-free, tacit programming, and again hack-style breaks that.

Case-3

https://github.com/tc39/proposal-pipeline-operator/issues/208 Question about ^ when a "child pipeline" is present. #208

Hack style introduces new context variables which should be avoided even it's lexical bindings since which provides us an extra complexity.

Sure, the lexical bindings are one of the essence to build FP language, but to force programmers to hold an extra variable that is fundamentally unnecessary in mathematical sense is against the simplicity and the reason of BUGs.

This is not the problem like "a little bit (^) after the line.." or people will eventually be accustomed to, but the problem of robustness of software development.

In software development, we've been fighting against the complexity and many trials failed, OOP is the one. They invented some artificial mechanism that actually corrodes robustness of the math structure. FB React finally discarded OOP Classs from their framework.

Case-X

The Case-1,2,3 are the tips of the iceberg. They are recognized during the very short period recently. To me, it's not surprising at all because I knew it's coming since Hack-sytle introduces mathematical inconsistency since as a spec, which has broken a very simple algebraic structure.

More cases will come when the silent majorities become not silent, and that's exactly what's going on now.

Hope and Reality

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.

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.

I understand your good will but I'm afraid to say too optimistic. You mention "functional community" or "functional libraries", and I also use these phrase, but in reality there is no border between "functional" and "majority" because "functional programming" is simply Algebra. Now, hack-style breaks a simple algebra structure of function application f(x) https://ncatlab.org/nlab/show/function+application or binay operation https://ncatlab.org/nlab/show/operation

When a programmer write a code of function application: f(x) === x |> f which is "minimal proposal", no one expects we must treat an extra context variables etc. This simply violates the simple algebraic structure.

Even if you hope to help increase interoperability between functional APIs and non-functional APIs, tweaking and breaking the structure of an algebra operator itself is taboo, instead, if we want some syntax sugar, the one should be top on f(x) === x |> f without messing the structure.

Partial application

https://en.wikipedia.org/wiki/Partial_application

Partial application does actually help increase interoperability between functional APIs and non-functional APIs.

Your focus has been wrong. Leave the Math operator alone that is fundamentally not a realm for language artificial design or invention by a human designer.

The focus or our resource goes to Partial application which is the famous mechanism or the syntax sugar, and this is established , robust and consistent.

I've read through @mAAdhaTTah blog entry: Hack Pipe for Functional Programmers: How I learned to stop worrying and love the placeholder

The problem he suggests has been resolved with partial application, so I am not convinced to chose Hack pipe.

In this thread, as @kiprasmel presented: https://github.com/tc39/proposal-pipeline-operator/issues/202#issuecomment-917626903

With it (partial application + F# pipes), you get the same functionality as you'd get with Hack, but 1) without the need of an additional operator |>> , and 2) the partial application functionality would work outside the scope of pipeline operators (meaning the whole language), which is great, as opposed to the special token of Hack that only works in the context of pipeline operators, 3) all other benefits of F# over Hack.

Another insight https://github.com/tc39/proposal-pipeline-operator/issues/205#issuecomment-918370726 @SRachamim

Hack style is a bad proposal that tries to combine two great proposals: F# |> + ? partial application. For me there's no question. F# is the only way to go.

This is the right orientation of software design, and actually been known and used for a long time.

Mathematically, *HackStyle = MinimalStyle PartialApplication* where `` is an operator of function composition.

In software design, we must know: One Task at a Time Code or a function should perform only a single task and use function composition image

or more generally, KISS principle

When we know the two are equal:

The latter should be chosen for robustness, and actually we already have found at least 3 cases that HackStyle has broken the existing code and failed to prove the robustness.

The Hope is one-sided

The silent majorities just need MinimalStyle, and for most of the functional programmers they don't need PartialApplication because they only use unary functions like Haskeller.

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.

"interoperability" arises from PartialApplication and not from the binary operator of function application that structure is strictly defined in Math. Please don't mess the Math. We need to respect.

Ok? Everyone is happy because we take advantage of wisdom of simple functions and function composition.

Not too late

Anyways, I’m personally enthusiastic about |>> for tacit unary function calls and, in fact, I’d be eager to eventually write a proposal for |>> myself. I would be happy to try to make that fight for it, later.

Thank you. I'm very happy to hear that, and probably the invisible silent majorities of JS community, too.

PS. Since MinimalStyle is a minimal operator, |> should be assigned to the minimal-style or F#-style,. Since HackStyle is a result of composition, |>> should be assigned to .

kawazoe commented 2 years ago

@stken2050 I'm speaking up here because I noticed your posts in the TypeScript draft PRs and you said you wanted the opinions of TS devs. I've tried the hack-style implementation over there for a bit and, I have to say, while I was strongly in favor of this style early on, I have came to dislike it. I really liked its portability and it did fulfill its promise of compatibility.

Excluding all of the nice FP principles that you mentioned in your post here, I think that we should also focus on the direction we want to give to the language. Do we want to encourage proper FP style code, where currying and data-last is the norm? Or, do we only want to be "compatible" with it, while keeping FP on the side? To me, this is core to deciding between a minimal/F#-style pipeline operator and a hack-style one; much much more than any mathematical correctness argument you might come forward with. I want a language that helps me write good, maintainable code, while minimizing the risks of mistakes. If it needs to be algebraic to accomplish this, then so be it, but to me, it is not a priority.

With that in mind, I have to give it to the FP crowd. FP appears to be amazing in reducing mistakes and writing maintainable code. For this reason alone, I am incredibly exited by the pipeline operator proposal in JavaScript which brings a big tool from FP languages in the mainstream. That being said, with its "tax everything" approach, Hack-style does not encourage FP programing, or any other form for that matter. You don't gain anything from doing OO or FP code when you need to use a pipeline operator in this form. They are both taxed equally. This is, I think neither good nor bad. It is what I, as a language designer with a vision of backward compatibility and "supporting every one", would also suggest.

On the other hand, since the F# proposal taxes non-FP code (that is code with multiple function parameters and/or code that doesn't keep data as a last parameter and/or code that uses this and/or ...) much more than proper FP code, it actively encourage people that wants to use the operator to write proper curried functions with data as their last argument. While it remains compatible with all styles, it actively pushes toward FP style code. If you make FP code more readable in pipelines than other forms, you will naturally end up with more FP code in pipelines. As pipelines become more and more mainstream, people will actively seek FP-styled libraries to use because they like pipelines. It creates a positive feedback loop that means more FP code which, is arguably a good thing on the basis of maintainability and minimizing the risks of mistakes.

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 chose 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. While 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. You have to explicitly state that you are up to no good in this part of the pipeline and this makes me instinctively pay more attention to what you are trying to accomplish. This form actively helps preventing mistakes. Arguably, the minimal style might even be better in this scenario since, I believe, those scenarios would be much more taxed.

In the end, I'd much rather pay a big tax every time I need to cross the FP and OO world while using a pipeline operator rather than pay a small tax all the time that also happens to obfuscate weird and potentially broken code. I feel this tax would encourage people to write proper FP code when using pipelines and thus reduce the risk of making mistakes, the overall cognitive load required to understand the code and make it simpler for devs with little to no experience with tools like Rx to avoid writing dangerous code.

mohaalak commented 2 years ago

first of all, I want to thank @js-choi @tabatkins, I think they are here to help. but I'm all for F# style cause the only reason that hack style is better than the F# is because of yield and await and in my experience people that want to use await like the imperative style not declarative. if they want to code declarative they can use the then function with tactic programming. cause everything that Hack style partial function can you can do it with just an arrow function. cause we don't want to type an arrow function we are adding a new complex operator.

and I think that developers that voted for pipe operator in surveys including me, were those who knew what was pipe operator was and worked with pipe functions in functional javascript libraries and rightly so they are disappointed that they cannot use this new operator with their libraries and inside their code.

SRachamim commented 2 years ago

It's still not too late to hold that Hack proposal. Let's push the partial application proposal first. Then it'll be easier for some to see how the F# or minimal proposal makes more sense.

ljharb commented 2 years ago

@SRachamim PFA has many challenges to advancement; this proposal should not wait on it, regardless of what style it goes with.

SRachamim commented 2 years ago

@SRachamim PFA has many challenges to advancement; this proposal should not wait on it, regardless of what style it goes with.

If it's Hack or wait, we must wait.

mohaalak commented 2 years ago

I genuinely think that this Hack style of pipeline operator has an adverse effect on the community and ecosystem of javascript. it's not just one but two operators one is |> and the other is ^ and all of this is for users that do not use functional programming style in their code those who like functional programming won't use this operator.

why we want to write this


getUser()
  |> Either.fold(
    err => `Something went wrong getting your user info. ${err.message}`,
    user => `Hello ${user.name}`
  )(^)
  |> console.log(^)

when we can write it this way

  pipe(
      getUser(),
      Either.fold(
         err => `Something went wrong getting your user info. ${err.message}`,
        user => `Hello ${user.name}`
     ),
     console.log
 )

but when I voted for one of the most wanted features in js, I was thinking something like this with PFA and F# pipeline

    getUser()
   |> Either.fold(`Something went wrong getting your user info. ${?.message}`,`Hello ${?.name}`)
   |> console.log
ken-okabe commented 2 years ago

@kawazoe I really appreciate your input based on your real world experience on pipeline operator . In general I totally agree with you and would like to add a supplemental explanation.

There are essential points that all programmers are interested in. After all, you and I care and share the 2 major factors here:

**1. BUGs / dangerous code or making mistakes in your words

  1. interoperability / "compatible" or tax in your words**

1. BUGs: Hack-style is dangerous

I think this factor is the most important in software development, and as I mentioned earlier, the history of software development is the war against complexity. Fundamentally complexity arises from false human assumptions over a concise mathematics structure.

Binary operator is a key to write down our code not in complicated but concise manner. 1 + 2 + 3 Pipeline operator is a binary operator of function application that is very basic algebra concept. a |> f1 |> f2 Basically, against binary operator itself, there is no space for human to tweak the math structure with extra syntax. Even if we mess this operator to a |> f1(^) |> f2(^) There are other infinite number (in principle) of binary operators perhaps the one of them may have monad structure and the binary operator may be assigned to >>= a >>= f1 >>= f1 so, will we also need to mess the monad operator to a >>= f1(^) >>= f2(^) ? Why not? Will we need to discuss whether to mess the monad operator or other binary operators each time like this? Of course not. Binary operator in algebra is essentially not a thing to mess up by false assumption by human. to be fair, even in Haskell, people often fails to define an operator properly but the binary operator of function application is not the one to design

Messing up an math operator that has a concise algebraic structure with human false assumptions, we can't avoid the consequence. Complexity.

Do we really want to chose 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.

Yes, in other word, context variables that we need to avoid as best as possible to avoid complexity. That's exactly what I've mentioned in https://github.com/tc39/proposal-pipeline-operator/issues/202#issuecomment-921433351 Case-3 Question about ^ when a "child pipeline" is present. #208

Hack style introduces new context variables which should be avoided even it's lexical bindings since which provides us an extra complexity. Sure, the lexical bindings are one of the essence to build FP language, but to force programmers to hold an extra variable that is fundamentally unnecessary in mathematical sense is against the simplicity and the reason of BUGs. This is not the problem like "a little bit (^) after the line.." or people will eventually be accustomed to, but the problem of robustness of software development.

Tweaking an existing algebraic structure is not only wrong in terms of mathematical consistency but also as the inevitable consequence, Hack-style becomes dangerous for all of us. A production of countless dangerous codes, broken code with BUGs. Lost of robustness.

To me, this is core to deciding between a minimal/F#-style pipeline operator and a hack-style one; much much more than any mathematical correctness argument you might come forward with. I want a language that helps me write good, maintainable code, while minimizing the risks of mistakes. If it needs to be algebraic to accomplish this, then so be it, but to me, it is not a priority.

The reason I favor to stick to math/Algebra or in another word: FP is that mathematical correctness directly corresponds to bug-free. In other words, if the software design or a code is not correct mathematically, the code has a BUG.

2. Interoperability - it's not F#-style VS Hack-style, tax free

It's not exactly F#-style VS Hack-style, but we need to have a perspective of *HackStyle = MinimalStyle PartialApplication* where `` is an operator of function composition.

Excluding all of the nice FP principles that you mentioned in your post here, I think that we should also focus on the direction we want to give to the language. Do we want to encourage proper FP style code, where currying and data-last is the norm? Or, do we only want to be "compatible" with it, while keeping FP on the side?

Although FP is a concise way to write a code, and great method to avoid bugs (because they stick to algebra, basically), this is a false framework we should discuss.

Just provide algebraic binary operator without messing up, and if we need interoperability, we should provide the partial application to the community.

I highly recommend to read @kiprasmel post: https://github.com/tc39/proposal-pipeline-operator/issues/202#issuecomment-917626903

and in my post:


Essentially, this is a tax-free discussion.

Having said that,

In the end, I'd much rather pay a big tax every time I need to cross the FP and OO world while using a pipeline operator rather than pay a small tax all the time that also happens to obfuscate weird and potentially broken code. I feel this tax would encourage people to write proper FP code when using pipelines and thus reduce the risk of making mistakes, the overall cognitive load required to understand the code and make it simpler for devs with little to no experience with tools like Rx to avoid writing dangerous code.

This is so true. Providing as not a product of function composition but a false product with human assumptions that is HackStyle failing to prove robustness in real-world experience of you, this is a bad tax with false human assumptions.

ken-okabe commented 2 years ago

It's still not too late to hold that Hack proposal. Let's push the partial application proposal first. Then it'll be easier for some to see how the F# or minimal proposal makes more sense.

I agree.

mAAdhaTTah commented 2 years ago

I'm so glad someone brought up Either, because it (and Maybe) are my two favorite things about functional programming, and both of those concepts are extremely useful in otherwise-not-functional code, especially with promises, and it would be much easier to introduce Either into an otherwise-not-functional codebase without having to explain point-free programming & higher-order functions. There's no reason why this is any less acceptable:

getUser()
|> Either.fold(^, err => `Something went wrong getting your user info. ${err.message}`, user => `Hello ${user.name}`)
|> console.log

I can explain to a mid-level JS dev "Either is an object that represents the results of a try / catch, and fold allows you to narrow that down to a single value depending on whether the block succeeded or failed" without needing to also explain "Either.fold is a function that returns another function that later gets called with the actual Either returned by getUser". Either.fold doesn't need to be written data-last; it is in JavaScript because of the ergonomics of the pipe function as implemented in JavaScript, but there isn't anything requiring functional programming to be data-last. Elixir is data-first, because Elixir-style |> inserts the pipe value into the first argument.

Functional JavaScript doesn't require point-free programming to do what it was wants to do; it uses point-free programming because that's what the current syntax makes most ergonomic. But if you had other tools available, you could implement fold like this:

Either.fold = (either, onLeft, onRight) => ...

And now I can use this both as part of a larger composition as well as a one-off for handling an API call with equal ergonomics. If we introduce a second pipe operator, you actually risk bifurcating the functional community itself, as some people will have already started to adopt the Hack pipe in this manner (e.g. me).

js-choi commented 2 years ago

Heya, hey, everyone. (And in particular, @stken2050, since you addressed me directly.) I appreciate everyone’s input here.

First, I want to reiterate my apologies from https://github.com/tc39/proposal-pipeline-operator/issues/202#issuecomment-919374937 and https://github.com/tc39/proposal-pipeline-operator/issues/206#issuecomment-918609518. I apologize that pursuing Hack pipes isn’t what many of you expected or wished for. I myself am enthusiastic about bothF# pipes and PFA syntax, and I plan to fight for both in TC39 in the future—but neither F# pipes nor PFA syntax have ever faced very good prospects (see #221). So, for now, we have been trying to focus on something that would help the entire ecosystem (particularly Web APIs).

As someone on the champion group who is enthusiastic about pipes (both Hack pipes and F# pipes) and PFA syntax, I—and everyone else on the group—appreciate everyone’s input and have been taking your comments seriously. (Though keep in mind that a lot of these comments echo stuff that we’ve been talking about since 2017, and there are a lot of constraints from TC39 representatives outside of the champion group.)

Although I appreciate these comments, a lot of them aren’t really on topic for this particular issue, which is specifically about the possibility of following up Hack pipes with a separate F#-pipes proposal (as well as @rbuckton’s PFA-syntax proposal.

Based on a lot of the comments I’ve seen over the past few days, I’ve opened several new issues, and I’d like to direct you all towards those issue and away from this issue. These include:

My sincere apologies if I marked your comment as off topic! I know it feels, to many people, as if your concerns are being blown over. But this thread is already super long, there were many comments were about many different topics, and any incoming readers would have a difficult time following the thread of this issue’s original topic. I invite you all to take your concerns to the other threads, and help out with improving the explainer in the other issues—if you feel like it! My apologies again, and thank you again. We’re all better for your input.

ken-okabe commented 2 years ago

@js-choi

I invite you all to take your concerns to the other threads, and help out with improving the explainer in the other issues—if you feel like it! My apologies again, and thank you again. We’re all better for your input.

Although I appreciate these comments, a lot of them aren’t really on topic for this particular issue, which is specifically about the possibility of following up Hack pipes with a separate F#-pipes proposal

All right, so which issue you have started can I read your response to my post, @kawazoe, @mohaalak or others as on topic?

I mean we need to continue the discussion, I will accept your moderation to set them as off-topic and it appears you are willing to invite all of us to some other threads but here the problem is I have no idea which issue should we migrate on. I appreciate your advise. Thanks.

js-choi commented 2 years ago

@stken2050: I feel that your comments in this thread comments have encompassed many topics at once. I would recommend that you split up your comments by sub-topic into multiple comments on multiple threads (but please remember to read the threads’ original posts first). Does that make sense? 😄

ken-okabe commented 2 years ago

@js-choi Yes, it does make sense, however I can't find a specific issue on the topic Hack-style proposal faces a problem of context variables leak. Should I start by myself?

  1. BUGs / dangerous code or making mistakes in your words
  2. interoperability / "compatible" or tax in your words

Because these are the topic we try to discuss from now on.

Should I start these two threads? Especially for 1

Frustrations of people looking forward to tacit syntax #215 Impact of Hack pipes on functional programming #217

The both are off topic because context leak is a technical issue and nothing to do with the mind-set of "frustration of people" nor "Impact" stuff of FP.

Having said that, Too much split of interrelated topic is an not ideal moderation because we lose the opportunities to discuss how each are interrelated and most importantly hard to follow the discussion.

ken-okabe commented 2 years ago

@stken2050: I feel that your comments in this thread comments have encompassed many topics at once. I would recommend that you split up your comments by sub-topic into multiple comments on multiple threads (but please remember to read the threads’ original posts first). Does that make sense? 😄

One more question before I confirm "does it make sense", please.

I have a plenty upvotes stuff on my own post which means my opinion is supported from community members, and looks like "off-topic" moderation illuminates the record.

I'm afraid to say that will give an false impression to anyone who see this discussion later. What do you think about the effect of your moderation?

Do you notice the effect of your moderation act and is it on purpose or not.

js-choi commented 2 years ago

Yes, it does make sense, however I can't find a specific issue on the topic Hack-style proposal faces a problem of context variables leak. Should I start by myself?

  1. BUGs / dangerous code or making mistakes in your words
  2. interoperability / "compatible" or tax in your words

@stken2050: The first one sounds like it might want to be its own thread (although preferably it’d reflect something we could change about the Hack-pipes explainer). The second one probably should be in #217, though remember that that issue is from the perspective of strengthening the Hack-pipes explainer.

Anyways, we in the pipe champion group are all only unpaid volunteers, so please forgive us if I am slow to respond.

As for whether I mean to hide upvoted comments—no, I’ve left plenty of upvoted comments on issues where they were on topic to their issues’ original posts (though remember that popularity doesn’t matter much to TC39; see #222). As long as a comment is on topic and meets the code of conduct, then I’m fine with any comment.

(Speaking of which, because this comment I’m writing right now is off topic from this issue, I will hide this comment and the others, haha. 😄)

js-choi commented 2 years ago

Like I mentioned in https://github.com/tc39/proposal-pipeline-operator/issues/221#issuecomment-928610179, I’m going to present a Stage-0 proposal for several new Function convenience methods to the Committee at its next plenary meeting. It’s called proposal-function-helpers.

That proposal’s new convenience methods would include standard Function.pipe and Function.flow functions.

To repeat https://github.com/tc39/proposal-pipeline-operator/issues/221#issuecomment-928610179, the pipe champion group has presented F# pipes for Stage 2 twice to TC39, being unsuccessful both times due to pushback from multiple other TC39 representatives about multiple concerns. (For more information, see #221 and HISTORY.md.)

Given this reality, the Committee is immensely more likely to pass a Function.pipe helper function than a similar syntactic operator.

Standardizing a helper function does not preclude standardizing an equivalent operator later. For example, the Committee standardized binary ** even when Math.pow existed.

In the far future, we might try to propose a F# pipe operator, but I would like to try proposing Function.pipe and flow to the Committee first, in an effort to bring their benefits to the wider JavaScript community as soon as possible. First steps.

Please feel free to read proposal-function-helpers’ explainer and leave feedback if you want.

Inori-Lover commented 2 years ago

it is not good, that one lang have two style to achive same purpose