-
Notifications
You must be signed in to change notification settings - Fork 107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Separate (complementary) F# pipeline proposal? #202
Comments
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 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 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 + 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 + 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 + [1] https://github.com/tc39/proposal-pipeline-operator#tacit-unary-function-application |
Yes, I'm essentially talking about reviving Split Mix, now that Hack pipes has achieved consensus as a direction from the committee. The line:
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. |
@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 |
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? |
@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 |
@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. |
@rbuckton I'd like to respond to your comment in #91 (comment) since it directly relates to the topic of this issue:
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. |
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 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. |
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. ^_^ |
I am personally pro-eventually-adding-a-split-mix tacit-unary-function-call
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: tc39/proposal-hack-pipes#18 (comment)
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. |
@tabatkins @js-choi thank you - that's very helpful context.
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?
There are many counterexamples to this in successful and popular languages, including F# which has Also, I don't think this is true: EDIT: THIS EXAMPLE IS WRONG
const {memoize} = require('lodash')
const track = value =>
value
|> ^.toLowerCase()
|> memoize(trackEvent)(^) Behaves differently from this: const {memoize} = require('lodash')
const track = value =>
value
|> ^.toLowerCase()
|>> memoize(trackEvent) In the first Another case, where it is just tax, but it's more significant than 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, 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
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. |
@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. 😀 |
@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. |
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 #207 (comment):
|
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
EDIT: THIS IS WRONG
@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 value
|>> fetchFromSlowAPI
|>> logSlowResponses(Date.now() + 3000) |
@mmkal I still don't get it. For the second example to log "slow response!", you need |
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),
); |
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 So, the updated example is value
|>> fetchFromSlowAPI
|>> logSlowResponses(Date.now() + 3000) Since |
@mmkal Ok thanks, now I understand. It won't work that way, though:
Every binary operator in JS evaluates its operands left-to-right. The F# pipeline operator has to be left-associative. 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) |
I through this is probably better handled by value
|> ^.toLowerCase()
|> JSON.parse(^)
|> do {
let { x, y, z } = ^
const api = new SideEffectyAPI(x)
api.initialize(y)
api.foo(z)
} BTW. The 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) |
See also tc39/proposal-hack-pipes#4 (which led to me rewording some stuff in the explainer a few months ago). |
Yup, a few people have addressed it now, but I'll stress again that The confusion is likely stemming from the fact that both are different from This is the same as |
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 |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
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 #202 (comment) and #206 (comment). 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. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Like I mentioned in #221 (comment), I’m going to present a Stage-0 proposal for several new That proposal’s new convenience methods would include standard To repeat #221 (comment), 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 Standardizing a helper function does not preclude standardizing an equivalent operator later. For example, the Committee standardized binary In the far future, we might try to propose a F# pipe operator, but I would like to try proposing Please feel free to read proposal-function-helpers’ explainer and leave feedback if you want. |
it is not good, that one lang have two style to achive same purpose |
In general, that is not true. Synonyms. Syntax sugar. Built-in for-each loops versus functions, etc. |
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)?The text was updated successfully, but these errors were encountered: