Skip to content
This repository has been archived by the owner on Jan 26, 2022. It is now read-only.

Bare constructor/awaited function call #3

Closed
littledan opened this issue Mar 15, 2018 · 11 comments
Closed

Bare constructor/awaited function call #3

littledan opened this issue Mar 15, 2018 · 11 comments
Assignees
Labels
discussion Discussion about a design decision

Comments

@littledan
Copy link
Member

I'm surprised by the inclusion of this construct. It seems a bit complicated to me, and it ends up necessitating disallowing arbitrary expressions on the right hand side of a pipeline (#2). I can see that it's used in some of your examples, but I wonder if it would be OK to just include an explicit (#) for these.

@js-choi js-choi self-assigned this Mar 15, 2018
@js-choi
Copy link
Collaborator

js-choi commented Mar 15, 2018

Arbitriary expression are allowed on the right-hand sides of pipeline expressions, as per #2 (comment). (I do need to reword how I communicate this, to avoid similar confusion in the future. I would be interested to know how I might do this, from your perspective.)

As I mention in this es-discuss post a few days ago, the topic syntax is expected to be used most often, as it supports arbitrary expressions. The bare syntax is only meant to optimize for unary functions, but in the end it is only a convenient abbreviation for topic style.

Bare style is a special syntax, with special evaluation semantics, optimized for one common use case: unary functions. The new and await tokens are just flags for bare style because unary constructor calls and awaited unary async-function calls are also quite common, as my review of real-world codebases in Motivation shows. But in general, I expect topic style to be used more often, and topic style itself is very terse.

But I have been considering for the past week to separate bare constructor calls and bare awaited function calls from the Core Proposal, into their own Additional Features BC and BA. I would change the examples in the Core Proposal that use bare constructor calls and bare awaited function calls so that they use topic style new …(#) and await …(#), as you suggest above. I will do this this weekend. I will also similarly update your presentation’s examples. Does this plan seem reasonable to you?

@js-choi
Copy link
Collaborator

js-choi commented Mar 16, 2018

From #2 (comment) by @tabatkins:

I think those do make sense to be a (single) separate feature, yeah; it makes it clearer that this is a syntax extension point that can take on new stuff in the future too. The x |> foo.bar part is the biggest argument for bare-form and against arbitrary-expression-form anyway. ^_^

@tabatkins
Copy link

it ends up necessitating disallowing arbitrary expressions on the right hand side of a pipeline (#2). I can see that it's used in some of your examples, but I wonder if it would be OK to just include an explicit (#) for these.

Ultimately this is a value judgement about the relative worth of making different things easier.

  1. Both bare-style and "expression-style" allow for the simplest use-case, x |> foo.

  2. Expression-style additionally allows for point-free/auto-curried/decorated examples more easily, like xs |> filter(pred). If we use bare-style this must be written in topic-style instead, as xs |> filter(pred)(#).

  3. Bare-style allows for method calls to work more easily, like x |> foo.bar. If we use expression-style this must be written in topic-style instead, like x |> foo.bar(#).

  4. Bare-style also allows for constructors to work more easily, like x |> new Foo. If we use expression-style this must be written in topic-style instead, like x |> new Foo(#).

  5. Bare style also allows for async functions to be awaited more easily, like x |> await foo. If we use expression-style this must be written in topic-style instead, like x |> await foo(#).

(For all of these, if we also don't have topic-style, then the alternative would have to be written as an arrow function.)


Additionally, in case 2, if we have bare-style and you accidentally try to write xs |> filter(pred), you'll get a syntax error pointing you straight to the problem, ideally with a simple error message.

On the other hand, in case 3, 4, and 5, if we go with expression-style, then writing out the simple version will silently be interpreted as the wrong thing, either raising a runtime error or just doing confusing things. x |> foo.bar is equivalent to do { const method = foo.bar; method(x) }, losing the this binding; x |> new Foo is equivalent to (new Foo)(x), constructing a Foo with no args and then trying to call it like a function; x |> await foo is equivalent to (await foo)(x), waiting on the foo value itself and then trying to call it like a function.

As we find more function-call patterns that could benefit from shorthanding, bare-style can continue to grow, keeping things easy to use. Expression-style can't; at best we just continue to get more possibly-confusing cases.


@littledan, your current proposal using expression-style tries to solve the await case by specially carving out one particular expression shape (x |> await foo), and decreeing it to have special semantics different from what it would naively look like. Breaking that shape in trivial ways, like x |> (await foo) instead causes it to be interpreted like normal, awaiting the foo value itself, rather than calling foo and awaiting its result. This just feels extremely clumsy to me. :/ Bare-form/topic-form makes the distinction between "expressions" and "special forms" much brighter and easier to see - you need to use # to get an arbitrary expression, otherwise you're only allowed the very limited syntaxes of bare-form, and will get a syntax error if you try to go beyond them.

It doesn't even try to solve methods or constructors yet, tho presumably it could add further special cases to make that work. That just means more possibility of confusion, tho, when people accidentally step outside the blessed expression-shape and accidentally fall into arbitrary-expression territory again.

I think this would be especially bad for methods - exactly how much variance would be allowed? Would x |> foo().bar work? x |> (foo.bar)? x |> foo[y].bar? All of these are reasonable things I could see someone writing, and expecting them to work the same as x |> foo.bar.

@js-choi
Copy link
Collaborator

js-choi commented Mar 17, 2018

@tabatkins: Note that there have been some recent changes in the original pipeline proposal, as well as @mAAdhaTTah’s Proposal 1. See babel/babel#7458 for the discussion from which those changes emerged. See babel/proposals#29 (comment) and the slideshow for next week’s meeting for a summary.

Basically, the fragility problem in #3 (comment) no longer applies to await in the original proposal, but it does apply to @mAAdhaTTah’s Proposal 1.

The fragility problem does also still apply to both the original proposal and Proposal 1 in their special casing of method binding.

We’re calling the original proposal the “minimal proposal”, and it forbids await after |>. So the original/minimal proposal doesn’t have the naive-special-case problem anymore. Instead, it doesn’t deal with await at all (unless you wrap, with unnecessary promises, the values of every step after every async function in every pipeline).

In contrast, @mAAdhaTTah’s Proposal 1: F-sharp Style Only with Bare Await, does address await with a special case: a bare nullary await pipeline body: … |> await. This does come with its own new ASI footgun (see tc39/proposal-pipeline-operator#85 by @bakkot). More relevantly for this issue, it still probably suffers from the same easy fragility: … |> (await) would silently behave differently without a runtime bug. @mAAdhaTTah might want to explore similar early error approaches similar to mine for Proposal.

Both the original/minimal proposal and Proposal 1 might also have special casing for method binding, in which case the fragility problems in #3 (comment) do also apply. I need to recheck the original/minimal proposal’s spec. As for Proposal 1, @mAAdhaTTah is in the middle of writing its spec, and he said he plans to make a special case for method binding.

If you have time, take a look at the slideshow for next week’s meeting, which is now available from the TC39 agenda. @mAAdhaTTah is also planning to update the original proposal’s readme and its wiki with the new plans (for which I am very grateful).

@js-choi
Copy link
Collaborator

js-choi commented Mar 17, 2018

Anyways, I’m still working on separating bare awaited function calls and bare constructor calls into their own additional features. @tabatkins, you mentioned that you think they should be grouped together in one additional feature rather than two (“I think those do make sense to be a (single) separate feature, yeah”). Was there any particular reason why?

@js-choi
Copy link
Collaborator

js-choi commented Mar 17, 2018

@littledan
Copy link
Member Author

You might want to leave this bug open for further discussion on the feature.

@js-choi js-choi reopened this Mar 18, 2018
@tabatkins
Copy link

@tabatkins, you mentioned that you think they should be grouped together in one additional feature rather than two (“I think those do make sense to be a (single) separate feature, yeah”). Was there any particular reason why?

Technically they could be separated, sure, but they seem very similar in practice - once you're allowing special keywords in bare-form, it makes sense to allow more.

@js-choi
Copy link
Collaborator

js-choi commented Mar 18, 2018

@tabatkins: Yesterday I couldn’t come up with a good name for a feature with both bare awaited calls and bare constructor calls, so I pushed a change that puts them in separate additional features. If you get a chance to glance over the new explainer, let me know if you feel that I should still combine them into one section. Thanks! Maybe the name “Bare Extras” could have worked…

@js-choi js-choi assigned js-choi and unassigned js-choi Mar 19, 2018
@js-choi js-choi assigned js-choi and unassigned js-choi Mar 21, 2018
@js-choi js-choi added explainer/readme About readme.md spec About spec.html discussion Discussion about a design decision and removed spec About spec.html explainer/readme About readme.md labels Mar 24, 2018
@Sceat
Copy link

Sceat commented Jun 1, 2019

Bare style is the obvious and purely instinctive way of piping something, getting rid of verbosity and currying 🤔

const add = a => b => a + b
1 |> add(1) // should print 2

isn't using bare style by default and using topic only if a # is spotted, a good idea ? a developer wanting to use topic would just have to insert the #, other functional programmers would not be tied up that way

@js-choi
Copy link
Collaborator

js-choi commented Mar 8, 2021

Thanks for the issue; sorry about the delayed reply.

After talking more with Tab Atkins and Daniel Ehrenberg, we’ve decided to archive this proposal in favor of a simpler Hack-pipes proposal, which is a subset of this proposal.

Feel free to open a new issue in the new proposal’s repository if you think it still applies.

@js-choi js-choi closed this as completed Mar 8, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
discussion Discussion about a design decision
Projects
None yet
Development

No branches or pull requests

4 participants