tc39 / proposal-do-expressions

Proposal for `do` expressions
MIT License
1.09k stars 14 forks source link

Cannot allow `yield` and meaningfully prohibit `return` #50

Open erights opened 4 years ago

erights commented 4 years ago

As https://github.com/tc39/proposal-iterator-helpers/issues/99 illustrates, if a yield is resumed with a return(), it acts as-if it is a return. The position advocated during the https://docs.google.com/presentation/d/14UYf30NeOd5TFZ4QJFigwBLZVotOwuQq3E-BCMIhGgk/edit#slide=id.g106f4536d9_0_109 presentation is that do expressions should allow yield within their bodies. But not return, because that would introduce a new control possibility: returning from inside an expression.

The possibility is not new, since yield can already return from inside an expression.

Prohibiting return alone does not accomplish this goal, since returning from inside a do expression can still happen using yield.

erights commented 4 years ago

Attn @bakkot

erights commented 4 years ago

Attn @kriskowal

bakkot commented 4 years ago

I'm aware that this is technically possible, but it is scoped narrowly to generators and to expressions which use yield and is not something most JavaScript programmers are often exposed to. I am reluctant to reason from its example.

pitaj commented 4 years ago

Rust allows returning from within an expression everywhere. It's often used to short circuit like so:

fn stuff(a: Option<u32>) -> Option<_> {
  let a = if let Some(a) = a {
    a
  } else {
    return None;
  }

  // do stuff with an unwrapped a
}

Given that Rust is one of the inspirations for this proposal, I think it's worth considering.

bakkot commented 4 years ago

@pitaj Let's keep the general discussion of the utility of return to #30, and leave this issue specifically for talking about the analogy to yield.

devsnek commented 4 years ago

@bakkot is your problem with return in a do expression that it uses the return syntax or that the behaviour of return happens?

hax commented 3 years ago

@pitaj This specific rust example seems not good (because it could be simplified as a? and do not need return)

Even consider similar usage in JS, it seems just ask for "return expression" like "throw expression" proposal, aka. let a1 = a ?? return null.

pitaj commented 3 years ago

@hax for Result, that would be correct, but the try operator working for Option is a fairly new. Regardless, the example still works as a demonstration and changing it so the try operator wouldn't work is trivial.

chriskrycho commented 3 years ago

In case folks here aren’t familiar with Rust’s approach, here’s an example illustrating @pitaj’s point that it’s much more general than the Option/Result/Try/? behavior:

enum ArbitraryData {
  Cool(String),
  Neat(i32),
  Rad { data: bool },
}

fn demo(data: ArbitraryData) {
    let s = match data {
        Cool(s) => s,
        Neat(n) => n.to_string(),
        Rad { data } => {
            println!("demo doesn’t care about booleans!");
            return;
        }
    };

    // other stuff with `s`
}

What JS should do with do expressions and generators specifically is indeed a separate question. And my own intuition here is that this “should” work:

function* cool() {
  let x = do {
    yield true;
    yield false;
    "look sir, droids!"
  };

  console.log(x); // look sir, droids!... after the first two yields.
}

While this is quite strange, and I certainly would never recommend it, intuitively it seems like this is how this should work? (I note that my own intuition matches the slides from the latest presentation.)

kriskowal commented 3 years ago

I agree with @chriskrycho that. It would be strange for do expressions to exist in JS and for his example to not work.

function* cool() {
  let x = do {
    yield true;
    yield false;
    "look sir, droids!"
  };

  console.log(x); // look sir, droids!... after the first two yields.
}
hax commented 2 years ago

One addition, eval() doesn't support yield. I guess it is based on the similar reason (cannot allow yield and meaningfully prohibit return).

So it seems we should either support both return and yield (and break/continue to outer scope), or support none of them like eval.