Skip to content
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

Move await in pipelines to before the operator, not after #145

Closed
dead-claudia opened this issue Dec 24, 2018 · 11 comments
Closed

Move await in pipelines to before the operator, not after #145

dead-claudia opened this issue Dec 24, 2018 · 11 comments

Comments

@dead-claudia
Copy link
Contributor

dead-claudia commented Dec 24, 2018

Edit: Update samples to actually be correct for the relevant proposed variants, and add Elixir-style pipelining

Currently, there appears to be two leading contenders for await in pipelines:

  • x |> f |> await - This targets the F# style, and is relatively obvious to parse.
  • x |> await f - This targets the smart style, but runs into ambiguity with the F# style.

Each of these are equivalent to await f(x).

I suggest this version instead: x await |> f. Here's some pros and cons:

  • Pros:
    • In contexts where await is usable in the first place, it's an error to just do await |> f, even in sloppy mode. So it's not ambiguous in either version. It also visually works for both.
    • When you separate it across multiple lines, it's obvious that await |> is a single unit, so it's less confusing that it means "call and await", much like `await foo().
    • It's easier to track what calls are awaited than the F# style's x |> f |> await.
    • It leaves open the door to x |> (await f) not requiring parentheses, keeping the grammar simpler and more consistent.
  • Cons:
    • You don't get clearly neat lines of |> when combined with await |>
    • It doesn't open the door to x |> awaitawait x or similar that the current F# style allows, treating them as pseudo-functions.
    • await expr is normally an unary operation, so value await |> func doesn't really align well with that.
    • If Elixir-style pipelines are chosen, the ambiguity this solves doesn't really exist.

Here's how these three would compare in code:

// Single-line
// F#-style
getValue() |> foo |> bar |> await |> baz

// Smart style
getValue() |> foo(#) |> await bar(#) |> baz(#)

// Elixir style
getValue() |> foo() |> await bar() |> baz()

// This proposal with F# or smart style
getValue() |> foo await |> bar |> baz

// Multi-line
// F#-style
getValue()
|> foo
|> bar |> await
|> baz

// Smart style
getValue()
|> foo(#)
|> await bar(#)
|> baz(#)

// Elixir style
getValue()
|> foo()
|> await bar()
|> baz()

// This proposal with F# or smart style
getValue()
|> foo
await |> bar
|> baz

I expect mixed reviews of this, so I'm not going to lose sleep if it doesn't make it. Also, I'm only mildly in favor of it.

@jhpratt
Copy link

jhpratt commented Dec 24, 2018

I'd very much prefer the F# style. The smart style I could get used to, but your proposal of not even having await next to the thing being awaited seems incredibly counterintuitive to me.

@bjunc
Copy link

bjunc commented Dec 24, 2018

Personally, the only one that makes sense to me is this:

let value =
  foo
  |> await bar()
  |> baz()
  |> await qux()

The pipeline is for functions (at least in Elixir). Await is not a function itself, but rather an operator preceding an expression (often a function that returns a Promise).

When I see this:

let value = 
  foo
  |> bar()
  |> await
  |> baz()
  |> qux()
  |> await

This reads to me as calling a function called await():

let barResponse = bar(foo)
let awaitResponse = await(barResponse)
let bazResponse = baz(awaitResponse)
let quxResponse = qux(bazResponse)
let value = await(quxResponse)

In Elixir, you can leave off parenthesis for zero arity functions (await/0)

@mAAdhaTTah
Copy link
Collaborator

To clarify, your smart style await examples would all need a placeholder token when using with await:

// Smart style
getValue()
|> foo
|> await bar(#)
|> baz

@dead-claudia
Copy link
Contributor Author

dead-claudia commented Dec 24, 2018 via email

@bjunc
Copy link

bjunc commented Dec 24, 2018

@mAAdhaTTah, pardon the ignorance, but what is the rationale for a "placeholder" in the async function reference? Is the purpose similar to Elixir's &1 (capture operator / value placeholder)?

And is "smart style" synonymous with "Elixir style" (sans the placeholder)?

@mAAdhaTTah
Copy link
Collaborator

@bjunc To be clear, a placeholder is only necessary in the Smart style pipelines. The overall need here is to avoid ambiguity between whether x |> await f desugars to (await f)(x) or await f(x).

They're not synonymous, but it seems similar to the capture operator? although I'm not familiar enough w/ Elixir to say so definitively. Smart doesn't do the argument insertion the same way Elixir does though; all of the insertion has to be done explicitly via placeholder.

@dead-claudia
Copy link
Contributor Author

@bjunc @mAAdhaTTah You might be interested in my most recent comment in the issue proposing Elixir-style pipelining.

@littledan
Copy link
Member

This alternative just seems very strange and counter-intuitive to me. I prefer either the F# or smart alternatives.

@dead-claudia
Copy link
Contributor Author

@littledan Just thought I'd reiterate the footnote in the original comment:

I expect mixed reviews of this, so I'm not going to lose sleep if it doesn't make it. Also, I'm only mildly in favor of it.

And to be quite honest, I find the syntax a bit awkward myself. That's one of the cons I listed.

@Fenzland
Copy link

Smart Style and Elixir Style are not compatible to main proposal, event to ECMAScript. Because () should means we call the function and pass the returned thing to the right hand of |> operator. On the other hand, # goes too far away, it'll push us away from reaching an agreement, arrow function is the most practicable plan for placeholder currently.

Both This Proposal Style and #F Style are reasonable to me.
This Proposal Style is briefer, and most importantly, it kept one thing: the value follow |> is always a function.
#F Style is most pipeline ordered. we call the function firstly, then await the promise, so put the await after the function.

@tabatkins
Copy link
Collaborator

Closing this issue, as the proposal has advanced to stage 2 with Hack-style syntax. Pipe bodies are now arbitrary expressions, so you can put await whereever it would be most readable: x |> await pFn(^), x |> pFn(^) |> await ^, etc.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 12, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants