tc39 / proposal-pipeline-operator

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

Hack is dead. Long live F#. #205

Closed kiprasmel closed 3 years ago

kiprasmel commented 3 years ago

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:

Ultimately... very little [differences]. <...> A three-character tax on RHSes in various situations.

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:

fetch("/api/v1/user")
 |> await ^.json()
 |> await ^.name
 |> console.log(^)

with F#:

fetch("/api/v1/user")
 |> await
 |> res => res.json()
 |> await
 |> user => user.name
 |> console.log

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 ALSO awaiting 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 https://github.com/tc39/proposal-pipeline-operator/issues/204


Another point by @OliverJAsh is that for Promises, they are already "pipe-able" via .then, and that it is completely possible to live without the async/await support in pipeline operators:

fetch("/api/v1/user")
 .then(res => res.json())
 .then(user => user.name)
 .then(console.log)

And going even further, you could say we don't need pipeline operators at all, because they're already here (the F# way):

["name"]
 .map((name) => name.toUpperCase())
 .map((name) => "hello, " + name)
 .map((name) => name + "!")
 .map((name) => console.log(name));
// hello, NAME!

Promise.resolve("name")
 .then((name) => name.toUpperCase())
 .then((name) => "hello, " + name)
 .then((name) => name + "!")
 .then(console.log);
// hello, NAME!

// F#:
"name"
 |> (name) => name.toUpperCase()
 |> (name) => "hello, " + name
 |> (name) => name + "!"
 |> console.log
// hello, NAME!

this is possible, because both [].map and Promise.then are, in FP terminology, Functors. See a quick explainer. In essence, they preserve function composition, e.g. g(f(x)) is the same as x |> f |> g, which is fundamentally what the pipeline operators are about!

Heck, you could create your own Functor:

function Box(x) {
    return {
        map: (f) => Box(f(x))
    }
}

Box("name")
 .map(name => name.toUpperCase())
 .map(name => "hello, " + name)
 .map(name => name + "!")
 .map(console.log)
// hello, NAME!

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:

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:

"name"
 |> ^.toUpperCase()
 |> "hello, " + ^
 |> ^ + "!"
 |> console.log(^)
// hello, NAME!

which maybe looks good first, but not when you consider further:

say we have utility functions:

const toUpper = (x) => x.toUpperCase();
const greet = (x) => "hello, " + x;
const sayLoudly = (x) => x + "!";

with F#, you can just do this:

"name"
 |> toUpper
 |> greet
 |> sayLoudly
 |> console.log
// hello, NAME!

meanwhile with Hack, you'd have to call the function each time:

"name"
 |> toUpper(^)
 |> greet(^)
 |> sayLoudly(^)
 |> console.log(^)
// hello, NAME!

is this a big deal? Yes.

Because you can replicate Hack's behavior with F#, but cannot replicate F#'s with Hack:

// F#, replicating Hack:

"name"
 |> (x) => toUpper(x)
 |> (x) => greet(x)
 |> (x) => sayLoudly(x)
 |> (x) => console.log(x)
// hello, NAME!

whereas you just cannot have function composition with Hack, without calling the functions with the token

// Hack. This will NOT work:

"name"
 |> toUpper
 |> greet
 |> sayLoudly
 |> console.log
// Error

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:

  1. you do not need a custom token (e.g. ^) to make it work. it's just a function with an argument!
  2. you can have both behaviors and choose the appropriate one yourself, instead of being forced into using using ^.
  3. you can specify the variable name yourself by creating an arrow function, which is more readable than any token the committee would end up going with anyway.
  4. it is easier to copy-paste/extract code into it's own function, whereas with Hack you cannot do this, since the current value token ^ is only usable in the scope of pipeline operators
  5. combined with the Partial Application proposal, it can be used just like Hack, and even better.

3. 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:

const sayLoudly = (x, howLoudly) => x + "!".repeat(howLoudly)

// F#
"name"
 |> toUpper
 |> greet
 |> (x) => sayLoudly(x, 5)
// hello, NAME!!!!!

// Hack
"name"
 |> toUpper(^)
 |> greet(^)
 |> sayLoudly(^, 5)
// hello, NAME!!!!!

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:

// same as before:
const sayLoudly = (x, howLoudly) => x + "!".repeat(howLoudly);

// F#
"name"
 |> toUpper
 |> greet
 |> sayLoudly(?, 5)
// hello, NAME!!!!!

// desugars to:
"name"
 |> toUpper
 |> greet
 |> (temp1) => sayLoudly(temp1, 5)
// hello, NAME!!!!!

hooray!

What happened here?

The sayLoudly function got curried / turned into a "unary" function, meaning that the argument x 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!

Promise.resolve("name")
 .then(toUpper)
 .then(greet)
 .then(sayLoudly(?, 5))

["name"]
 .map(toUpper)
 .map(greet)
 .map(sayLoudly(?, 5))

Box("name")
 .map(toUpper)
 .map(greet)
 .map(sayLoudly(?, 5))

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 )

