-
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
Moving ahead with the minimal proposal #167
Comments
I think that it's more accurate (altho not entirely, ofc) that every proposal is for everybody, because everybody may see it in the future and thus be forced to learn it. Empathy includes understanding that syntax has a cost, and that even though you, personally may consider it worth it, that doesn't mean every current and future user of the language will agree. That's the burden on those adding new things to the language, and that's why "no change" is very often much better than "the wrong change". |
So I guess I should count you among "those who feel it is too much". Good. I prefer to have a concrete interlocutor! The price here is minimal: it's a pipe, the data flows through it. Compare that to the mountains of complexity that classes and decorators add, and that is deemed essential. People may have to see the pipeline but they won't have to use it (well, unless their boss tells them so, eh). With Web Components, the WhatWG imposes the complexity of classes to everyone and their daughter. Then this little operator adds too much complexity? It feels like a double standard. |
You certainly can't; I love the pipeline operator and dearly want it.
Just like everything else, people will have to use it if they maintain code that contains it, or if they're debugging code they depend on, that uses it. |
Good to know :-). Back to some abstract foil then... Point taken re. maintenance, and I'm ususally a proponent of small languages, but this, and especially the minimal proposal adds minimal complexity, either syntactical or conceptual. There are no traps like the dynamic scope of The exact semantics of The syntax could be confusing for a total beginner, but then, the first Google result for FWIW, debugging a pipeline is trivial: const log = (...args) => (console.log(...args), args[0])
const x = foo()
|> log
|> barzouille
|> log.bind(null, "post-barzouille: ")
|> bazribouille
|> log |
I NEVER use the
Almost the same point, I'm a current and future user of JS and I don't think adding the . IMO these arguments are red hearings. This is a light, easy to understand syntax addition that will bring joy to countless JS current and future users. . From reading the threads on this repo, we seem to have a few "committee" members that for one reason or another are against it, for no reason that's clear to see, and will hold this up indefinitely from what I can tell. I just wish we could somehow get the "committee" members on record and find out exactly what the concrete objections are. We NEED better dialog from them, but it seems this isn't going to happen with the current TC39 leadership/custodians if past is prolonged. It's been a disappointment (at least to FP developers like myself) to have such a important proposal sitting in TC39-limbo for years. . OO devs got their |
@aadamsx except that the objects created by the The thing we need to convince people of is that "code using the pipeline operator" is always easier to maintain than code not using it. Construct that argument, and we can get it! Merely complaining that things are going too slow isn't going to help anyone. |
Provided it is first written/ported to USE the The elephant in the room in this case is that the way the DOM is implemented, and the fact that inheritance was seen as the natural way to extend it. I see that as a lack of imagination, but that ship has once again long sailed. Also, "adult" languages in vogue at the time had classes, JS was seen as a toy, it needed classes to look like a grownup. Complaining that things are going too slow may not help, but framing this in terms of empathy does. https://twitter.com/awbjs/status/1085557705371078657 @JAForbes is brilliant. He's well aware of the the cost of using arrays here. Yet he's yearning for some kind of syntax for pipes, and ends up being mocked, schoolyard-style, by senior TC-39 members. [x]
.filter(Number.isFinite)
.map( x => x.toFixed(2))
.concat('N/A')
.shift() This is the kind of patterns FP folks resort to in native JS to compensate for the lack of piplines. If that doesn't look like pain I don't know what does. If you can't tell red and green apart, no matter of explaining will help. My point is that there are people on the TC who are colorblind, and in denial about it. Not everyone sees the world through the same lens. |
If you had to maintain any code composing functions, that code is ALWAYS easier to maintain when using
In that case you have to construct an argument that "code using the |
@pygy For what it's worth, pipelines wouldn't actually help that example. |
@noppa In that code,
I'm not sure why TC-39 members jumped on James like that, there may some context that's been lost to me. The pile on looks bad though, and is IMO representative of the lack of perspective and empathy. |
How did we go from "which way do we want to do this obviously useful feature that's more valuable to general programming in javascript than any other proposal up for discussion?" to "Do we want this at all?" It's hard to join this discussion and not be inflammatory because there are no people who have coded in a language with this feature that would come back and say no we don't need it here in javascript land. |
Our style in TC39 tends to be a bit more conservative, where we try to hold off until a feature is "done". I think it'd be worth it to move ahead with something in the spectrum from minimal to F#, if we can agree that they're self-contained enough. I could understand the argument that they're not complete enough to ship until we have partial application, though (which is more complicated and I'm not sure will be acceptable to the committee). |
With the trend of CPUs increasing their number of cores, making functional programming easy will set JavaScript up for success in concurrent programming since you can avoid a lot of state issues. The pipeline operator will be great for this. It's intuitive and has a proven track record in other languages. |
@szTheory This proposal is much more superficial than something that would enable that... |
If functional programming makes concurrency easier, and the pipeline goes a long way toward making functional programming easier, it seems like a win. Having an ergonomic way to compose functions is a real game changer. |
I think immutable datastructures are pretty separate from methods vs functions. They're both referred to as "functional programming" but the composition of data and code are just different things. |
Based on what exists today: # Rxjs
const operator = pipe(
(obs) => custom(param)(obs),
map(x => x+1),
filter(x => x % 2 === 0),
custom2(param),
)
const obs2 = obs1.pipe(operator)
# ramda
const operator = R.pipe(
R.multiply(2),
(x) => x + 1,
Math.abs,
)
operator(-4) //=> 7
# lodash-fp
const operator = _.flow([
_.multiply(2),
(x) => x + 1,
Math.abs,
]) Those pipelines work the same and have been tested and used by the community for quite some time. What are the limitation for having
eventualy but clearly not priority, something like
|
How would the last part work? Currently the proposal states that
Or
Or is that going too far, or impossible in JavaScript? 😊 |
@hlehmann Probably worth noting that at least in the case of rxjs, creating an operator from the import * as Rx from 'rxjs'
import * as ops from 'rxjs/operators'
const square = (x: number) => Math.pow(x, 2)
const add = (x: number) => (y: number) => x + y
const operators = (observable: Rx.Observable<number>) => observable
|> ops.map(square)
|> ops.map(add(1))
operators(Rx.of(4)).subscribe(console.log) // prints 17 |
Folks I agree that we should move forward with a minimal proposal. I feel like it's hard to jump in here without starting another long back and forth, but I strongly believe the best minimal proposal is Hack style only. Indulge me to quickly lay out why:
There's nothing you cannot do with the Hack-style proposal that you can do with the other proposals if you're just willing to type 3 more characters. Wouldn't you all agree that having Hack-style available in Chrome today would beat the "niceness" of not having to write the 3 characters? Let's get going with a minimal proposal. If it really kills people we can add the "smart" syntax later, but if we start with F#/smart we can never go back to plain Hack-style. |
@xixixao That is an excellent summary of many of the reasons I so strongly favor topic-style (hack-style). And in private discussion with the other pipeline champions while we prepare for the June meeting, I've also come to the same conclusion regarding "smart mix" - the three saved characters isn't worth the break in referential transparency or overall consistency. As such, I'm going to be arguing for plain Hack-style at the upcoming meeting. I think my only two remaining arguments are:
|
One of the big use-cases of the |> operator is RxJS operators. In F#-style, instead of the userland pipe function, you could write it like so: const queryResult$ = searchQueryInput$
|> debounceTime(300)
|> switchMap(query => http.get(url, {query}))
|> map(formatQueryResult); Is there a nice way to write something like this when Hack-style was used? |
@tabatkins can you please elaborate a bit on the point 7? Especially on what you meant by this phrase:
And why you think it's required to sacrifice ergonomics of manual call to write functions in that style? Ramda clearly shows us it's not necessary: R.map(x => x*x, [1, 2, 3])
(3) [1, 4, 9]
R.map(x => x*x)([1, 2, 3])
(3) [1, 4, 9] |
@Jopie64 I think the argument is that these are outliers in the JS landscape. Sure, you could write |
@matthewwithanm how you determine who are outliers? Is there some statistics on which style is preferred, say, by people often using |
I'm not arguing for one style or the other but it doesn't seem controversial to say that the curried style isn't the dominant one in JS today. (I'm not talking about the preference of a subgroup, just what's most common.) Of course, you might weight the importance of that differently than others do. |
In the F# version of this proposal, the Partial function application proposal serves to bridge this gap so I wouldn't be concerned about the community turning every function into curried unary functions. My main concern with the hack version of this proposal is that it can potentially block the partial function application proposal from moving ahead. Not only that proposal would be used in conjunction with the pipeline operator, but it is a general feature that can be used in other parts of the language compared to the hack topic which is only usable inside the pipeline operator. For example in Reactjs, it is normal to pass partially applied functions as a prop to other components. The partial function application proposal would make that easier. Would the hack topic be able to fill that role? I believe that Javascript evolution as a language should be through small proposals that work together, that generalize, and which are consistent, Not via a big proposal that tries to solve everything but fails to do so. |
In what way you do see the hack-style as obstructing partial application? |
tc39/proposal-partial-application#36 (comment)
|
Long-term, we'd have to build partial application on top of Smart Pipelines, should we go that way, into a more generalizable form. The proposal repo has extensions for this; specifically, pipeline functions. This doesn't make sense to me:
How is |
I respectfully disagree; it's even common in POSIX-compliant process buffering / terminals that accommodates anonymous pipelining:
In general class MyElement extends someOtherMixin(connect(store)(HTMLElement)) {
} Becomes class MyElement extends HTMLElement |> connect(store) |> someOtherMixin {
} The hack style seemingly adds cognitive noise here (I'm not well-versed on how Hack-style accommodates mixins currently looking out for OOP programmers) class MyElement extends HTMLElement |> connect(store)(#) |> someOtherMixin(#) {
} If the hack-style proposal indeed expects that mixins are handled with it in such a way, I find it unnecessarily verbose adding cognitive noise in a matter F#-style & the minimal proposal doesn't. |
class MyElement extends HTMLElement |> connect(store) |> someOtherMixin {
} I think you are misunderstanding me. In the examples above, I have no problem with using curried-in-place functions in pipeline examples, but I have problem with people using ONLY unary and curried-ahead functions in pipeline examples. |
@highmountaintea Gotcha; it's a matter of habit of being terse with hypotheticals; I could've written something like class MyElement extends HTMLElement |> connect(store) |> mergeFeatures(?, Feature1, Feature2) |> aliasElement('some-new-element-name', ?) {
} |
To be more specific, syntax is important in a pipeline discussion. So it's not good to start with a fake example like below, then use it to argue which proposal has better syntax: let r = x
|> a
|> b
|> c First of all, it's hard to visualize what real world example it's corresponding to. Second, syntax might look nice for unary functions, but terrible for HOFs. Again, not arguing which proposal is better, but simply saying don't use fake unary function examples @lozandier just to clarify, my original comment was in answer to somebody else, not specific to any of your comments |
Hack pipeline's power is its flawIMO, the hack pipeline, as proposed here is a fundamentally flawed design. It frees up people to write things in ways that make them even harder to read. If you have It will just encourage really weird/bad patterns: Where's the magic someThing |> myFunc(1, 2, 3, 4, 5, /* ... */ 99, #, 101, 102, 103, /* ... */ 392, #, #, 'lol') What am I doing with the magic someThing |> whatever(Math.random() * Math.min(# + 1, # / 200))(#)(#) The simple pipeline proposal solves this issue by implicitly passing the value in one and only one way. Meaning that every developer always knows right where to look to see where the value was "piped" to. No surprises. It's a solid design because it's less powerful. Rather no
|
Ha! In fact, while typing up my "undesirable" operator example, I made a typo where I'm not even sure how it would work (because of the added complexity of the (The next trick is finding it) (Magic |
@benlesh Thanks for sharing. Two quick comments:
|
@mAAdhaTTah I've replaced As for your point 2. You are mistaken... which is funny, because it precisely proves my point that the Hack-style pipeline is a bad design because of how it hurts the understandability and readability of the code. Here's a screenshot with the tokens circled in red: It's my strong opinion that the Hack-proposal shouldn't even be considered, and if it's the only proposal in contention, the entire proposal should be scrapped just to save room for another shot at getting it right in some other generation. I'm willing to be convinced otherwise, I'm just not sure how that's possible, given my contention with the Hack-style pipeline is specifically how wild you can get with its token and its design. |
@benlesh Sorry, my comment was referring to the missing topic token in |
@mAAdhaTTah is it though? If so, why? Technically, there is one within the context of the other: And therein lies another point in favor of the simple pipeline, it's harder to create syntax errors. You're guaranteed to have passed the value along implicitly. No "forgetting" where it goes. And if people really want that power, they can still do it: // Hack version
foo |> bar(#, 1, #, 3, #)
// Simple version
foo |> $ => bar($, 1, $, 3, $) And what about other scenarios? // Hack version
foo |> bar(1, 1 |> # + #, 2, 3, 4, 5, #)
// Simple version
foo |> (fooResult) => bar(1, 1 |> n => n + n, 2, 3, 4, 5, fooResult) I suppose many parens are required there? So let's try it: // Hack version
foo |> bar(1, (1 |> # + #), 2, 3, 4, 5, #)
// Simple version
foo |> (fooResult) => bar(1, (1 |> n => n + n), 2, 3, 4, 5, fooResult) Even with the parens, I find the #, # ,# of the Hack example to be harder to read. It lacks the ability to just give it a variable name. I fail to see the advantage of Hack pipeline while the disadvantages are very clear to me. |
@benlesh I know that it's not a complete solution since we want to support editors/users who don't have all JS tooling, but good IDE will help immensely. You can see the type and code for the token: Finally, it also shows the correct context for nested tokens too: |
Because the RHS of the pipeline operator has to use its topic token or it's a syntax error. On the line beginning with To be clear, I'm team F#, just tryna be accurate. |
I suspect I should point out, I was using such an example to outline increased visual noise in the hack-style, where having fewer number of symbols / elements helps greatly with clarity: it is easier to see how boilerplatey x |> a(#) |> b(#) |> c(#) compared to myObservable |> filter(x => x % 2 === 0)(#) |> debounceTime(200)(#) Note that the number of important elements in both cases are kind of the same, but the point is harder to see in the later example. Of course, that abstraction / simplification, while it magnifies some aspects of the proposal, it can miss another. It does not mean that those points are necessarily overlooked (in the same comment for example I did bring up the very same "curry-in-time" issue too), its just that it sometimes helps to have examples that focus on one aspect of the syntax rather than others. |
Guess I'm getting old, but this wasn't easy to figure out honestly. So in this case for example: f(a) === a |> f(#)
=== f |> #(a)
=== a |> (# |> f(#))
=== f |> (a |> #) All the statements are correct and equivalent, except the last one, which is due to EDIT: also got the third statement wrong initially, but I think thats because I am really used to think about pipe as in |
I wouldn't necessarily chalk it up to being old lol. I've been staring at pipeline examples for the better part of 3 years now.
Yup, that's correct. |
To prove minimal proposal extensibility, I created a babel plugin that allows minimal And it can be useful on it's own: let addOne = _ + 1;
// equals
let addOne = x => x + 1; Try it in the babel playground. More details in #186. |
Closing this issue, as the proposal has advanced to stage 2 with Hack-style syntax. |
Memes aside, I truly do think this is a mistake. 😢 Very interested to read the TC39 Meeting Notes on this one. Hopefully they shed some light on this seemingly out-of-the-blue decision. |
I knew he would push hack style even if fsharp was the better. Hack style is a mistake for JS, I won’t be using it. Where is the announcement and explanation of reasoning behind this decision by @tabatkins? Why the lack of communication? This is not the way to lead an proposal. This is a big, years long, disappointment. |
@aadamsx I understand the disappointment and frustration, but let's not make things personal. It's not like Tab is on a personal crusade to crush anyones hopes and dreams. I don't know what next steps are at this point, but whatever they are, let's try to keep things constructive. 🙂 |
There were unaddressed concerns about the hack pipeline in this issue. The proposal being in stage 2 with the hack pipeline is irrelevant and this should have have been closed. |
It’s an attempt to shut down the conversation @benlesh — he’s closed many unresolved issues today. |
Such disappointment. I guess I keep using custom |
How do you call that in your countries guys, when some group of people at the top, not subject to any community control, makes non-transparent decisions for their own reasoning, affecting millions of people? Hmm. To make it absolutely clear, the next logical step would be to lock this conversation with some excuse like "too heated" or whatever. |
I'm going to ask that people communicate their concerns and criticisms of this proposal without making personal attacks. This was not the decision of a single person, the Hack-style pipelines proposal was presented at the last TC39 meeting and the committee reached consensus to move to Stage 2. |
I'm going to go ahead and lock this thread; the comments since I closed this issue have been solely personal ire directed at me or otherwise off-topic. (And "inb4 lock because too heated" is never a productive comment.) This proposal has been active since 2018, and in those three years we've iterated over practically every syntax possibility for this feature. (Curiously, the Elixir-style pipeline received only cursory treatment, tho, despite it being a good match for JS's standard arglist organization.) In that time we hadn't reached consensus on a syntax proposal, and importantly hadn't convinced the TC39 committee that the proposal itself was sufficiently worthwhile to advance past stage 1. Over the last year I, and several others, have advocated for the Hack-style syntax, and produced what were apparently relatively compelling arguments for our position (see my essay, and this proposal's current README); as a result, the majority of TC39 members directly interested in this proposal either began to support this syntax, or at least considered it an acceptable outcome. This also ended up getting over the objections of the rest of the committee, such that at this last meeting the committee agreed to advance it to Stage 2. Decisions can be changed. Stage 2 still allows for significant changes if necessary, tho ideally any major concerns have been dealt with prior to this. I invite further debate, but caution y'all that we've seen and considered a lot of arguments and angles already; please read the closed issues in this repo. Repeats of previous arguments aren't going to be productive, but any new information is welcome. For example, one of the major bits of reasoning cited in my essay is that the pipe operator is useful for linearizing any nested expression, not just nested function calls. I looked at the set of use-cases that could benefit from linearization by pipes, estimated the usage of the various categories, weighed the cost/confusion imposed on each category by each alternative, and found Hack to be the best. Evidence that my rough estimates of category size will be way off in actual usage (like, a convincing argument that this operator really will only (or at least a strong mostly) be used by HOF libraries rather than other code) could sway things. Ultimately, tho, for this proposal to progress a decision has to be made, and any decision will disappoint some number of people. My goal is to ensure that in the long term my actions disappoint the least number of people by the least amount, and I think I'm on track for that. If you disagree, and feel that you have new arguments that haven't already been gone over in previous issues (or perhaps an old argument that was unfairly passed over) please open a fresh issue. |
In Rust land, it is a common practice to implement features in stages, with the initial steps designed to be forwards compatible with possible future evolutions that have yet to be settled. It works for them with great success.
I think that the minimal proposal should be shipped, after being amended to make sure it is forward compatible with both the smart or the F# proposal.
In #163 (comment), @ljharb said:
Folks: this proposal is not for you. Not every proposal has to be useful to every programmer.
Classes come to mind as the seminal example. Taxonomies are easy on the mind, but a very poor way to model the world at large. Classes are thus a nice way to model some phenomena, most of which are human-made, but they often are not the correct tool to model a problem even if you try hard to shoehorn it. We could do that kind of modeling with JS using prototypes, but taxonomies were deemed sufficiently important to need their own sugar.
In my code, I treat the
class
keyword as a syntax error... but I also understand that OO modeling is central to how some people think and write programs and I'm fine with it!The same applies here. This is badly wanted by a subset of the users, who would probably rather have this than infix arithmetic operators. Please have some empathy.
The text was updated successfully, but these errors were encountered: