proposal-generator-arrow-functions icon indicating copy to clipboard operation
proposal-generator-arrow-functions copied to clipboard

Syntax

Open chicoxyzzy opened this issue 5 years ago • 85 comments

Possible solutions

Arrow function syntax

// Irregular
() =*> ...

// not the same order as in regular generator functions
() =>* ...

// also wrong order
() *=> ...

// ASI hazard
*() => ...

Introduce new generator keyword for both function and arrow function

generator function() {}
const foo = async generator function() {};

class Foo {
  x = 1
  // No more ASI hazard!
  generator foo() {}
}

Previous discussions https://github.com/tc39/proposals/issues/216

chicoxyzzy avatar Oct 21 '19 18:10 chicoxyzzy

@littledan and I had some hallway-track conversations about this. My thoughts were to add a gen keyword (much like the async keyword), and treat the * sigil as the ugly wart it is:

gen function foo() {} // same as function* foo() {}
foo = gen () => {};

class Foo {
  x = 1
  // No more ASI hazard!
  gen foo() {}
}

When creating an async generator, the order would be async gen.

jridgewell avatar Oct 21 '19 18:10 jridgewell

There is an ASI hazard here

foo = gen ()
  => {};

chicoxyzzy avatar Oct 21 '19 18:10 chicoxyzzy

That's a syntax error currently, right? I don't understand how it would cause a new ASI hazard.

jridgewell avatar Oct 21 '19 18:10 jridgewell

I can imagine only two variants: 1.

foo = (*)(arg) => {};
foo = (*(arg)) => {};

second could be simplified for one / zero arguments as:

foo1 = (*arg) => {};
foo0 = (*) => {};

MaxGraey avatar Oct 21 '19 19:10 MaxGraey

Adding a new gen is interesting, but there's no value imo in abbreviating - why not generator? (it's not func or async fn etc)

ljharb avatar Oct 21 '19 19:10 ljharb

More radical solution is using different brackets like [] instead ():

foo1 = [] => {}
foo2 = [[first, second]] => {}  // with array destruction
foo3 = [a, b] => {}

MaxGraey avatar Oct 21 '19 19:10 MaxGraey

TBH, any of *=>, =>* or =*> look totally fine and clear to me.

chicoxyzzy avatar Oct 21 '19 20:10 chicoxyzzy

*=> seems least surprising to me, with =*> seeming least obvious (it helps to still have ‘the arrow’ for recognition).

bathos avatar Oct 21 '19 20:10 bathos

Adding a new gen is interesting, but there's no value imo in abbreviating - why not generator?

Either is fine with me.

*=> seems least surprising to me, with =*> seeming least obvious (it helps to still have ‘the arrow’ for recognition).

Least surprising, but still super ugly. I (as a relatively experienced JS dev) still struggle with the order for function * foo() {} ("is it before function, in between, or after the identifier?"). I imagine new devs are just as confused ("what does the star mean?").

A keyword would be less esoteric, and much easier to Google for. So extending generator support to arrow functions gives us a chance to tackle both with one proposal. 😃

jridgewell avatar Oct 21 '19 20:10 jridgewell

I vote for keyword gen and *=>.

rumkin avatar Oct 21 '19 21:10 rumkin

@ljharb

Adding a new gen is interesting, but there's no value imo in abbreviating - why not generator? (it's not func or async fn etc)

There is the answer in your question: as async is a shortening of asynchronous, thus gen could stand for generator.

rumkin avatar Oct 21 '19 21:10 rumkin

I vote for

const myGen = async gen () => yield await sth()
// or
async gen function myGen () { yield await sth() }

It I think it is more writeable readable than *:

  • readable because you will not need to keep arbitrary special characters in his mental memory.
  • writeable becauses async is the adjective and gen is the noun in our expression if read as a sentence. Like this we are closer to english grammar.

Thoughts on oother programming languages

  • Haskell - it is very hard to read for beginners as sigils are used to create a DSL for working algebraic data types. It is hard to google for the meaning of sigils. Elm has very limited use of sigils and is much more readable documentation is more retrievable IMO
  • PHP makes extensive use of $. I find it just makes variable less legible as you need too visually segment the sigil and the variable name.

janwirth avatar Oct 21 '19 22:10 janwirth

@rumkin async is a well-known abbreviation for asynchronous, and is effectively a word on its own; gen is not.

ljharb avatar Oct 21 '19 22:10 ljharb

@ljharb - I agree. I also think it is easier to type and thus less useful to abbreviate.

Researches found that "Shorter identifier names take longer to comprehend" https://link.springer.com/article/10.1007%2Fs10664-018-9621-x

janwirth avatar Oct 21 '19 22:10 janwirth

If a keyword is introduced for the arrow case, does it necessarily mean also introducing it for the ‘longhand’ case?

bathos avatar Oct 21 '19 22:10 bathos

For consistency, I would hope so.

ljharb avatar Oct 21 '19 22:10 ljharb

If we look for inspiration in python, we notice that they have no keyword to declare a generator - it is implied by yield

janwirth avatar Oct 21 '19 22:10 janwirth

https://link.springer.com/article/10.1007%2Fs10664-018-9621-x

@FranzSkuffka, I think this work isn't relevant for well-known language syntax. It's about new identifiers. Such identifiers are always located in brain's short memory and meaningful names help our brain to build an abstract model faster.

rumkin avatar Oct 21 '19 22:10 rumkin

Added generator keyword as a possible solution to the README.md and to the first message of this issue

chicoxyzzy avatar Oct 22 '19 08:10 chicoxyzzy

How about const *asd = () => {}?

inoyakaigor avatar Oct 23 '19 14:10 inoyakaigor

That’s between a const keyword and a const binding identifier. The arrow function is the part in the initializer, after the equals sign. These expressions can appear in many places, not just in const declarations.

bathos avatar Oct 23 '19 15:10 bathos

Is there any ASI hazard to *() => {} other than a lonely *() => {} expression statement? If not I don't really think it's compelling to discount that.

Jamesernator avatar Oct 24 '19 01:10 Jamesernator

hi! why not *>? like (x, y, z) *> { … } and async (x, y, z) *> { … }

constb avatar Oct 24 '19 04:10 constb

Functions have gone from function foo(bar) {} to const foo = (bar) => {}.

Hence it feels like function* generator(i) {} would be const* generator = (i) => {} but this clearly opens up a can of worms (like people typing const* a = 1 and would annoy people from other languages familiar with pointers. So ignoring that, *() => {} makes the most sense intuitively.

jashsayani avatar Nov 14 '19 01:11 jashsayani

@Jamesernator

IIUC that’s the only case, yeah. The following would parse as a MultiplicativeExpression with an invalid right hand side:

foo
* () => {};

The asterisk would match MultiplicativeOperator and the parentheses would match CoverParenthesizedExpressionAndArrowParameterList. If the covered part refined successfully to ParenthesizedExpression (e.g. * (a)), then failure would occur at =>; otherwise it would occur at the parens themselves. Either way, no arrow function.

That said, I wouldn’t have called this a hazard: it would throw a SyntaxError up front. It’s not a ‘trap’ like

foo
[bar]

The extent of the ‘hazard’ is just that a semicolon is necessary. The absence of one poses no risk of producing code that evaluates at all, much less with a different meaning from what was intended. (AFAICT)

bathos avatar Dec 10 '19 14:12 bathos

Does the keyword option have to be a variant on the word generator?

When teaching them, the word "Generators" has caused a lot of confusion in how they relate to "Iterators". So I started referring to them as "Iterator Functions" and that seemed to help people understand their relationship.

So I think iterator could be a good alternative choice for a keyword.

But even better than that: "Iter" is already a well-established abbreviation for "Iterator" so the keyword iter could work:

iter function fn() {...}
async iter function fn() {...}

jamiebuilds avatar Jan 02 '20 23:01 jamiebuilds

I would think that making the keyword for defining generator functions iter would increase, not decrease, confusion regarding the subset-superset relationship between generators and iterators, no?

bathos avatar Jan 03 '20 02:01 bathos

Maybe, but as it stands right now the relationship isn't seen by many. Anecdotally, I've talked to developers who describe generators as "pausable functions" (not sure where this comes from) or "how async functions are implemented under the hood" (which I think comes from transpilers), and I've seen them use the iterator protocol directly with while loops and calling .next() instead of using for..of. I suspect generators are underutilized (not that they are something every developer would use daily) for this reason.

jamiebuilds avatar Jan 03 '20 18:01 jamiebuilds

A generator is a pauseable (synchronous) function. It also produces an iterator (but isn’t one). They are (sadly) the dominant implementation detail for transpiler output of async/await; i think that’s indeed where that one comes from.

given that the syntax is async function but it returns a Promise, the keyword seems to suggest what it is and not what it produces. Similarly, I’d expect a generator keyword not to be about the iterator it produces, but about what it is (a value generator).

ljharb avatar Jan 03 '20 18:01 ljharb

I think I agree with the gist of the previous comment, but would point out that a ‘generator function’ produces a generator and that a generator is a specific kind of iterator. The function itself isn’t an iterator or a generator, at least not as the spec defines those terms.

bathos avatar Jan 03 '20 20:01 bathos