To get what we want with Hack Pipeline without hamstringing the entire JavaScript functional programming community, the ideal solution (that would please everyone) is to land the F# pipeline, and then focus on the partial application proposal.

and further problems with the Hack proposal: https://github.com/ReactiveX/rxjs/issues/6582

Currently our [RxJS] "pipeable operators" rely on higher-order functions. Using these with the Hack Pipeline will be cumbersome, ugly and hard to read: <...>

( https://github.com/tc39/proposal-pipeline-operator/issues/203#issuecomment-917677239 )

A nagging sensation came to me as I looked at the README: pipe() examples are conspicuous by their absence:

const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x)

pipe(
  x => one(x),
  x => two(x),
  x => three(x),
)(value)

Why are there no example like this? It's a common and unremarkable pattern in FP-JS circles. Even without the exciting point-free potential, surely F# would be synchronous with this? Is there a use-case that Hack solves better that justifies its idiomatic deviation?

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 )

the only downside of F# style is you have to write 3 extra characters per pipe.

Exactly, and as discussed, this is not even an issue, and definitely not enough to justify Hack > F#.

This combined with the cluster that is the discussion about what should be used at the placeholder [token] further pushes me in favor of F# style.

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.

( https://github.com/tc39/proposal-pipeline-operator/issues/91#issuecomment-917645179 )

I have been a staunch advocate for F#-style pipes since the beginning,

👀

but it's been difficult to argue against the groundswell in support of Hack-style pipes due to some of the limitations of F#-style pipes.

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.

If we kept debating on F# vs. Hack, neither proposal would advance, so my position as Co-champion is "tentative agreement" with Hack-style.

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.

Both sides have advantages and disadvantages. F#-style feels to be a better fit for JS to me, especially given the existing ecosystem, and meshes better with data-last/unary producing libraries like Ramda and RxJS. F#+PFA [Partial Application] works well for data-first libraries like underscore/lodash.

I agree, and so does @benlesh & others.

However, F#-style is harder to use with yield, await,

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.

and methods where the topic is the receiver (i.e., ^.method()), which is an advantage for Hack-style.

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.

So we were stuck at impasse. I don't think a solution that mixes both is tenable, and I'd venture to guess that having multiple pipe operators (one for each) may not make it through committee.

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?

As what feels like the lone dissenter against Hack-style on the committee (or at least, the only vocal dissenter specifically in favor of F#-style), I'd rather not stand in the way of the feature advancing at all, despite my reservations, because I think it's a valuable addition regardless of approach.

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: https://github.com/microsoft/TypeScript/pull/38305 (240+ upvotes, 0 downvotes)

b) the Hack operator: https://github.com/microsoft/TypeScript/pull/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:

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 can not be separated into a separate proposal. Thus I feel like it's unfair to do comparisons where F# pipelines don't additionally have the partial application to help them. It results in a weird situation where the robustness of partial application actually hurts F# pipeline argument.

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:

Counting the comments on a gist is not a representative sample; there's an obvious bias towards comments that are arguing against the OP rather than agreeing with it.

, 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):


Thank you. Long live JavaScript.

thesoftwarephilosopher commented 3 years ago

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.

kiprasmel commented 3 years ago

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

thesoftwarephilosopher commented 3 years ago

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.

y-nk commented 3 years ago

I'd rather not have await keyword being alone in a line. the "it's more cognitive load because it does 2 things at once" point depends on the person coding. dealing with non-superstar engineer on a daily basis i can tell they will be more confused by writing what you propose. You could definitely argue back "but they'll just have to get used to it, it's not much" but the cognitive load would be for them to make sense of something that intuitively wouldn't, which defeats your "react vs angular" comparison.

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 .then() is kinda pointing out that your take would work for sync piping but not async ones, which is (imho) a set back in the recent evolutions of the language trying (hard) to reunite sync/async (praise async/await for the clarity it brought).

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:

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.

...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 :)

nmn commented 3 years ago

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 .map are fairly commonly used in JS, and there are many developers that will always use an inline function instead of “point-free style”

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.

kiprasmel commented 3 years ago

@y-nk hey, thanks for your input.

I'd rather not have await keyword being alone in a line. the "it's more cognitive load because it does 2 things at once" point depends on the person coding. dealing with non-superstar engineer on a daily basis i can tell they will be more confused by writing what you propose. You could definitely argue back "but they'll just have to get used to it, it's not much" but the cognitive load would be for them to make sense of something that intuitively wouldn't, which defeats your "react vs angular" comparison.

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 await and res => res.json().

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 fetchJson - fetch is a good candidate to be "wrapped".

See https://github.com/tc39/proposal-pipeline-operator/issues/91#issuecomment-917667672

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 .then() is kinda pointing out that your take would work for sync piping but not async ones, which is (imho) a set back in the recent evolutions of the language trying (hard) to reunite sync/async (praise async/await for the clarity it brought).

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 async/await start becoming even more apparent when you start dealing with cancellations, and this is actually a big part of what @benlesh & others are (trying to) solve with libraries like RxJS. This is a whole topic in an of itself, and with some help from e.g. @getify & others, we could dive in deeper, but perhaps on another day.

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

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.

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

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!".

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.

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.

Also, you lost your cool here:

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.

...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 :)

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.

lightmare commented 3 years ago

PFA solves F#'s shortcomings and (arguably) beats the only remaining value proposition of Hack.

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.

Even better - Partial Application can be used outside F#'s pipeline operators, aka anywhere in the language!

Then PFA should be advancing on its own. It's on hold, because most of its strength lies in F#'s weakness.

kiprasmel commented 3 years ago

hey @nmn, thank you!

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.

<...>

I agree.

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.

Exactly. I'd like to add an important detail: F# pipelines, for many people, resemble exactly that of what the bash / shell pipes are:

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 stdin coming in & stdout going out (though not necessarily), and this is what makes it so powerful. This is the F# way.

See also https://github.com/tc39/proposal-pipeline-operator/issues/206.

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.

Disagree here. As already mentioned, the Pipeline Operators are with us already - be it Functors, the pipe function, or even [].map or Promise.then -- you can already use the functionality of them.

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

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.

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.

kiprasmel commented 3 years ago

@lightmare thanks for your input.

PFA solves F#'s shortcomings and (arguably) beats the only remaining value proposition of Hack.

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)

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

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.

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

Even better - Partial Application can be used outside F#'s pipeline operators, aka anywhere in the language!

Then PFA should be advancing on its own. It's on hold, because most of its strength lies in F#'s weakness.

Some people have interesting opinions on this. @Pokute in particular, which I've already mentioned in part "4. The JS Community":

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 can not be separated into a separate proposal. Thus I feel like it's unfair to do comparisons where F# pipelines don't additionally have the partial application to help them. It results in a weird situation where the robustness of partial application actually hurts F# pipeline argument.

Thank you.

mAAdhaTTah commented 3 years ago

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:

While I originally came into the conversation advocating for the F# pipe, I’m now convinced the Hack pipe is the superior option, and that it would be beneficial to the functional community to embrace it. At core, Hack pipe is a better bridge between mainstream & functional JavaScript and makes it easier for mainstream JavaScript to adopt functional patterns.

Hack Pipe for Functional Programmers: How I learned to stop worrying and love the placeholder

thesoftwarephilosopher commented 3 years ago

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.

kiprasmel commented 3 years ago

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

As for syntax, it’s a relatively small change

I began my essay by opposing this. It is not a small change. See also #206

ghost commented 3 years ago

@sdegutis

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.

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.

shuckster commented 3 years ago

Does it add value on either side of this argument to hypothesise an alternate history of for-loops?

for (1 .. 10) print ^ - 1
kiprasmel commented 3 years ago

@mAAdhaTTah

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.

This sentence is an oxymoron. How can you make such a conclusion if in fact you did not read the whole post?

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:

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:

While I originally came into the conversation advocating for the F# pipe, I’m now convinced the Hack pipe is the superior option, and that it would be beneficial to the functional community to embrace it. At core, Hack pipe is a better bridge between mainstream & functional JavaScript and makes it easier for mainstream JavaScript to adopt functional patterns.

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.

Around that time, the previous champion of the proposal, Daniel Ehrenberg, stepped down and Tab Atkins-Bittner stepped in to take his place. While Daniel favored the F# pipe, Tab favored the Smart pipe previously and the evolved Hack pipe now and as champion, had been working to bring the TC39 committee (the one that specifies JavaScript) to consensus around the Hack pipe.

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?

mAAdhaTTah commented 3 years ago

This sentence is an oxymoron. How can you make such a conclusion if in fact you did not read the whole post?

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.

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 posted it here to discuss it here, but the post is intended to be accessible to everyone, not just people active here.

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.

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.

How do you make sure both sides are represented fairly and equally?

By having several members of the champion group, some of whom advocated for F#.


The cognitive load discussion between you and me, and anyone else for that matter, are just personal preferences

They are not. If the cognitive load of having await httpGet(url) was high, you'd see a lot more code like this:

const getP = httpGet(url);
const result = await getP

But you don't, because it's not.

thesoftwarephilosopher commented 3 years ago

@kiprasmel

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.

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 .map() like your .map(double) example above, 99% of the time writing out an anonymous function itself to avoid confusion and to make things explicit.

You may argue that this actually argues in favor of F# style since that style can write out the full function like |> x => double(x), just like .map(x => double(x) does. I'd agree that it's more consistent with these HOFs.

But I'd argue that it's more pragmatic to use Hack style:

Every time I write .map(double) and decide later I need to change it to use an explicit argument because I have some options, I have to rewrite it to .map(x => double(x, options)). This is already a DX problem I've been annoyed with regularly for the past decade straight, without interruption (for the few times I have written the name-only style first).

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 |> double(var) which is only slightly more verbose, but with the benefit of being explicit (already a quality I require when I judge any written software), and the added benefit of being easily extensible with DX, i.e. |> double(var, options) just requires adding the new param and nothing else.

kiprasmel commented 3 years ago

@mAAdhaTTah

You didn't answer this though:

"You did not mention Partial Application with F#. See my excerpt of what @Pokute has commented."

(@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 can not be separated into a separate proposal. Thus I feel like it's unfair to do comparisons where F# pipelines don't additionally have the partial application to help them. It results in a weird situation where the robustness of partial application actually hurts F# pipeline argument.


@mAAdhaTTah:

The cognitive load discussion between you and me, and anyone else for that matter, are just personal preferences

They are not. If the cognitive load of having await httpGet(url) was high, you'd see a lot more code like this:

const getP = httpGet(url);
const result = await getP

But you don't, because it's not.

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.

thesoftwarephilosopher commented 3 years ago

@kiprasmel

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.

kiprasmel commented 3 years ago

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

mAAdhaTTah commented 3 years ago

"You did not mention Partial Application with F#. See my excerpt of what @Pokute has commented."

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.

Not a fair comparison - your example is outside the realm of pipeline operators.

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 await on a separate line is not idiomatic. You can't ignore real patterns in real code because it suits your preference.

thesoftwarephilosopher commented 3 years ago

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

kiprasmel commented 3 years ago

@mAAdhaTTah

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.

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:

You can't ignore real patterns in real code because it suits your preference.

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

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 https://github.com/tc39/proposal-pipeline-operator/issues/200#issuecomment-918196823

mAAdhaTTah commented 3 years ago

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.

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.

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

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.

thesoftwarephilosopher commented 3 years ago

and 2) this is not how data analysis is done. By definition, a small sample will miss-represent a bigger one.

I've never heard of this version of statistics, where data samples are inherently untrustworthy.

The committee itself does not represent the bigger population, because, 1) as you yourself @sdegutis point out:

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.

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.

kiprasmel commented 3 years ago

@sdegutis oh so now you can pick yourself who is "overly intelectual" and who is not?

and then apply this as an argument against F#? lmao

thesoftwarephilosopher commented 3 years ago

I have enough life experience to feel comfortable saying that you are far more intelligent than me at certain mathematical programming concepts like Functors, and can more intuitively read and write partially applied functions than I.

ghost commented 3 years ago

@mAAdhaTTah

which looks to closer to FP-style code, which is a minority style in JavaScript.

Again to me Hack looks nothing like any existing JavaScript coding style while F# looks the same as fluent method chaining.

[-1, 0, 1, 2, 3]
  .filter(isPositive)
  .map(x => x + 1)

1234
  |> double
  |> x => x + 1

Meanwhile the Hack syntax looks to me just as if loops had been implemented with special cased syntax:

for (1 .. 10) print ^ - 1

Completely new and foreign.

mAAdhaTTah commented 3 years ago

@tam-carre See here for an example of existing idiomatic mainstream JavaScript and how Hack provides a significant improvement over F#.

kiprasmel commented 3 years ago

@mAAdhaTTah but this is, as @runarberg highlights, once again just your opinion.

And that's why #204 was created.

mAAdhaTTah commented 3 years ago

@kiprasmel It is not an opinion. It's an argument. And unless you have a counter-argument, dismissing anything you disagree with as "well, that's just, like, your opinion, man" isn't going to be convincing.

kiprasmel commented 3 years ago

@mAAdhaTTah from what you linked, the conclusion:

Looking at those two snippets, I believe either pipe is clear advantageous over temp variables, and that Hack is clearly advantageous over F# pipes, as it more cleanly integrates with await and doesn't require extra wrapping arrow functions at every step. I also believe mainstream JavaScript writes code like this very frequently and that therefore Hack is more broadly useful than F#.

For me, looking at those 2 snippets, or in general, I believe the opposite - that F# is advantageous over Hack. And this is what I call an opinion.

And what I am saying is that until you actually study this with a big enough sample size, that's all it's going to be - an opinion/preference.

And as a counter argument to the same argument of yours, see, once again, https://github.com/tc39/proposal-pipeline-operator/issues/204

mAAdhaTTah commented 3 years ago

For me, looking at those 2 snippets, or in general, I believe the opposite - that F# is advantageous over Hack. And this is what I call an opinion.

Do you have an argument for why F# is better in those snippets or are you just going "looks prettier imo"? Cuz I pointed out in the quoted line that F# requires extra arrow functions (which is more verbose than Hack) & moving await onto a separate line (with is less idiomatic than Hack) which are arguments for Hack's advantageousness.

ghost commented 3 years ago

@mAAdhaTTah

@tam-carre See here for an example of existing idiomatic mainstream JavaScript and how Hack provides a significant improvement over F#.

I consider the await syntax tax to be a distraction as, just like arrays in Javascript which can be mapped and flatmapped with infix operators Array#map and Array#flatMap, Promises already possess an infix operator for chaining in Promise#then, you can await the entire chain and a new infix pipeline operator is not needed.

await chromeClient.rpc("Page.captureScreenshot", { format: "png" })
  .then(x => fs.writeFile("screenshot.png", Buffer.from(x.data, "base64")))

