-
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
Hack is dead. Long live F#. #205
Comments
For point 2: actually hack can do more than f# can because you can do expressions like create arrays or use math operators etc. and a downside of f# is you have to create unary functions for all these things. |
@sdegutis I have already considered such use cases in the above writing. Hack is not doing anything more, it's different syntax (which is very important), but it does nothing more advantageous than F# - quite the opposite. It's clear that I prefer a unary/curried function over whatever Hack has to offer, and I think I've explained it well enough. The fact that in such cases you need to use an (arrow) function is a pro, not a con. Read again. |
I’ve read it and reread your ideas a few times. I still just don’t agree. You make some points but ignore other valid counterpoints. None of what you said so far is new to me and yet all the arguments are countered by proponents of Hack pipes to my satisfaction. And you make a lot of objective assertions about how many people prefer F# over Hack pipes and assert a general community consensus which I think are jumping to conclusions without sufficient data. For example the typescript issue votes indicate nothing except one issue has had more attention than another. The way you talk and reason here, I have only ever seen people use when they simply don’t understand the opposing viewpoint correctly. |
I'd rather not have I'm taking on this particular argument because one of the things frontend people (like me) look forward to is to get rid of the promise chaining for something lighter in syntax and yet as simple to understand, and seeing that you're advocating we're good with On couple of unrelated remarks: Thanks a lot for the writing, i learnt a lot (and i really mean it). Links provided were all instructives and imho your argumentation is strong enough that you don't need to bring in famous names to make a point (it just feels you're pushing with a crowd of names where you don't have to, really). Also, on your TS repo comparison you may also include (for fairness) that they're implemented 1 year appart and that F# was first (and so it's most likely that it would have the most upvotes). Where you lost me in your demonstration is at comparing React/Angular only because you're clearly biased towards one. The comparison isn't fair since the 1st sentence of each paragraph is "Why React is great" and "The mistake that Angular made" which is not even slightly balance, and later concluded with "F# is React, Hack is Angular" to "nail the coffin". You should rather recognise that React and Angular are 2 frontend libraries not designed for the same purpose, don't cover the same scope, and their reach in functionality brings in design choices where the philosophy was different in implementation. Also, you lost your cool here:
...which i can understand but tbh that's not with that kind of writing that you'll convince the people against it (and deciding, nonetheless) to change their mind. But that's just my opinion, and i'm a nobody - feel free to ignore :) PS: I, as well, don't like Angular but it's still not relevant comparison and you're not fair in it anyway :) |
As someone mentioned in the original essay I want to chime in with some thoughts. I still think F# is better than the Hack-style proposal. My reasoning for this is not based on power, but because I prefer adding just an operator over new syntax. F#-style proposal is adding just one new operator. JavaScript already has operators and arrow functions. Adding a single operator seems like it’s not bloating the language all that much. Perhaps people don’t fully understand higher-order-functions, but methods like Similarly, if the F#-style proposal progressed, I expect most developers to always use inline arrow functions to keep things explicit. But, we already understand how function calls work and learning one new operator is simpler than learning a completely new bespoke syntax just for piping. BUT, I don’t agree with stalling the HACK-style proposal if that is the proposal with the momentum. I’m honestly tired of waiting for this proposal and every time there seems to be a little bit of momentum an argument breaks out and progress gets stalled. I’m tired and will take the “worse” proposal if that’s what I can get. It can also be argued that adding new bespoke syntax is how JS progresses at all. The class syntax is very bespoke and it chose to do things to look like other languages rather than try to stick to existing syntax where possible. |
@y-nk hey, thanks for your input.
Here I have to disagree. The cognitive load discussion between you and me, and anyone else for that matter, are just personal preferences, but the Rust community has actually done the usability research and reached conclusions that I've already mentioned above. Also, the fetch example I chose, is quite rough. Usually you'd have a utility, something like this: const fetchJson = (url, opts) => fetch(url, opts).then(res => res.json());
fetchJson("/api/v1/user")
|> await
|> user => user.name
|> console.log which gets rid of the 2 additional lines of Mind you that the fact that we had to do 2 consecutive awaits suggests that we should have a function on top of them, and only do it once, just like I did in the example above with See #91 (comment)
Async/await is great for day-to-day, but you should not underestimate the power of Promises and the foundational principles of FP that sits behind them, especially if you don't understand them / aren't aware. The flaws of
Cheers and thank you. I'm not putting anyone's names for the purpose of doing just that; I either mention the further arguments that I found from them that are directly related, or I bring attention that the JS community needs them to stand up, when the mentioned members are/were in favor of F# over Hack.
I am very aware and from the very start I had a paragraph explaining this already: "Of course, A [F# proposal] has been out longer, but the general consensus is pretty clear!".
That's fair. I was wondering about this example too. See, I've been following Pipeline Operators from the very start, but the fact that Hack, and not F#, moved into Stage 2, I only found out 2 days ago, and thus I had to 1) catch up with everything, and 2) write this down, which means very limited time and not as well-thought-out examples & stuff. As I wrote in part 6: I probably shouldn't be the person writting this essay in the first place - there exist way more experienced people who could've done a better job than me. But I suppose someone is better than no-one.
I still stand by this. I agree the writing could be better - I've just talked about this in the previous paragraph - limited time, and limited experience on my part. But my point still stands. Thank you. |
This is way oversimplifying it. PFA solves one issue with F#, but does not cover everything Hack can do. // Hack pipe
x |> foo(^.bar); // PFA does not allow foo(?.bar)
x |> foo(^ + 1); // PFA does not allow foo(? + 1) You could argue that in proper FP fashion the above would be written as: // F# pipe
x |> member('bar') |> foo;
x |> inc |> foo; However that's not PFA beating the value proposition of Hack — which is that one can use arbitrary expressions within a pipeline. You're just discounting the value others might see there.
Then PFA should be advancing on its own. It's on hold, because most of its strength lies in F#'s weakness. |
hey @nmn, thank you!
I agree.
Exactly. I'd like to add an important detail: F# pipelines, for many people, resemble exactly that of what the find . -type f | grep -E '.jsx$' | xargs sed -ibp 's/var/let/g' Pipes in the shell enable you to compose simple utility functions together, and this is one of the foundational ways of doing shell scripting. It makes sense - it's simple yet very effective. The whole suite of utilities are designed in a way to deal with See also #206.
Disagree here. As already mentioned, the Pipeline Operators are with us already - be it Functors, the This means that there should be no rush whatsoever to implement a solution, because 1) it's already possible, 2) the current solution (Hack) is incomplete (as argued above, and also in #200, #206 etc.).
Maybe, but I could just as easily argue that Classes were a step in the wrong direction. It is fine, however, because it does not necessarily impact "me" - I can just use regular functions and objects, but if the Hack pipeline operator gets shipped instead of F#, you are now directly taking away a big part of functionality that others would like to use as well. See again #206. |
@lightmare thanks for your input.
Correct - you cannot do this with PFA. But, you can just as easily do this: // F# pipe
x |> y => foo(y.bar);
x |> y => foo(y + 1); which, as I argue, can be easily seen as a benefit and yet another argument of F# over Hack. See the last 2 paragraphs of "2. The Downfalls of Hack".
Once again - that's F# itself beating Hack, and PFA providing convenience in some cases. I agree that others see value in Hack, but even without PFA, F#, as already argued above, feels superior in many ways. See also #206
Some people have interesting opinions on this. @Pokute in particular, which I've already mentioned in part "4. The JS Community":
Thank you. |
I'll start off by conceding that I mostly skimmed this post, but my initial reaction is most of this has in fact been covered before. And that's a good thing! We've investigated & poured over a lot of the problem space over the past 4 years, and the decision to advance Hack has been considered deeply. Ironically, while you were writing this, I was writing something myself, arguing that Hack pipe is better for functional programming. This seems as good a place to share it as any:
– Hack Pipe for Functional Programmers: How I learned to stop worrying and love the placeholder |
As for cognitive load, I’d argue hack pipes are actually easier because you just mentally place the value from the left into the placeholder on the right. It’s a direct translation and nothing else. Whereas with F# pipes you have to mentally deal with partial functions and currying which at least to me is not intuitive and difficult to mentally follow. I know for some people who get functors and monads it’s probably easier but that’s not me. As for syntax, it’s a relatively small change. Smaller syntax isn’t automatically better and in fact that’s one reason why I was glad to move away from Clojure to TypeScript after doing it for 5 years professionally. There may be a kind of purity to smaller syntax but we are humans and natural languages, as well as the success of JS, show that we are perfectly fine and perhaps even do better with more syntax. I’m fully convinced that Hack pipes are mentally lighter and more pragmatic for JS devs. |
@sdegutis you did not say anything new here, just like in your previous reply. The cognitive load argument - everyone is just pulling it out from their ass. I've already mentioned in in the writing, AND answered multiple replies in this thread: The cognitive load discussion between you and me, and anyone else for that matter, are just personal preferences, but the Rust community has actually done the usability research and reached conclusions that I've already mentioned above.
I began my essay by opposing this. It is not a small change. See also #206 |
@sdegutis
I understand that this coding pattern is idiomatic: function double(n) {
return n * 2
}
[7, 59, 8]
.map(double) I would say that passing a function reference to a HOF is a similar case as having the pipe operator expect a unary function. 37
|> double Then you have the freedom of dealing with functions of >1 arity by using an anonymous function expression, or by writing curried functions. [6, 7]
.map(n => add(n, 2))
.map(curriedAdd(2))
7
|> n => add(n, 2)
|> curriedAdd(2) That is why Hack-style seems unlike idiomatic JavaScript to me. The desire to bypass anonymous function expressions might better be suited for the Partial Expression or Partial Application proposals, which would have the benefit of not being special cased for the pipeline operator. F#'s function expression "syntax tax" is a language-wide phenomenon and should not be addressed with syntax special-cased for a single feature. |
Does it add value on either side of this argument to hypothesise an alternate history of for-loops?
|
This sentence is an oxymoron. How can you make such a conclusion if in fact you did not read the whole post?
Why did you write this in your own blog and not in an issue in this repository? You take away the ability to discuss it in the place where it's mean to be - here, in the repo issues. I highlight this problem in Q 13 in the last part of the essay. From your article itself:
Hard disagree. You did not mention Partial Application with F#. See my excerpt of what @Pokute has commented. And I'm not going to repeat myself again - read what I wrote.
Okay, so you just proved my point and answered question 11. This is terrible. The fact that one member, in favor of F#, stepping down, and another, in favor of Hack, stepping up, completely changes the course of where the proposal is going, is ridiculous. Of course it was not the only factor, but it is clear it had a lot to do. Repeating Q 11: How do you make sure both sides are represented fairly and equally? |
Unless you can't summarize your own arguments very well, none of the arguments you've provided here are novel. I've been working on this proposal for 4 years, and have made these arguments before. If you think you have an argument that hasn't been made yet, please direct me to it.
I posted it here to discuss it here, but the post is intended to be accessible to everyone, not just people active here.
Yes, it went from a proposal that stalled and had not advanced to a proposal that achieved consensus for Stage 2. It only looks ridiculous because it wasn't your preferred outcome.
By having several members of the champion group, some of whom advocated for F#.
They are not. If the cognitive load of having const getP = httpGet(url);
const result = await getP But you don't, because it's not. |
Considering it's a survey from the Rust community, there is clear selection bias towards the kind of people who use Rust, which is already a language that comes with a heavy cognitive load and relatively high learning curve. It's not a personal preference, but a very real cognitive limitation some people have, including myself. JS is a very common-denominator language, and the Hack proposal fits perfectly with that, unlike the F# proposal. @tam-carre You're proving the cognitive load point with perfect examples actually. Having a partially applied function nested inside a unary function is just headache inducing for me. I already avoid using function names by themselves in You may argue that this actually argues in favor of F# style since that style can write out the full function like But I'd argue that it's more pragmatic to use Hack style: Every time I write Whereas the Hack style is a great middle ground solution, having the best of both worlds: (a) you can avoid writing the ident and the arrow, but (b) you get the ident for free, which leads to |
You didn't answer this though: "You did not mention Partial Application with F#. See my excerpt of what @Pokute has commented."
Not a fair comparison - your example is outside the realm of pipeline operators. Even if it were - it is still personal preference. This is the problem. It has not been studied thoroughly enough with actual users. @sdegutis Your whole argument of having to change the function if you want to add another argument is nullified if you consider the Partial Application proposal. Sorry, at this point you are just cluttering the conversation. |
I strongly feel that you and some other proponents of F# here are not actually hearing one of the main concerns I have: the cognitive load of your proposal is a bit too heavy for me and many JS users. But I don't think I can make this point any better or clearer than I have so far. And in my experience, very often a person with very high intelligence, especially in the software and math worlds, will assume they actually have average intelligence, and not be able to recognize the intellectual limitations others have, and thus will have a very difficult time believing that the things they can understand are difficult or actually impossible for many others to understand. For this reason I think we have been going in circles on the topic of cognitive load, and the other topics for this same reason, which I see no way out of. So I will gladly hop out of this conversation and wish you all the best in figuring this out. |
@sdegutis dude - I completely agree with you. The thing that I am advocating for is that the proposal needs to be carefully studied with various users to find out WHAT scenario is optimal and most intuitive, and THEN choosing the fitting proposal. I am not saying one is better than the other. All I am saying is that we need way more sample data, rather than the clashing personal preferences of the few people that are discussing this. |
Partial Application was introduced at the same time as Pipe and that we have been arguing about F# paired with PFA this whole time. This is not a novel argument.
Exactly. It's idiomatic code pre-pipeline, and this will continue to be idiomatic code post-pipeline. Hack doesn't require a different syntactic or stylistic approach. If readability is closely associated with familiarity, choosing a syntax that looks similar to current idiomatic code is more familiar and thus more readable. Putting the |
I assumed that's what the past 4 years of various intermittent informal discussions by committee members and their colleagues have been, both on here and in various chat rooms and maybe even in person. And all of this constitutes a relatively secure and robust sample set of data about user behavior, preferences, and thoughts, which I think is more than sufficient for the purpose. And all the points made and summarized in previous discussions, I only see being rehashed in this thread (and the last one, and the gist before that), and nothing new brought up, and no new counter arguments provided. In other words, I think the decision makers have thoroughly done exactly what you're asking for, and are ready to move on. And so am I. |
I am not saying this is a novel argument. I am saying that when you consider it, it makes F# more powerful, and it must be considered when comparing F# vs Hack. @mAAdhaTTah & @sdegutis:
Read my previous reply to @sdegutis - it is not about my or anyone else's preference, because it will unavoidably be unrepresentative of the bigger population. I am advocating for more discussions, and more experiments with people actually trying to use the different pipeline operators, and finding what's most intuitive, and THEN choosing the superior proposal: The committee itself does not represent the bigger population, because, 1) as you yourself @sdegutis point out:
and 2) this is not how data analysis is done. By definition, a small sample is very likely to miss-represent a bigger one. edit: see also #200 (comment) |
We have considered it. F# + PFA is not as powerful as Hack and doesn't address the fact that Hack composes expressions, not functions, and outside of FP style, expressions are the primary method of writing JavaScript programs.
What is done in real code is what's most intuitive. We don't have to conduct studies to know this. The entire argument for Hack pipe hinges on the fact that this looks closer to real, mainstream code than F# does, which looks to closer to FP-style code, which is a minority style in JavaScript. |
I've never heard of this version of statistics, where data samples are inherently untrustworthy.
You're confusing two types of intelligence. The people I have met who work at bigger companies on bigger projects like the committee members are, typically have an ordinary level of this type of intelligence, which can deal with Functors and Monads and etc. They are very similar to me in this respect from my experience. This makes them excellent representatives of the overall JavaScript community, since they're not necessarily intellectually gifted, just on the slightly higher end of knowing and understanding typical software patterns. The fact that they're committee members doesn't automatically imply a higher level of any specific kind of intelligence, only the fact that they seem to have good judgment on software in general, and a generally agreed upon good eye to what would make software easier and better to write for common devs. |
This comment has been minimized.
This comment has been minimized.
@getify I think you are being a bit unfair. Engine developers need to deal with additional headaches in either case. In the case of F# + PFA they probably need to optimize function objects created inside pipelines and find a good debugging mechanism to dive into the call stack. But in the case of Hack engine developers must figure out a good way to represent the changing nature of the topic marker in the debugger. In either case they have some work to do. And whichever gets added to the language, I’m sure they will do a good job. |
@runarberg Engine developers themselves expressed concerns about the F# style pipe. He is not being unfair. |
If pipes cant take a function they are broadly worthless to me. The whole point of pipes is that you can't dot through functions. I don't want to have a long list of lambdas, I want to have a list of functions. So if you implement it in hack style and it doesn't support the behavior demonstrated below, I will still need an operator which can do that or else my code will be parentheses soup. x |
I'm accused of being unfair? I think this is example of unfair hyperbole. Consider: divideByTwo(
addThree(x)
)
// compared to:
x
|> addThree(^)
|> divideByTwo(^) The second one there is hardly "parentheses soup" compared to the first one. And the more steps you add to the composition, the more the I understand disliking the I understand that you probably are claiming you never pipe/compose with anything but already unary functions, but... a lot of people are also contemplating the downside of F# (without PFA) of needing to add (at least) I would assert that those two competing concerns/downsides ( But... as I said in my previous message, I don't think PFA's solution there is "free". I dislike its cost, for different reasons. My point is, there's no "clearly this is superior" and "clearly this is inferior" here... we all know both have some upsides and some downsides. The hyperbole of "this one is useless for me" or "this one is absolutely perfect for me" doesn't really advance our discussion any. |
Yes, that's why PFA is wanted. The fact that it isn't guaranteed as a concurrent standardization, is still fine, because F# pipes would be useful on their own. But, this cannot be used as an argument of Hack being better than F#, because Hack effectively is a combination of F# + PFA (arguably a worse one), and the fact that it's easy to separate out the PFA proposal from the F# proposal (unlike Hack, where it's impossible, because Hack without This was pointed out very early by @Pokute: It's unfortunate that partial application proposal is not mentioned here, since it complements F# pipelines. F# pipelines practically has the burden that partial application as part of F# pipeline proposal is so easily to separate as a distinct proposal that no-one can really object to separating it. Hack-style #-placeholders [ |
I DID NOT make that claim. I said that What I am claiming is that even though F#+PFA removes the |
Is the issue here really the name of the operator? Is Even if Hack is the only game in town for now, can we keep the nomenclature away from the Bash/FP prior-art? |
To be clear, I won't be using it if it has this ^ syntax. My coworkers are going to be asking me why we're using xor. At least with the simpler pipes it's actually using something that already exists in javascript. It's really a needless complication to something that should be dead simple. By contrast if we break out ^ to a separate feature, I can use pipes, and you can use ^. Passing functions as values is very vanilla javascript, by contrast ^ is magical. |
Not in my opinion. I think there are substantive issues, not just naming. Hack style does not require the special handling of |
I did not say you made that claim, I am highlighting a point related to what you said.
Why is it not a strong argument in favor of F# + PFA? The reasons you list out are very valid (1. F# + PFA removes That (expressions vs unary functions) I'll cover soon. Can you clarify:
what are these costs exactly, and how is Hack's |
Please see this comment. Also, special casing of |
It remains to be seen (and will probably never be realized) whether this is an actual issue for potential users. No usability studies were conducted before it was claimed this was a downside, and since #204 was closed, no studies were ever intended. It was simply decided by a committee that a special case handling of |
I don't see anywhere in this thread (which I have read all of), including the comment you linked to, where the concerns I have, which relate to conceptual cohesion and thus teachability, about special casing things like |
That's one angle to consider the concern, but I have a different angle, which I just stated -- the conceptual cohesion and teachability. I've repeatedly referred to this concern in this thread. Thus far, I don't see it having been addressed in a substantive (non-dismissive) way. |
Teachability is also measurable and the teachability of the special case of |
I think that's a very interesting point, but it's the same if you want to await in a bunch of nested functions, so what you're proposing is a solution to an existing problem with or without pipes, similar to the placeholder. |
Okay. You like And that's fair. But does that really outweight the benefits that F# + PFA have over Hack? Did I miss anything else? (Apart from
) Coming back to the engine stuff, @mAAdhaTTah replies that:
3 questions to @mAAdhaTTah, or anyone else for that matter:
As @runarberg replies:
|
I'm not arguing for it to NOT be studied. I think it should be. But the lack of it being studied thus far, or at least seriously explored, is not evidence in favor of either style. Hack doesn't suffer from any potential weak outcomes in this area, whereas F#+PFA might or might not. That fact alone is only mild/weak signal in favor of Hack. But my experience teaching these sorts of things gives me pause, where I wouldn't prefer any proposal to advance just assuming it's not a problem. That's only anedoctal evidence, and I suspect nobody here gives much weight to my skepticism. But that is why I landed on the side of Hack, and it's a part of why I'm still there even with all this discussion. |
I'm not going to be reading this entire thread (it's, at the time of me posting this, 85 screen-heights worth of text on my monitor, posted in just 22 hours since the thread was started), but I see several comments on this thread that are insulting or dismissive, and either violate TC39's CoC or skirt close to it. I won't tolerate "discussion" of this type, and will be deleting comments as necessary, and possibly banning users from this repository, if things cannot remain civil. This is the only warning. |
Sorry if I got heated. I thought by positioning it as two separate features we could both get what we wanted. One side could have pipes as they are familiar and the other side gets placeholders as they are used to. I am not opposed to hack style placeholders, I just didn't want them to be the exclusive way things are done. Again I apologize for getting a bit polemic, I felt like it was being proposed as an either|or even when I was explicitly trying to include other approaches, but I should have been more patient. Thank you for helping @tabatkins . |
For what it’s worth, @syg of Google V8 expressed some thoughts in the 2021-06 pipe incubator meeting, before the 2021-08 plenary meeting:
I believe that @codehag of Mozilla SpiderMonkey has expressed similar concerns offline, mentioning that people generally overestimate how much optimization engines can do with regards to things like arrow functions in F# pipe bodies (from what I recall, she’s actually slightly against any pipe operator). I don’t have a source for that second quote, though, and my memory is fallible, so please take that one with a grain of salt. |
It's in the incubator notes.
|
That, plus the special handling of |
Reading @getify’s comments, I’d like to summarize the state of things as follows: (please correct me if I get anything wrong)
My thought here is that while you may say that F# is abusing the arrow function syntax, I think it’s a strength that it is using the existing syntax. After a lot of thinking and explanations by Tab Atkins, I no longer think there is any “power” that on proposal has that the other one lacks. They are both fully-featured (or can be). So my thoughts boil down to the fact that I think adding bespoke new syntax for single new feature is counter-productive and I’ve yet to see any arguments from supporters of Hack-style on why adding new syntax for just this one feature is a good idea. I could see a good argument for why new syntax is good: Example: Generator functions were added to JavaScript and they introduced new bespoke syntax. This was a very good design because:
Could anyone give me examples of how the new syntax introduced for Hack-style will fit into the larger syntax of the language? f# is just an operator, which I consider a strength. |
This comment has been minimized.
This comment has been minimized.
It buys us the ability for expressions in the pipe to use things that a function boundary (like an inline arrow function wrapped around expression body) would obscure, such as using // Hack:
fn()
|> await Promise.race([ ^, cancelToken ])
.. IIUC, that's not directly possible (or, at least, as ergonomic) with the F# style pipes, even with PFA, because you have to wrap that // F#
fn()
|> v => Promise.race([ v, cancelToken ])
|> await
.. In fact, for a simpler example, where it would just be // Hack:
fn()
|> await ^
...
// F#
fn()
|> await
... If you compare those two versions, the conceptual "magic trick" that has to happen to treat So yeah, IMO there are definitely pathologic cases where F# (even with PFA) gets more worse than the worst case in Hack, which is that you have to add To answer your question directly, I think avoiding those cases and magic is worth the invention of the |
@tabatkins I hope you don't let a few unpleasant comments from a few particular people dissuade you from reviewing the other well thought out and articulated discussion in this thread. I understand that it may be a lot to review at once and keep up with, but many of us have, and we are all hoping that you will extend the same courtesy to review our concerns as well. We appreciate it. 🥰 |
This thread had, at the time I posted that comment, received a post on average every six minutes for twenty-two straight hours. For anyone who didn't livestream the thread, it's far too long to read. I've been working in standards for over a dozen years now, and participated in my share of centi-threads, and let me tell you - they're never worth reading (even if I'm the one responsible for them!). I did, fwiw, read the OP beforehand - it doesn't contain any information that wasn't already expressed in one of the multitude of threads talking about F#-style pipelines over the last three years. I also, after posting my last comment, did actually skim the thread in reverse to check for posts that deserved to be proactively hidden; this continued to be true. We've had these discussions over and over; the points have been made, the arguments have been considered, and the current proposal is where we've ended up after all of that. Expressed in this very thread are comments from several people who feel they've been talked over and spoken for, and that expressing any opinion that is not pro-F#-style will provoke a hostile pile-on. They're not wrong, and that's unacceptable. I'm going to go ahead and close this thread; it appears to have run its course, and in any case is filled with unacceptable levels of hostility. As I've said in previous threads here, new information is welcome, or arguments one feels were unfairly passed over. But this repo has three years of arguments on this exact subject already; please review those before asking the rest of us to read twenty-three thousand words in one new thread. I will continue to close threads that do not meet the minimum guidelines from our CoC to be respectful, friendly, and patient. If necessary, further moderation action may be taken; I pray it doesn't become necessary. If you still feel like you'd like to talk to my manager, here's the list of chairs, here's the CoC, and you can most easily get in touch with people via TC39's Matrix rooms |
I would like to thoroughly discuss the pros & cons of the F# vs Hack & other proposals.
I will have questions at the end that I would like answered by the committee & others involved.
I want to argue that the method for choosing the proposal (F# vs Hack) is wrong.
In @tabatkins discussion on the differences of the proposals, their conclusion on the differences is this:
I disagree. There are big differences between the proposals, and they cannot be measured mearily by the amount of characters. Let's dive in.
1. Function composition
I'll start from the
async
/await
&yield
argument against F#:With Hack, you can do this:
with F#:
with F#, you must have the
await
keyword on a new line/pipe, meanwhile with Hack you can have it at the same line.I argue that F# is better than Hack because in the Hack example, you are doing 2 things at once: taking the
res.json
(which is further hidden by the^
or whatever token), AND ALSOawait
ing the promise. It's a bigger cognitive load, as opposed to the F#'s way where only 1 thing is done per 1 line/pipe.As mentioned by @runarberg, the superiority of Hack over F# regarding
async
/await
is very questionable - the claims are not backed up in the current proposal's README and further documents, and as a given example, the Rust's community has, after a very thorough RFC, went with the same exact solution that F#, not Hack, would allow.Discussed further in #204
Another point by @OliverJAsh is that for
Promise
s, they are already "pipe-able" via.then
, and that it is completely possible to live without the async/await support in pipeline operators:And going even further, you could say we don't need pipeline operators at all, because they're already here (the F# way):
this is possible, because both
[].map
andPromise.then
are, in FP terminology, Functors. See a quick explainer. In essence, they preserve function composition, e.g.g(f(x))
is the same asx |> f |> g
, which is fundamentally what the pipeline operators are about!Heck, you could create your own Functor:
and get the same effect as F# proposal, though without special cases for async/await & yield.
So then, why do we even need the pipeline operator?
In F#'s case:
Box
)Box
- reduces complexity ofg(f(x))
intox |> f |> g
async
/await
&yield
would be a great addition|>>
operator (proposal 3), without (!) the need for an extra operator (we'll get to that).Hack would provide the same benefits as F#, but worse - let's dive in further.
2. The Downfalls of Hack
What the Hack proposal offers is the ability to reference the current variable with a special token (e.g.
^
), instead of creating an arrow function:which maybe looks good first, but not when you consider further:
say we have utility functions:
with F#, you can just do this:
meanwhile with Hack, you'd have to call the function each time:
is this a big deal? Yes.
Because you can replicate Hack's behavior with F#, but cannot replicate F#'s with Hack:
whereas you just cannot have function composition with Hack, without calling the functions with the token
meaning, F# = freedom of choice, and Hack = no freedom of choice (in 2 cases - 1st, composing curried/unary functions concisely, and 2nd - choosing the name of the argument, as opposed to a predefined symbol(s)).
With F#, instead of Hack, an added benefit is that:
^
) to make it work. it's just a function with an argument!^
.^
is only usable in the scope of pipeline operators3. The Partial Application proposal
What if your function takes in multiple arguments, but with the F# pipeline operator you can implicitly provide just one argument (otherwise you need to create an arrow function)?
This is what Hack oughts to solve:
great. maybe. the third pipe is supposedly better in Hack than F#, but F# is still better in the first 2 pipes.
But what if? What if F# could get even better?
Here comes Partial (Function) Application, PFA for short:
hooray!
What happened here?
The
sayLoudly
function got curried / turned into a "unary" function, meaning that the argumentx
is now the only argument that the function takes in, and all others are inlined.As you might notice, this looks exactly the same as Hack, just a different token (
?
instead of^
)But there's more!
Since this is Partial Application, it is not specific to pipeline operators, as opposed to the Hack's pipeline operator with a token that's only available within the pipeline.
Meaning, you can use partial application anywhere in the language!
This is the way.
This is exactly what I am advocating for.
And I'm not alone:
4. The JS Community
( https://gist.github.com/tabatkins/1261b108b9e6cdab5ad5df4b8021bcb5#gistcomment-3885521 )
and further problems with the Hack proposal: ReactiveX/rxjs#6582
( #203 (comment) )
This is very similar to my
Box
Functor example above, and goes hand-in-hand with what @benlesh says, highlighting yet again why F# > Hack.( https://gist.github.com/tabatkins/1261b108b9e6cdab5ad5df4b8021bcb5#gistcomment-3757152 )
Exactly, and as discussed, this is not even an issue, and definitely not enough to justify Hack > F#.
Exactly what got me into this too - the discussion of what special token to use for Hack (I've used
^
here) is a good indicator that the Hack proposal is lacking. People were considering tokens such as^
,%
,$_
,$$
, and other ridiculous ones, because the initial ones (#
/$
) are incompatible with js -- all of this mess to avoid a simple arrow function with F#. The most upvoted comment there also reaches the same conclusion.( #91 (comment) )
👀
As I've argued here, I think it's the opposite - it's harder to argue against F#'s pipes rather than Hack's, especially if combined with Partial Application.
Here I strongly disagree. A half-baked solution (Hack, especially without the
|>>
operator) that will be impossible to revert once it's shipped is FAR worse than no solution at all. And then we have an a far better (AFAIK?) solution: F# + Partial Application, which seems unbelievable that it is not the one that's being considered and advancing into further stages.I agree, and so does @benlesh & others.
Disagree. See 1. Function composition.
TL;DR: If considered thoroughly -
async
/await
is actually an advantage of F#, not a disadvantage, with a proven track record by the Rust community.yes, but this is literally the only advantage of Hack, as discussed above.
x => x.method()
is just as viable, and works just as well in other cases where Hack is supposedly advantageous. As I already argued above, I think the F#'s way is actually better even in this case - see the last two paragraphs of 2. The Downfalls of Hack, or even better the whole essay. Overall, there's no way the "inconvenience" of doing this the F# way with an arrow function instead of with Hack outweights all the benefits of F# + Partial Application.This is regarding the 3rd proposal - Split mix - Hack's
|>
+ F#'s|>>
. I agree - it is not very tenable, because we would introduce yet another operator. Furthermore, why all this complexity, multiple operators etc., when we can have the best of both worlds with F# + PFA?Once again - disagree, because a half-baked solution is worse than no solution at all. Digging further, however much time it requires, is definitely worth it, considering how much impact it will have.
This is where I, and a big part of the JS community, needs people from the TC39 committee like @rbuckton, @RyanCavanaugh, @littledan, potentially @DanielRosenwasser & others in favor of the F# approach to stand up (others are just as welcome).
Just look at the interest, discussion, amount of upvotes in the TypeScript's repo for implementing:
a) the F# operator: microsoft/TypeScript#38305 (240+ upvotes, 0 downvotes)
b) the Hack operator: microsoft/TypeScript#43617 (12 upvotes, 8 downvotes)
Clearly, A is preferred over B. Of course, A has been out longer, but the general consensus is pretty clear!
As the implementor of the above 2 PRs, @Pokute, mentions in the @tabatkins discussion:
Initially, the pipeline operator, back from 2018 or whenever, has always been shown first as the F# version, and has only been changed in the README recently to accomodate Hack moving into Stage 2. This is not what the community actually wants! When we talk in general about the Pipeline Operator and how much people want it (e.g. @littledan's slides), most if not all who are excited about it, are still refering to that same version they saw - the F# one.
There are
many @peey,
many @anatoliyarkhipov,
many @nmn,
many @nmn,
many @nmn,
many @samhh,
many @Avaq,
many @OliverJAsh,
many @OliverJAsh,
many @jleider,
many @OliverJAsh,
many @benlesh,
many @tam-carre,
many @benlesh,
many @benlesh,
many @lightmare,
many @kiprasmel,
many @stephaneraynaud,
many @samhh,
many @shuckster,
many @aadamsx, and definitely even more
arguments in favor of F# over Hack, or concerns with Hack, some of which have already mentioned in my writings above.
The few that are in favor of Hack over F#, are far and few in between. Surely, if more people liked Hack over F#, there would be more arguments from their side as well, or at least attempts to answer the F# over Hack arguments thoroughly, which I definitely feel has not been done well enough.
And while I somewhat agree with @tabatkins that:
, you simply cannot deny the fact that there indeed is a lot of people in favor of F#, and I am pretty certain it's greater than those of Hack.
Heck, even if the count of people doesn't matter, the arguments for and benefits of F# over Hack are definitely more prevalent and agreed upon than those of Hack over F#, at least in the JS community itself, but apparently not in the TC39 Committee.
5. A metaphor
For me, F# vs Hack is like React vs Angular.
I'm not looking to start a framework/library war, so hear me out:
Why React is great is because it's as close to the javascript language as it can be. Unlike Angular, it didn't try implementing literally every aspect of the language in it's own way with e.g. template rendering and all the mess that comes with it -- for-loops/conditionals as html attributes, 2-way data binding, etc. -- whom will never be better than javascript itself; classes + decorators + dependency injection; specific libraries that only work with Angular; etc. In React, you take existing javascript libraries and just use them, or create your own, who will also be usable elsewhere outside of React (with an exception for Hooks, but that's just fine). It's just javascript, with sprinkles of jsx on top, and unlike Angular, you're not subdued by the authors of the framework/library on what you can or how you can do something, but rather by the language itself, which is a far better bet.
The mistake that Angular made, imo, is trying to re-implement the javascript language itself into an opinionated framework. React just bet on javascript and took off. Try creating a component in Angular - they have a whole CLI tool ready for you just to generate some boilerplate for every new component. React? Lol, create a function, and here is your component. It's just a function. It's simple. It composes well. It's what we love about javascript.
How is this related to F# vs Hack?
Well, there's a lot of parallels!
Hack wants to create an extra token (e.g.
^
, or%
, or$_
, or whatever bogus combination of symbol(s) get picked by a small group of people, effectively enforcing their choice for everyone, instead of letting the individual choose themselves), and that token also won't be usable outside the context of pipeline operators.F#, on the other hand, works better with curried functions, has an advantage with
await
being simpler (and has a track record of Rust to back this up, as discussed above in 1. Function composition), and is also just as viable as whatever Hack tries to solve, at the cost of creating an arrow function, which is actually better, because freedom of choice for the name of the argument! Furthermore - it does not introduce an additional token, which makes it easier for the ecosystem to adapt, AND it stays more in-line with the language itself, because, just like in React, it's all about the function. See also 2. The Downfalls of Hack above.Add Partial Application to F#, and you've got yourself exactly what React got with Hooks. PFA solves F#'s shortcomings and (arguably) beats the only remaining value proposition of Hack. Even better - Partial Application can be used outside F#'s pipeline operators, aka anywhere in the language!
F# is React, Hack is Angular.
Many people, including myself, have coded extensively in both - I can almost guarantee our opinions about the two are identical - React over Angular every. single. time.
6. Is Hack really dead?
I suppose there are limitations with the F# proposal as well? Sadly, I am not too aware of them (other than the things I've mentioned above which are actually not limitations but advantages), but more experienced people could give pointers? (Especially from different FP language backgrounds).
I probably shouldn't be the person writting this essay in the first place - there exist way more experienced people who could've done a better job than me. But I suppose someone is better than no-one.
There are some things I didn't cover, but this is already very long and should serve as a good starter / a continuation from the bikeshedding of Hack's token and the discussion of tradeoffs between F# vs Hack & others.
Thus, I invite everyone to discuss this Pipeline Operator thing further, and in no way rush into Stage 3.
In particular, I'd like at least the following concerns to be addressed, especially by members of the TC39 committee, especially those involved with pipeline operators, whichever side they're favoring (but everyone else is just as welcome too):
1. What you just read here - does it make sense, what are the shortcomings you see, what else could be improved and addressed?
2. Did you read anything new or was literally everything already considered?
3. Do you have any plans to carefully study the usability of the proposals, just like @runarberg highlighted that the Rust community has done? If no - why, if yes - how, and will it have any impact before we reach Stage 3? Or have you already done this and where can we find the results?
4. Do you have any concerns that the current proposal is incomplete / half-baked?
5. Do you have any concerns that if we move forward with the Hack proposal, without including the F#'s
|>>
operator together at the same time, it could lead to us missing some detail which would prevent|>>
(or whatever other token choice for this functionality) from being possible, while the Hack's pipeline operator would already have shipped, and it would be too late to go back?6. In general and in this case, do you think a half-baked solution is better than no solution at all -- even if there are ways to implement the solution already (Functors etc.), meaning the half-baked solution isn't necessary -- even after reading this and comments/arguments from other threads linked here -- even knowing the impact that it will have -- even knowing that we've made such mistakes in the past (nodejs callbacks vs promises)?
7. Do you know any people in the TC39 Committee who have a strong background in functional programming languages, e.g. Haskell, F#, Elixir etc? Did they participate in the discussions regarding Pipeline Operators?
8. Were there any experts in the FP field invited to participate/help make decisions with regards to the pipeline operator? Did they help? How did the committee handle their feedback, how much weight did it have?
9. Would it be possible in the future to be more transparent of how exactly the decision was made?
10. What members of the TC39 committee made the decision, what members were not present (e.g. @littledan because vacation? etc.)?
11. Do you believe that certain people not being present could sway the decision in one direction or another, why, and how do you make sure both sides are represented fairly and equally?
12. Is it still possible to switch from Hack's to F#'s proposal? Would you help facilitate the switch if you believed that F# is better over Hack, even if the switch could further delay the landing of pipeline operators, because in the long term it would be worth it?
13. Would there be a way to collect all information in one place (or at least clearly link to additional resources from the main source of thruth which probably should be the proposal repo README / RFC issue), as opposed to the current situation with 1) the proposal repo itself, 2) @tabatkins gist (!), 3) discourse, 4) @littledan's slides, etc.
14. What could I or any of the javascript community members do to further help with this? For this proposal, and in future ones?
Thank you. Long live JavaScript.
The text was updated successfully, but these errors were encountered: