jashkenas / coffeescript

Unfancy JavaScript
https://coffeescript.org/
MIT License
16.45k stars 1.98k forks source link

Bug: yield cannot be used in do -> expressions reliably? #5461

Closed borkdude closed 5 months ago

borkdude commented 5 months ago

I expect yield to be used anywhere in a function, but it seems do -> breaks this expectation since it's compiled to an IIFE.

Repro:

https://coffeescript.org/#try:perfectSquares%20%3D%20-%3E%0A%20%20num%20%3D%200%0A%20%20do%20-%3E%0A%20%20%20%20y%20%3D%201%0A%20%20%20%20yield%20y%3B%0A%20%20loop%0A%20%20%20%20num%20%2B%3D%201%0A%20%20%20%20if%20num%20%3E%2010%20%0A%20%20%20%20%20%20break%0A%20%20%20%20yield%20num%20*%20num%0A%20%20return%0A%0AglobalThis.foo%20%3D%20perfectSquares()%0A%0A

Note that I'm only theoretically interested in a solution as I'm writing my own to-JS transpiler and was wondering if CoffeeScript authors have thought about this more than I have. More info here.

edemaine commented 5 months ago

I would not view this as a bug, but as intended semantics: do -> literally means to build a function (->) and call it (do) — very different from the TC39 do proposal. This functionality of do -> is useful, for example, if you want to build a generator right now. For example:

processGenerator do ->
  yield 1
  yield 2
  yield 3

Here's [a more real-world example with async](https://coffeescript.org/#try:await%20Promise.all(%0A%20%20for%20url%20of%20urls%0A%20%20%20%20do%20-%3E%20await%20(await%20fetch%20url).json()%0A)), where do enables await syntax without "infecting" the entire function:

Promise.all(
  for url of urls
    do -> await (await fetch url).json()
)

I think where CoffeeScript's behavior is less intuitive is when it happens with implicit IIFEs; see #5363 where I asked about return inside a for loop, but the same applies to yield within a for loop. We actually get my intended behavior from #5363 in Civet, but not when it's a for-loop expression which, as #5363 details, is hard to transpile. We've discussed this some in a Civet context, where we plan to do more thorough rewriting to handle cases like this, but haven't tackled it yet. See https://github.com/DanielXMoore/Civet/issues/202 and https://github.com/DanielXMoore/Civet/issues/381.

borkdude commented 5 months ago

Makes sense, I interpreted do -> as the TC39 proposal. The babel transpiler for that proposal does the thing I would expect (for implicit IIFEs).

edemaine commented 5 months ago

Cool, I didn't know about that plugin. I agree that it works well with yield, but it doesn't work well with return (compare with the proposal) and doesn't support break.

borkdude commented 5 months ago

but it doesn't work well with return and doesn't support break

luckily for my language (squint) this isn't a problem since it is expression-oriented and therefore doesn't support explicitly returning things. I think it could be a reasonable trade-off for do expressions too since they have an implicit return value, maybe it should even barf on an explicit return

https://babeljs.io/repl#?browsers=defaults%2C%20not%20ie%2011%2C%20not%20ie_mob%2011&build=&builtIns=false&corejs=3.21&spec=false&loose=false&code_lz=GYVwdgxgLglg9mABMAFASkQbwFCMRBAZykQA9EBeRAEzi1z3yJIE9LEBGABgbwDYGAX2yCgA&debug=false&forceAllTransforms=false&modules=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=env%2Creact%2Cstage-2&prettier=false&targets=&version=7.23.6&externalPlugins=%40babel%2Fplugin-proposal-do-expressions%407.23.3&assumptions=%7B%7D

borkdude commented 5 months ago

Here is an illustration of generator functions in squint

https://squint-cljs.github.io/squint/?src=KGRlZm4gXjpnZW4gZm9vIFtdCiAgKGpzLXlpZWxkIDEpCiAgKGpzLXlpZWxkKiBbMiAzXSkKICAobGV0IFt4IChpbmMgMyldCiAgICAoanMteWllbGQgeCkpCiAgKGxldCBbeCAoZG8gKGpzLXlpZWxkIDUpCiAgICAgICAgICAgIDYpXQogICAgKGpzLXlpZWxkIHgpKSkKCih2ZWMgKGZvbykp