Named placeholders
Just wondering if it wouldn't guide our intuition a little better if we could gives names to placeholders in the same manner as arrow-function arguments?
envars
x |> Object.keys(x)
keys |> keys.map(x => `${x}=${envars[x]}`)
arr |> arr.join(' ')
str |> `$ ${str}`
line |> chalk.dim(line, 'node', args.join(' '))
out |> console.log(out);
No need to choose between ^ or %, no conflicts with mod/pow, and has a better chance of producing self-documenting code.
What are the drawbacks to this approach?
It now looks quite similar to the F# proposal except it disallows point-free in exchange for dropping an arrow:
envars
|> x => Object.keys(x)
|> keys => keys.map(x => `${x}=${envars[x]}`)
|> arr => arr.join(' ')
|> str => `$ ${str}`
|> line => chalk.dim(line, 'node', args.join(' '))
|> out => console.log(out)
Where for clarity point-free means something more like this, which would've been deemed more idiomatic with that proposal:
envars
|> keys
|> map(x => `${x}=${envars[x]}`)
|> join(' ')
|> prefix('$ ')
|> chalk.dim('node')(args.join(' '))
|> console.log
If there is actually any chance of an F#-style operator following the blessed Hack one then I think it covers this use case without introducing more special syntax.
Related comments:
The named placeholder idea was also proposed here: https://github.com/tc39/proposal-pipeline-operator/issues/91#issuecomment-363092439 With a similar response here: https://github.com/tc39/proposal-pipeline-operator/issues/91#issuecomment-917466063
@Avaq
The named placeholder idea was also proposed here: https://github.com/tc39/proposal-pipeline-operator/issues/91#issuecomment-363092439 With a similar response here: https://github.com/tc39/proposal-pipeline-operator/issues/91#issuecomment-917466063
I wouldn't say my response is similar per se. Let me just clarify: the first link advocates for named placeholders with Hack, and mine advocates F# over Hack. edit: correction: The first link also considers F# over Hack in the second part, which is identical to mine.
Named placeholders, as @samhh mentions here in the comment above:
https://github.com/tc39/proposal-pipeline-operator/issues/203#issuecomment-917481832
It [named placeholder] now looks quite similar to the F# proposal except it disallows point-free in exchange for dropping an arrow
(note the point-free mention on yet another point for why F# is better than Hack, even with named placeholders!), and
If there is actually any chance of an F#-style operator following the blessed Hack one then I think it covers this use case without introducing more special syntax.
which is exactly what I'm advocating for in my response - F# over Hack:
https://github.com/tc39/proposal-pipeline-operator/issues/91#issuecomment-917466063
In the meantime, I'm trying to write a longer peace about pipeline operators, how the F# way is already available via [].map and Promise.then (monads & stuff), relations with functional programming and why we shouldn't rush this, but it will take a while for me. The point is - the Hack proposal choice feels rushed, we need more experienced people in the discussion (esp. from the FP space since they've had experience solving the same problem we're trying to solve) and I am very afraid of us making a big mistake.
Advocates of functional programming (FP) have very much been part of the discussion post and prior to the decision to go with hack. (See e.g. here). However I’m not aware of any of them being a member of the language committee that ultimately made the decision to go with hack pipelines.
I guess we are all waiting to see the meeting notes and see which justification for why the concerns raised by FP folks were not important enough to warrant further consideration.
Good points @runarberg.
As per https://github.com/ReactiveX/rxjs/issues/6582#issuecomment-909556856,
It's at the discretion of members of the TC39 and has little to do with what non-members want or even popular common usage. It's just what a paid member is willing to push forward, unfortunately, and the other proposal didn't have a champion from a member company.
which seems complete bonkers. How it is even possible to choose one of the competing proposals if nobody is defending the other one?
How it is even possible to choose one of the competing proposals if nobody is defending the other one?
That's not what happened. There were several members of the champion group who advocated for F# but with the committee starting to achieve consensus, believe Hack is better than no pipeline and won't be blocking the proposal.
Thank you for the replies so far all. I must say, after reading through them and the linked content I'm convinced that the F# style fits JavaScript better than the Hack proposal.
I was rather alarmed by the slightly conspiratorial wording that @kiprasmel quoted. It added to a nagging sensation that 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?
Your observation is correct @shuckster.
@benlesh has discussed this in the RxJS repo (basically, Hack sucks):
Currently our "pipeable operators" rely on higher-order functions. Using these with the Hack Pipeline will be cumbersome, ugly and hard to read: <...>
and as he mentions in the @tabatkins discussion:
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.
That's not what happened. There were several members of the champion group who advocated for F# but with the committee starting to achieve consensus, believe Hack is better than no pipeline and won't be blocking the proposal.
I don't belileve you or I or anyone outside the committee knows exactly how things have gone down @mAAdhaTTah -- I think there are dissenting views on the committee that should not be overlooked: https://github.com/tc39/proposal-pipeline-operator/issues/91#issuecomment-917645179. Also, I think it would be better to have no pipe rather than a half-baked hack-pipe.
If you have to type placeholders -- you're doing it wrong! 🐙
@aadamsx That dissenting view has not been overlooked; he advocated for F# but the committee's momentum is heading towards Hack, and he has made (imo) a reasonable decision that he will not prevent that momentum from allowing the Hack pipe to advance into the language. You're welcome to disagree with that decision, but he has been a part of this process since the beginning and is hardly being overlooked.
the committee's momentum
is a joke. I completely agree with @aadamsx:
it would be better to have no pipe rather than a half-baked hack-pipe.
and I will post more on this soon.
I don't belileve you or I or anyone outside the committee knows exactly how things have gone down
If you don't know how it went down, then why are you so sure the answer isn't "most of the committee was convinced of Hack's advantageousness, and the ones who preferred F# saw the writing on the wall & preferred Hack to advance than nothing at all"?
why are you so sure the answer isn't "most of the committee was convinced of Hack's advantageousness, and the ones who preferred F# saw the writing on the wall & preferred Hack to advance than nothing at all
I'm not saying one way or the other, just that you/I/we don't know for sure, but only what other committee members let out.
What I take from Ron B's comments, all where on board to go to Stage 2 based on what was presented by Tab, but NOT ALL agree as of yet that Hack > F# -- therefore no consensus.
I'm not saying one way or the other, just that you/I/we don't know for sure, but only what other committee members let out.
Given Ron's comment and the conversations I've had with Tab & other champions, given that I myself moved from F# -> Hack preference, and given that the committee doesn't advance things without consensus, I have far more evidence this is what happened than a coup by a small number of committee members. The committee had to be in consensus to advance, because if a single member objected, it would not, could not, advance.
From where I stand—as a layperson in language design and a mere user of the JavaScript language—there are two possibilities here. Either the TC39 process was hacked in order to advance one proposal over the other or the TC39 process is flawed.
I was under the impression that users of functional libraries were going to get a language construct which would ease development using them. A few years ago this construct was supposed to be the bind operator (::) but that was abandoned in favor or the pipe operator (|>). I’m not sure this is how things were, but this is how I perceived it. The pipe operator laid in limbo for a couple of years, some discussion popped up occasionally comparing alternatives, but no conclusion was reached. The pipeline there seemed to be a deadlock in choosing either the pipeline operator that I thought we were promised or an alternative which would make it useless for us. Several proposals were voiced that would break the deadlock (including minimal proposals (#167), the lowest common denominator (#192), usability studies (https://github.com/tc39/proposal-pipeline-operator/issues/167#issuecomment-751322272)). Then after no progress, all of a sudden a decision was made and the benefits that the pipe operator provides to the functional community has been striped away. The decision was made based on unconvincing evidence and a sizable portion of the community are now seemingly either disappointed or angry.
To my eyes something has failed. Either the wrong decision was made or at the very least, only minimal effort has been made in convincing us otherwise. The arguments I am perceiving rely heavily on appeal to authority which is not a good look on the TC39 process.
I'm disappointed that I've had to once again mark a large chunk of an issue thread as off-topic. The OP and the first few comments presented a reasonable issue and discussion; this is not a place to diverge into yet another discussion of F#-vs-Hack or baseless speculation on committee proceedings. There are dedicated threads for that.
@tabatkins - Apologies for my part in that. I've since caught up in much of the discussion and regret getting caught-up in the politics of it.
I'm not certain the syntax suggested in the OP is actually viable; there might be parsing ambiguities with the preceding line.
Putting that to the side, tho, this runs into some of the arguments against temp variables from the README. Definitely not all, since the bindings are still scoped to a single pipe step, which avoids several issues, but the "naming is hard" part still applies.
Most of the time, you don't need to give a name to the topic; it's clear from context, and the topic value doesn't represent a semantically significant unit in your project. (See the README again, where several of the names are just restatements of the operation happening on the line, rather than actually meaningful names.) So I expect most of these lines would, in practice, just use the name x or something, whatever the user typically uses for temp var names. I'd prefer we just use a chosen standard name in those cases, to communicate the semantic that the value is just flowing from the previous line and isn't significant on its own.
That said, I'm not opposed to naming the placeholder sometimes, when it helps. It would just need a good syntax that doesn't cause issues.
I'm disappointed that I've had to once again mark a large chunk of an issue thread as off-topic.
@tabatkins, with all due respect, could you just not?
Nobody complained about the comments being off-topic.
If you had marked my comment https://github.com/tc39/proposal-pipeline-operator/issues/91#issuecomment-917755642 to move the discussion from #91 to #205 earlier than you did, you would've denied a wider discussion with community members that's happening right now in #205, AND made it worse for the #91 thread itself, because you'd get actually off-topic comments there, instead of them being on-topic in #205.
Also, how is https://github.com/tc39/proposal-pipeline-operator/issues/203#issuecomment-917677239 off-topic? It highlights an important concern, and you allow yourself to hide it from others' eyes just because it had something non-so-related to say too?
Do you realize the negative impact that your moderating has?
@kiprasmel - Judging from his last reply, I don't actually think @tabatkins is afraid of hearing criticism, even if it's delivered emotionally, so long as we keep it in separate threads. I still genuinely wonder about pipe() examples, but I realise I didn't do myself any favours by embroiling myself in conspiracy, especially as I had merely read the README at that point.
Anyway, I did have an idea for placeholder-naming that follows-on from the OP, but I'll reply separately as this post is also clearly off-topic at this point.
Thank you for the reply @tabatkins . My initial crack at placeholder naming is as follows:
envars
|> Object.keys(^)
|> ^keys.map(x => `${x}=${envars[x]}`)
|> ^arr.join(' ')
|> `$ ${^}`
|> chalk.dim(^line, 'node', args.join(' '))
|> console.log(^chalked);
Essentially, everything following the token is working like an inline comment up until the next consumable character.
Janky, but that's all I got at the moment. I do appreciate the arguments against temp-variables, but I'd like to think the Hack token is just about comparable to the i in the uncountable for-loops of times gone by. But still today we have a chance of renaming them!
It took me a while to realize that I suggested the exact same thing in another topic a few days after @shuckster, apologies for not giving proper credit, I must've tricked myself into thinking "iOriginal".
Although I like the OP syntax the most (out of all the named-placeholder options I've seen), I share @tabatkins concern regarding grammar. I'm not sure how exactly it'd play out, at first I thought it's fairly simple: Expr Identifier |> Expr. But it seems like it would require more lookahead for any expression followed by a newline and an identifier.
Currently, when the parser reads Expr \n Identifier, it inserts a semicolon at the newline. But if that could be the left-hand-side of a pipeline, it would need to check the next token as well.
So it might be safer, though not as pretty, to place the identifier after the operator: Expr |> Identifier Expr.
Exploring further, here's something crazy:
In this variant you could replace the Identifier with (BindingIdentifier | BindingPattern) — since the context is clear from the preceding |> you can have destructuring right there.
And then, if we disallow newline between the binding and the pipe body, we could allow a pipe without that binding to behave differently...
// Expr |> BindingIdentifier [noLineBreak] Expr
// single-binding Hack behaviour
obj |> _ Object.keys(_) |> _ log(_.join(", "));
// Expr |> BindingPattern [noLineBreak] Expr
// destructuring Hack behaviour
/(?<k>\w+)=(?<v>.*)/.exec(input)
|> { groups: { k, v }} (options[k] = v);
// Expr |> Expr
// F# behaviour
arg |> foo |> (x => bar(x, 2));
// yes I would absolutely require parentheses around arrow functions
// to avoid all kinds of ambiguities
@js-choi I just noticed you added the follow-on-proposal label here. May I suggest considering named placeholder an alternative baseline instead?
If we started with grammar such as: Expr |> BindingIdentifier [noLineBreak] Expr
The topic would be user-defined identifier, so you could introduce the pipe operator without a new topic token.
Expr is a placeholder for whatever the precedence would dictate. I would give the pipe the highest precedence among binary operators — putting a writer-tax on binary expressions and yield in pipeline. But that's a separate discussion.
Then if need arises you could allow destructuring the topic in place in a follow-up: Expr |> BindingPattern [noLineBreak] Expr
And when bikeshedding the topic token finally concludes, you could enable: Expr |> Expr without explicit topic binding. Or if implicit topic becomes unnecessary, reserve that for F# style. (edit: this has serious potential for human-reader confusion, so probably not viable; but Expr |> PFA might be viable)
I did a search of this repository for the word "with" and didn't see this idea, which I take as an imperfect hint that this hasn't been suggested in the repo. Just thought, since with use is discouraged, maybe something like this could work...
with name |> expr(name)
such as...
// A single name
const shoutedName = with userName |> capitalize(userName) |> amplifyVolume(userName)
// multiple names??
const shoutedName = with userName |> capitalize(userName)
with capitalName |> amplifyVolume(capitalName)
// or perhaps it only names the next one?
const shoutedName = with userName |> capitalize(userName) |> amplifyVolume(^)
This would seem to be unambiguous to the untrained eye, since old-style with requires parens with(expr).
It also seems coincidentally teachable since "with x" and "const x" are similar.
@aikeru none of those pipelines seem to have an initial value.
@aikeru none of those pipelines seem to have an initial value.
Ah, ha! Perhaps that foils this idea, then, but maybe something like this?
const result = unNamed with named |> make(named)
...or not. Thanks anyway!
I just noticed you added the
follow-on-proposallabel here. May I suggest considering named placeholder an alternative baseline instead?
Whether to make declaring an identifier at each pipe step required in the initial proposal…
It’s not only up to me, of course.
But I’m personally lukewarm towards this. It seems pretty verbose, since it’d be required in each step.
By the time we’re naming variables, we might as well be just using variable assignment, right? A lot of the purpose of this proposal is to Not Name the Thing When You Don’t Want to.
Anyways, sorry if that response is disappointing. Hopefully it’s understandable. Also, it’s not only up to me—there are other champions—but I also suspect that it would be unpopular with the greater Committee…
By the time we’re naming variables, we might as well be just using variable assignment, right?
No. Because apples-to-apples equivalent would use const which you can't re-assign. Also variables don't go out of scope afterwards.
The difference is who gets to name the variable: the committee, or the user.
Yeah, baking this in as a requirement for each pipeline step is a no-go. The existing piping syntaxes in JS (method chaining, userland pipe()) don't name the topic variable being passed between each stage, and that still makes for perfectly reasonable and readable code. We don't need to require additional verbosity at each stage for this piping syntax.
The existing piping syntaxes in JS (method chaining, userland pipe()) don't name the topic variable being passed between each stage, and that still makes for perfectly reasonable and readable code.
Not sure how that's relevant, as this piping syntax does name the topic.
We don't need to require additional verbosity at each stage for this piping syntax.
That's assuming a single-character topic token. Otherwise there's no additional verbosity required.