proposal-do-expressions icon indicating copy to clipboard operation
proposal-do-expressions copied to clipboard

Cannot allow `yield` and meaningfully prohibit `return`

Open erights opened this issue 5 years ago • 11 comments

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 avatar Jun 02 '20 01:06 erights

Attn @bakkot

erights avatar Jun 02 '20 01:06 erights

Attn @kriskowal

erights avatar Jun 02 '20 01:06 erights

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.

bakkot avatar Jun 02 '20 01:06 bakkot

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.

pitaj avatar Jun 02 '20 01:06 pitaj

@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.

bakkot avatar Jun 02 '20 01:06 bakkot

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

devsnek avatar Jun 02 '20 01:06 devsnek

@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.

hax avatar Mar 02 '21 18:03 hax

@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.

pitaj avatar Mar 02 '21 18:03 pitaj

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.)

chriskrycho avatar Mar 20 '21 17:03 chriskrycho

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.
}

kriskowal avatar Mar 20 '21 20:03 kriskowal

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.

hax avatar Jul 07 '22 01:07 hax