const twelve = await Promise.resolve(6) // or await getAPromisedNumber()
  .then(double)  
thesoftwarephilosopher commented 3 years ago

This is the argument I've seen against it:

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 ALSO awaiting 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.

Again it's about cognitive load. The F# argument is that it's actually easier to show that await is a separate operation.

All of this really comes down to the fact that different people think differently. What's intuitive or obvious to one person is hard or confusing to another, and vice versa. That's human nature.

When it comes down to the discussion of this, we're just going to have different opinions, because we're different people with different ways of thinking. That's expected.

When it comes to making the decision, I think the decision makers already have enough arguments on both sides. So I guess we have to leave it to them to make a decision at this point. And they have, it's on Stage 2. So a person who disagrees with them, asking them to reconsider their decision, while only presenting the same reasons for their disagreement that the committee already considered and took into account, seems like an endless and fruitless debate.

(For my 2¢, it's far more clear and intuitive to me when await is on the same line. By far.)

mAAdhaTTah commented 3 years ago

I consider the await syntax tax to be a distraction

Given that the committee wants a pipe operator that support async / await, it isn't a distraction but a core requirement & point of comparison between the two syntaxes.

kiprasmel commented 3 years ago

@mAAdhaTTah

Do you have an argument for why F# is better in those snippets or are you just going "looks prettier imo"? Cuz I pointed out in the quoted line that F# requires extra arrow functions (which is more verbose than Hack) & moving await onto a separate line (with is less idiomatic than Hack) which are arguments for Hack's advantageousness.

Yes. Because:

  1. I prefer to name the argument myself
  2. I prefer explicit await on a new line

And in a general case, I also prefer being able to compose functions.

How is this not preferance? It literally is!

shuckster commented 3 years ago

The argument for being "more or less intelligent" to understand a feature has disturbed me somewhat. Is it not the case that, in explaining something new to someone, it is beneficial to draw on their prior knowledge? "You know how you do this over there? Well, it's like that over here." Is Hack not something of a little "syntax island" in this respect?

ghost commented 3 years ago

I consider the await syntax tax to be a distraction

Given that the committee wants a pipe operator that support async / await, it isn't a distraction but a core requirement & point of comparison between the two syntaxes.

Supporting async / await is not the same as putting weight on the async / await syntax tax.

thesoftwarephilosopher commented 3 years ago

@mAAdhaTTah

Do you have an argument for why F# is better in those snippets or are you just going "looks prettier imo"? Cuz I pointed out in the quoted line that F# requires extra arrow functions (which is more verbose than Hack) & moving await onto a separate line (with is less idiomatic than Hack) which are arguments for Hack's advantageousness.

Yes. Because:

  1. I prefer to name the argument myself
  2. I prefer explicit await on a new line

And in a general case, I also prefer being able to compose functions.

How is this not preferance? It literally is!

Even in this case, Hack actually wins:

value
  |> (x => doSomethingAsync(x))(%)
  |> await %
kiprasmel commented 3 years ago

@sdegutis I would kindly ask you to reconsider commenting. There are new arguments, considerations to be made, and discussions to be had, and you're just trying to gate-keep it. And you also showe in your preferences without adding anything else to the conversation, even cluttering it.

mAAdhaTTah commented 3 years ago

Explicit await on a separate line is not idiomatic. That is not an opinion, that's how code is written in the wild. You're welcome to prefer it on a separate line, but that is not how the rest of the community writes code, and the argument is that a pipe that fits in better with how the community writes code is preferable.


Supporting async / await is not the same as putting weight on the async / await syntax tax.

If you're going to support async / await, you can't then ignore the differences between how the two proposals support that syntax.

kiprasmel commented 3 years ago

@mAAdhaTTah you didn't answer the fact that I can name my own argument AND compose functions.

Explicit await on a separate line is not idiomatic.

For you. Literally the problem I'm highlighting. p r e f e r e n c e.

That is not an opinion, that's how code is written in the wild.

Written currently. But the fact is that we do not know how it will be written when we ship the pipeline operators.

For god's sake, how many times will I need to repeat the same thing: studies need to be done, you are just inlined with your preferences that are very likely to miss-represent the broader community.

mAAdhaTTah commented 3 years ago

For you. Literally the problem I'm highlighting. p r e f e r e n c e.

That's not what idiomatic means. It is not "for me". It's how the community predominantly writes code.

For god's sake, how many times will I need to repeat the same thing:

I'll keep repeating myself until you realize that we don't need studies to know how the community predominantly writes async / await code. It's incumbent on you to prove that introducing a new idiom, that doesn't align with how most people write code, is going to be preferable to most people. That's a steep hill to climb.

You can't argue "we don't know how it will be written" when you're attempting to study how people write code now. You're grasping at this as a counter-argument because you know await getThing() is more common then const promise = getThing(); await promise; and you hope there's some magical evidence out there that will support your claim that novel syntax is an improvement over a familiar one. The data we do have suggests familiarity is correlated to readability. Do you have evidence that suggests this novel syntax is preferable?

pigoz commented 3 years ago

Do you have an argument for why F# is better in those snippets or are you just going "looks prettier imo"?

I think it boils down to the fact that the people who are passionate about pipe+PFA are the ones using functional programming libraries in TypeScript. You will not see the "average" JS developer commenting on this thread.

Some of the the most popular libraries, like fp-ts, have been written to be forward compatible with the F#-style pipe. That's because it's generally what the pipe operator looks like in functional languages. If you show the Hack-style pipe to someone with background in any functional language, it's gonna be very counter intuitive for them because it has no resemblance to an actual pipe aside from the syntax.

On top of being counter intuitive, this operator is going to be almost useless for the same vocal minority of people who wanted the pipe operator in the first place.

The design error (IMHO), is the committee took a functional programming concept, disregarded how the functional community of the language uses it, and instead based their opinion on what they felt like will be the usage based on non functional programming patterns. BTW, this is a typical pitfall of design by committee.

ghost commented 3 years ago

@kiprasmel Take note of how emotional some of your latest posts have been; no matter how disingenuous you believe others to be acting this is not conducing to a healthy debate or helping your views

kiprasmel commented 3 years ago

@tam-carre frustration sucks. I will, thank you.

mAAdhaTTah commented 3 years ago

I think it boils down to the fact that the people who are passionate about pipe+PFA are the ones using functional programming libraries in TypeScript. You will not see the "average" JS developer commenting on this thread.

I appreciate you admitting that most developers arguing for F# are functional programmers rather than average developers. It ties in quite nicely with the argument I'm making generally; namely, that functional programming does not represent the wider community, and that the wider community (and JavaScript more generally) is expression based. If you're deeply embedded in functional JavaScript, then idioms that are natural to you will not be natural to the wider community.

On top of being counter intuitive, this operator is going to be almost useless for the same vocal minority of people who wanted the pipe operator in the first place.

I strongly disagree with this claim.

instead based their opinion on what they felt like will be the usage based on non functional programming patterns.

You think it's a mistake that the committee tried to advance a proposal that works for the predominant style of JavaScript?

shuckster commented 3 years ago

Is it rational to lean on argumentum ad populum?

pigoz commented 3 years ago

You think it's a mistake that the committee tried to advance a proposal that works for the predominant style of JavaScript?

I think it's a mistake if that feature is a fp feature. I think in that case looking at how that would be used in the functional community would be a good idea.

I understand that the committee hopes that the larger community will use the Hack-style pipe operator, but I'm not sure that's gonna happen. OTOH the JavaScript functional community would definitely use F#-style+PFA, since it's just sugar for the pipe function present in some libraries today. PFA would also be used on its own, which is quite nice.

But then again, I'm just one random developer on the internet. It seems quite pretentious for me to suggest how the committee should take their decisions. I just wanted to share my perspective as a not-too-smart user who has been doing fp in TypeScript for a couple of years. I'm now returning to the shadows 😅