proposal-generator-arrow-functions
proposal-generator-arrow-functions copied to clipboard
Syntax
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
@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
.
There is an ASI hazard here
foo = gen ()
=> {};
That's a syntax error currently, right? I don't understand how it would cause a new ASI hazard.
I can imagine only two variants: 1.
foo = (*)(arg) => {};
foo = (*(arg)) => {};
second could be simplified for one / zero arguments as:
foo1 = (*arg) => {};
foo0 = (*) => {};
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)
More radical solution is using different brackets like []
instead ()
:
foo1 = [] => {}
foo2 = [[first, second]] => {} // with array destruction
foo3 = [a, b] => {}
TBH, any of *=>
, =>*
or =*>
look totally fine and clear to me.
*=>
seems least surprising to me, with =*>
seeming least obvious (it helps to still have ‘the arrow’ for recognition).
Adding a new
gen
is interesting, but there's no value imo in abbreviating - why notgenerator
?
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. 😃
I vote for keyword gen
and *=>
.
@ljharb
Adding a new
gen
is interesting, but there's no value imo in abbreviating - why notgenerator
? (it's notfunc
orasync fn
etc)
There is the answer in your question: as async
is a shortening of asynchronous
, thus gen
could stand for generator
.
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 andgen
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.
@rumkin async
is a well-known abbreviation for asynchronous, and is effectively a word on its own; gen
is not.
@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
If a keyword is introduced for the arrow case, does it necessarily mean also introducing it for the ‘longhand’ case?
For consistency, I would hope so.
If we look for inspiration in python, we notice that they have no keyword to declare a generator - it is implied by yield
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.
Added generator
keyword as a possible solution to the README.md and to the first message of this issue
How about const *asd = () => {}
?
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.
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.
hi! why not *>
? like (x, y, z) *> { … }
and async (x, y, z) *> { … }
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.
@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)
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() {...}
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?
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.
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).
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.