-
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
Minimal-Hack proposal #192
Comments
This ... is a really good idea. It may be easier to figure out which side to choose (F# or hack-style) after we start to see how people use the pipeline operator in the real world. There is one option this eliminates, and that's the potential to bring the F# pipeline in without partial application (e.g. maybe we don't think partial application, in general, is a useful enough feature to warrant new syntax). But, if we decide that we definitely want partial application with F#-style pipes, then I would love to see something like this go through. (I don't know what people's thoughts currently are about partial application, and how much people are wanting that feature in general). |
I'm not sure it does eliminate that option even. Under this proposal we could say every expression needs a This could be forbidden for now: const alwaysBlue = () => 'blue'
val
|> alwaysBlue // syntax error You'd have to do something like this: const alwaysBlue = () => 'blue'
val
|> (_ => alwaysBlue())(?) Not the prettiest, but ignoring the input value is a weird use case so fine that it stands out IMO. Edit: maybe I misunderstood your point - if you mean that JavaScript without any partial application style placeholder syntax would be off the table. I'm fine with that too, personally. |
Yes, I was talking about the fact that having F#-style without any partial application syntax would be off the table. I'm personally unsure if removing "x =>" from a function definition is important enough to warrant a new syntax. Maybe it is, maybe it isn't. |
As someone who already makes use of manual currying and partial application as they exist in the language today, the risk of needing to spam |
That sounds like a good argument for why the follow-on proposal should go in the F# direction, not the Hack direction. For what it's worth, I personally 100% agree. I don't think it's an argument against getting this in for now, though. The benefits of this proposal are:
Here's the scenario I'm hoping for:
But as soon as we reach stage 3, real applications and libraries can and will start using pipelines for more than just POCs. This will be immensely valuable, because it will allow scanning source code, and we'd have new data for the debate. If we detect that there are more "spammy question marks" that could be plain curried functions in F# world like In short: this is not attempting to resolve the debate. It's attempting to give us an ok-ish pipeline operator while we debate, and to give us more empirical data on which we should choose. As mentioned, neither Hack nor F# proponents should expect to be satisfied by this - they both still have to wait to get the full benefits of their proposal. |
The data will be biased towards |
Hate me for saying this, but Javascript is not a curried language, and I don't think we should intend for the pipeline proposal to encourage people to start curring (I know some people try to use Javascript in a curried fashion anyways, sounds like you're one of them @samhh). If we design a pipeline operator that starts encouraging everyone to provide both a curried and uncurried version of different functions, then we've designed the operator wrong - we've designed an operator that goes against how people currently use the language, and people have to start updating their code in order to make the operator more user friendly. That's the incorrect path to take. Instead, the operator should be designed to fit in with how people are already defining their functions. (This is not an argument for or against F# or hack-style. It's more of an argument about our intentions with this operator) |
I agree with @samhh. I think, when minimal-hack would reach stage 2+, users of libs that would benefit from F# would not or barely use minimal-hack and just stick with their current approach. (I know I would.) E.g. Currently: numbers$.pipe(
filter(isEven),
map(add(3))) Minimal hack: numbers$
|> filter(isEven)(?)
|> map(add(3))(?) I mean, who'd choose the second style over the first? I think this would yield a distorted view when the usage data is analyzed later. |
Most JS users would, I think, which is the far more important demographic to consider than "people who even know what currying is", let alone "people who use currying in JS". |
@samhh @Jopie64 here are a few reasons it might be worth currying proponents to use
I'm with you that the three characters shouldn't be necessary but IMO it's a small price to pay to break the deadlock and get some material progress.
Even assuming the arguments above didn't hold up in many cases, if we're collecting usage data, we could/should include Side note: to be honest I slightly disagree with @theScottyJam that new syntax should shy away from incentivising new behaviour. And most people wouldn't need to provide two versions of their functions. Only very broad utility libraries like lodash would need to (and they already have, with But that's exactly why I think minimal-Hack could be the first step of the solution - @theScottyJam and I agree it would be beneficial proposal despite potentially disagreeing about the end state (of course that's just two people (so far), but encouraging given the years of stalemate). |
"Using the curried style" being good, or worth incentivizing, is not something there's consensus on. |
I think this whole currying thing may be a bit tangental anyways. If F# becomes the chosen operator, that's not necessarily because it works better with curried functions. I personally like F# due to its simplicity - it's easier to learn and understand over its hack-style counterpart. It also doesn't require utilizing one of our few available ASCII symbols as a topic variable. The question really comes down to "if F#-style gets introduced, are we willing to commit ourselves to also following through with the partial application proposal"? If yes, then there's nothing wrong with pushing this minimal proposal through while we continue our flame wars between F# and hack styles. |
I am here to learn, How do you reposition parameters in F#? Using a style like Elixir where Enough battles about what the order of the parameters must be have been fought already, which I appreciate how Elixir handles the pipeline operator combine with the capture operator (&). So I am not sure if F# or the proposal supports such cases. 5 LoC allows you to stop worrying about it and don't force brutal alterations to the code because of the orders of the parameters, or you don't need to write even more functions to do basic stuff like changing the order for your use case. I rather have the capture operator, than an auto-curry, it is always easier to build an auto-curry later, but if you can't change the order of the parameters, we will be doomed by legacy code, at least from my perspective and experience. |
If we're talking about the F#-style pipeline operator, the RHS simply takes a unary function, so the only "parameter ordering" that would be needed is "where should the single input go". You wouldn't need something as complex as "&[number]", because within the pipeline operator, only "&1" would be useful (to produce a unary function). So, here's how it would work according to this proposal: // With the proposed partial-application syntax shorthand:
const result = data |> f(1, ?, 2)
// Without the proposed partial-application syntax shorthand:
const result = data |> x => f(1, x, 2) As a general purpose solution to parameter reordering (if we're talking about parameter reordering outside of pipelines), you just always use an arrow function: const fn = (a, b, c) => f(c, a, b) There's nothing wrong with naming the parameters as you reorder them. IMO, a |
After understanding a bit more, I rather stick to Elixir than F# on this one. What could go wrong ™️ I rather have the capture operator and that you are able to do it either way, than locking the specification to a solution that will cause endless discussions and endless LoC changes because of a simple input/output mismatch. Thank you for the clarification thou |
F# pipeline is not going anywhere without partial application. If it could stand on its own, we'd already have the minimal F# pipeline in the language. Every time an issue with the F# proposal pops up, that simply isn't an issue in the Hack proposal, someone responds with F# + some other pipe dream. If/when partial application gets through, then F# pipe can (finally) compete with Hack or Elixir pipe. |
We should try not to get too deep into partial application syntax debate here. There are many issues in both repos that are doing that already. I created this issue to find out if we could work to find a syntax that is an intersection of F# + partial application and Hack. All we need to know is that it's possible - and it seems to me it is, if we limit ourselves to top-level function calls with a compulsory placeholder token. If we can get to that point, then let's start bikeshedding whether the placeholder token should be let person = { score: 25 };
let newScore = person.score
|> double(🐙)
|> add(7, 🐙)
|> boundScore(0, 100, 🐙)
|> somethingCurried(123)(🐙) |
There's an issue with this assumption:
This would wind up being a thing where the way the right-side of the argument behaves is dependent on whether there's a placeholder somewhere in the expression. This is, if I remember, the reason the "Smart" proposal didn't get off the ground. |
No, it's not dependent on whether or not there's a placeholder. Say somethingCurried is defined as follows: const somethingCurried = x => y => x**y Then, both of these would do exactly the same thing in the current F# + partial application proposal: const result = 2 |> somethingCurried(7)(?) // result is 49
const result = 2 |> somethingCurried(7) // result is 49 The reason being is that the Edit: |
This approach is a no-go, for a few reasons. First, a philosophical reason: maximally-minimal approaches are rarely, if ever, actually a good idea. In JS's recent past we tried that with classes; I'm not super familiar with the issues around it, but several committee members are strident in their belief that it was a mistake and just causes a lot of pain as we try to backfill classes with the syntax and abilities they were always meant to have. In this case, it results in a syntax that is literally the worst of both worlds. It's worse than F# because it requires an extraneous It's far better for either camp to just decide on a solution. Second, a more technical reason: this isn't just leaving the door open for F#-vs-Hack, it's bringing partial application into the deal as well. The (This is why, in the latest iteration of the proposal we'll be presenting at the next meeting, we've kicked over to |
Huh. I was under the assumption that partial application would only go in if F#-style pipes did. (I guess it was just something I assumed). I didn't think we would introduce two separate placeholder tokens into the language that achieved nearly the same thing. |
I do agree it would be far better if a camp just decided on a solution. But, unless there's any indication that that'll happen any time soon (I have no idea, maybe we're on the verge of actually having a decision made), I don't see an issue with fleshing out both directions entirely (eliminates your concerns of coming back to pipelines and finding out there's issues actually doing the intended implementation) and going with the "worst of both worlds" for now, once its all fleshed out, which is better than nothing. Unless, effort doesn't want to be exerted to entirely flesh out both sides - which is understandable. |
Is that really true though? In general, I find the opposite - thoughtful, continuous, gradual improvement is a great way to progress. Which is why phrases like "the best is the enemy of the good" exist, and "Kaizen" is a popular and successful philosophy.
Might it be worth considering this approach?
I think specific examples would be helpful for this argument. Do you know which committee members, and what the pain points were? Had forwards-compatibility been considered fully?
Yes. Its strength is that it's forwards-compatible with the two serious end-goal contenders, not that it's better/as good as either of them. Also, "both" is the wrong word, because there are more than two worlds - we are living in the worst of all of them!
Yes. If it had the benefits of Hack over F#+P.A., it would be ruling out F#+P.A., and the stalemate would continue (and vice versa). Note that you still get big benefits over the status quo in a huge number of use cases - simple function calls like
Yes. But if that takes waiting until somebody in their thirties retires, let's go for an intermediate solution.
Yes. If there are people who don't want Hack, and don't want partial application, I haven't seen them speaking up in the debates. Do you know of anybody in that camp?
Why can't we collaborate, and choose a placeholder that champions of Hack and partial application approve as forwards-compatible? Phase 1: |
@yordis - as far as I know, the idea of elixir's capture operator has not been shared yet, so feel free to open an issue a new issue to begin a conversation on that. As for the way elixir's pipeline operator work, there is this issue which discusses the pros and cons of doing pipes elixir style, and whether or not we want to have elixir-style pipes in Javascript. Edit: This was in response to the next comment - looks like it got deleted and recreated with more details. |
I seriously though for like 5 mins before asking the following, my apologies, I am just trying to understand folks, because this is one of those feature that I do care a lot, I am sorry. Please point me to some docs, or explain it again if you dont, seriously, so much information and so many battlers and thoughts that I keep getting lost. What is the problem with doing something like Elixir Pipe and Elixir Capture Operator? At least from my experience in Elixir, It is quite declarative and simple to pass lambdas around, or re-arrange arguments of the function, and such, and the best part, Pipes and Capture Operators are not in the same, they are two different features that combine obviously work like a champ because I think they are well thought out. # single argument
data_by_columns
|> put_in_one_row()
|> or_do_as_many_calls_as_you_want()()()()
|> eventually_you_expand_it_to_single_func_pointer_to_the_pipe()
|> return_value_becomes_first_parameter_of_the_incoming_func()
|> lets_go()
# partial app
data_by_columns
|> put_in_one_row()
# notice that the capture operator is used, we create a closure that will call `more_stuff/2`
# eventually with the appropiate values, what is the problem?
|> then(&more_stuff(partially, &1))
|> or_just_pass_stuff("with_extra_params") Capture means If I want auto-curry things like F#, you can built-in things like create anonymous functions using the same capture operator per every single argument, or stuff that Rambda already does quite well. I am so sorry I am asking, I am not trying to win an argument here, I seriously want to understand because the proporsals that halt the entire thing because you need partial applications feels fishy, and I dont understand the answers that say "we must have the feature", when from my experience, that is not the case in Elixir, granted, the capture operator is handy to fix the partial application issue, and stuff like that (curry functions are just a partial application with arity of 1, so same deal) So please help me here, I dont get it, and the more I read, the more lost I get. |
@yordis - There are multiple valid ways of doing this. Elixir's solution to partial application would be another valid solution, and if you think it's a worthwhile idea, by all means, present it in an issue on this repo (or, even better, on the partial application repo), and we can have a good discussion around it to see how it would really fit into the Javascript ecosystem, and if others share your opinion and think that it would really be a better fit than the current proposal. We welcome new ideas like this :). However, the purpose of this comment thread is to discuss the minimal hack proposal. Ideally, we would want to try and keep these issues on-topic, so it's not really feasible to have a conversation around elixer-style pipes in the middle of this thread (unless, your idea relates to the minimal-hack proposal in a way I'm not seeing?). Thanks for caring about these proposals and for spending the time to share your ideas. |
@theScottyJam thank you so much, I am having a hard time decoupling what "minimal" means since all the conversations keep getting into partial apps, and such. I am sorry for my confusion, just trying to understand. Thank you so much for the comment, appreciate your patience |
Why should we take the partial application proposal — in its current form — as a given? It is severely limited and has a host of issues that are yet to be resolved, possibly by changing the syntax. @yordis suggested Elixir capture operator, which doesn't have any of these issues:
|
@lightmare - the current state of the partial application proposal is simply one of the prerequisites for this idea. Certainly, we shouldn't charge on with "minimal hack" unless we're sure that, if we were to take the F# + partial application route, we're happy with the current formulation of it. So yes, this does mean we still have to make a number of decisions and be willing to set them in stone, before we accept the minimal-hack proposal. If we decide on a different formulation of partial application goes through, such as elixir style, then it would automatically render this proposal obsolete. |
I'm just the person who proposed this idea and don't have any tc39 association, so my opinion isn't especially important, but I also think we should be open to slight adjustments in partial application, if they helped. I suspect some changes would have been made in the last couple of years under normal circumstances but partial application has been on hold until pipelines can settle on a direction. This proposal could be the reason that it becomes worth picking up partial application again. If the direction is "minimal Hack", and @tabatkins can be persuaded that some form of it is better than nothing, then there could be a collaboration process between @rbuckton / @codehag (partial application champions, I believe) to figure out a syntax that nobody will "regret". Maybe that could also be a subset of Elixir-partial-application, with a So maybe 👾= |
(Note that while I have my own objections to the partial-application proposal, they're completely unrelated to anything about pipelines. That's why I'd prefer not to make decisions that tie partial-application's fate into this as well.) Anyway, let's let the meeting at the end of this month happen, since I'll be presenting on pipeline there. If we can make progress there, then great, no need for compromises like this. If we can't, then we can think more about what's needed. |
@tabatkins any updates? 🙏🏻 |
Yup, the proposal advanced to Stage 2 at the meeting, with Hack syntax. README is updated now, so closing this issue. |
@tabatkins thank you! Is there a link to the meeting notes somewhere? |
The meeting notes haven't been published yet - we allow a few weeks for corrections to be made before making them public. When they are released you'll find them at https://github.com/tc39/notes |
For anyone curious in the meantime, it looks like the slides presented are here: https://docs.google.com/presentation/d/1xTnIoh1ECLKAjEQ5YZ5hRw2MZzW67Q78exAR_q2EDgM/edit?usp=sharing Found here: https://github.com/tc39/agendas/blob/master/2021/08.md |
First: I'm sorry if this exact thing is currently the dominant strain, but as far as I can tell from reading the readme, the wiki, the gists and the flamewars in the comments, it isn't.
For the same reasons as outlined in the intro to #167 - achieving progress before perfection, essentially - I feel there's a proposal available that might be palatable to all stakeholders. It won't fully satisfy everyone (or, in fact, anyone) - but it could give us a pipeline operator.
I've called it minimal-hack to distinguish it from the minimal(-F#) proposal, which is biased towards F#, I think.
In a nutshell, it's the Hack proposal, but everything that's inconsistent with the F# + partial application proposal combination is disallowed, for now:
The
?
is compulsory, like in the Hack proposal, but no expressions like7 + ?
, noawait fn(?)
, etc. are allowed. For those use cases, wrapper functions have to be written. Every expression is a function call, and?
must be one of the arguments.There would need to be some negotiation to find a syntax that both Hack and partial-application felt comfortable conforming to in future. Afterwards we could have a smaller flamewar about Hack vs partial application. But in the meantime everyone else could happily use a pretty good pipeline operator.
|> somethingCurried(7)(?)
to become|> somethingCurried(7)
, but that would just be for readability and nothing would break if users don't apply that autofix.|> add(7, ?)
could be simplified to|> 7 + ?
) - likely not autofixable, but again harmless in the former.The text was updated successfully, but these errors were encountered: