Adding support for generators (yield keyword) #3078

Closed
wants to merge 4 commits into
from

Projects

None yet
@almost
almost commented Jul 21, 2013

Generators have landed in V8 and Node.JS and they're pretty useful. I think CoffeeScript should have support.

I've hacked in basic support, I'm sure more work is needed but I thought it would be useful to start the discussion.

I'm interested in what the CoffeeScript policy is for features not supported by all JS engines, should they be left out or would it be useful to have a fallback implementation (based on a CPT and a Trampoline?) as a command line option? Neither seem satisfactory but then neither does restricting CoffeeScript user's use of exciting new JavaScript features :)

@almost
almost commented Jul 21, 2013

I wrote a blog post about it with an example ported to CoffeeScript: http://almostobsolete.net/coffeescript-generators.html

@vendethiel
Collaborator

I'm interested in what the CoffeeScript policy is for features not supported by all JS engines

I think it's "later".

@purge
purge commented Jul 21, 2013

Has there been a discussion about this policy? I think when features like this drop in node stable, I think it's up to the developer to make an informed choice as to whether they can use them (with a suitable warning in the documentation).

There are engine specific features that can already be used from Coffeescript so this wouldn't be without precedent (though i'm aware experimental keyword feature support is a rabbit hole one might not want to go down).

@maccman
maccman commented Jul 21, 2013

This is going to be super useful in Node applications (indeed Harmony already has yield support). Looking forward to seeing it part of CS.

@michaelficarra
Collaborator

@purge: See #3073 and #2638

@almost
almost commented Jul 21, 2013

I also added support for the yieldfrom statement, it's like ES6's "yield*" and lets you combine generators in a more natural way:


range = (start, stop) ->
  yield i for i in [start...stop]

addBeginAndEnd = (generator) ->
  yield "BEGIN"
  yieldfrom generator
  yield "END"

g = addBeginAndEnd(range(0,3))()
// G will now yield BEGIN, 0, 1, 2, END

I haven't included it in this Pull Request yet but the commit is here:

almost@67f2347

Should I include it in this PR? Make another PR? Or should I just leave it out for now?

EDIT: Now included in the PR (along with the iteration syntax)

@jashkenas
Owner

@almost Feel free to include it in this PR if you dig it. In general, try to make a PR as nice and complete as you're able to.

@almost
almost commented Jul 21, 2013

Ok, I've included in with the rest. I'm going to see about adding a for...from syntax now for iteration now..

@almost
almost commented Jul 21, 2013

Ok, so apparently "from" is used quite a lot in existing code (including the CS code). Any ideas for another keyword to use?

@vendethiel vendethiel commented on the diff Jul 21, 2013
src/nodes.coffee
+
+ compileNode: (o) ->
+ "yield#{[" #{@expression.compile o, LEVEL_PAREN}" if @expression]}"
+
+exports.YieldFrom = class YieldFrom extends Base
+ constructor: (expr) ->
+ @expression = expr if expr and not expr.unwrap().isUndefined
+
+ children: ['expression']
+
+ compileNode: (o) ->
+ sendvar = o.scope.freeVariable 'send'
+ resvar = o.scope.freeVariable 'result'
+ refvar = o.scope.freeVariable 'ref'
+
+ code = "var #{refvar} = #{[" #{@expression.compile o, LEVEL_PAREN}" if @expression]};\n"
@vendethiel
vendethiel Jul 21, 2013 Collaborator

I think we can do better than that. We should have methods for temp vars & we can generate AST nodes

@almost
almost commented Jul 21, 2013

I've added a iteration syntax for generators (or any iterator that has a .next() that works the same way as a generator). I did initially want to use "for x from y" but found that "from" is popular variable, I could hack the parser so that you can use it as a variable anywhere where it wouldn't be ambiguous but I think that's probably a bad idea.

The syntax I've currently gone for (suggested by Richard Dallaway on Twitter) is "for x outof y" but it would be easy enough to change if anyone can think of a better word.


range = (start, stop) ->
  yield i for i in [start...stop]

for x outof range(0,10)
  console.log(x) 

(which will print the numbers 0 to 9)

The code for this one could probably do with refactoring (the other bits as well, but this one more so) but I'll hold of on that until it's been discussed whether it would actually be wanted.

@almost
almost commented Jul 21, 2013

What about for x <- y?

@vendethiel
Collaborator

I think it'd be syntictally ambiguous with the proposed syntax for backcalls.

@almost
almost commented Jul 22, 2013

Hmm yeah, that's true.

Another option is to not have an explicit syntax for it but leave it to a utility function. Maybe _'s _.map and _.each could be extended to recognise iterators or new versions of those functions could be added...

@akloster

It should be possible to emulate generators without breaking compatibility, by emulating the iterator/generator protocol like an ES6 to ES3 compiler would do. That way, a generator function would be compiled to a function which returns an iterator, and the iterator would be an object with a next method etc.

Reframing a generator function into that format can get tricky. Traceur for example doesn't support generators currently. Continuum (https://github.com/Benvie/continuum) claims to support this.

@almost
almost commented Jul 22, 2013

@akloster While it is possible it really involves mangling the code, you first have to do a continuation passing transform where you split the function up into multiple bits (broken wherever there's a yield statement). CoffeeScript currently relies heavily on JavaScript's underlying semantics (that's one of it's major selling points) and it would lose a lot of the benefits of this. You'd have to reimplement loops, try...catch and other control flows in a way that was compatible with the new style.

@akloster

@almost Yes, I've thought about this tradeoff. I think it might be worth the trouble, especially with sourcemaps as a help, and this only affects generator functions, but it's not up to me to decide this. Another option is to offer an es6 mode in coffeescript and then use an es6 to es3 compiler downstream for those who need it.

@almost
almost commented Jul 22, 2013

I like that as an option (having an es6 to es3 compiler), it reduces duplication of work. Looks like traceur does now support generators (at the cost of producing a massive state-machine instead of normal JS code):

http://bit.ly/119f7sr

@almost
almost commented Jul 22, 2013

Another question, should CoffeeScript have generator expressions (which right now would compile to generator functions) and if so what would the syntax look like?

@bjmiller
bjmiller commented Aug 5, 2013

I would expect ->* to result in an ordinary ES6 generator.

@almost
almost commented Aug 5, 2013

@bjmiller The approach I've taken here is to detect generators by the presence of the yield keyword (as Python does). I think this fits with CoffeeScript's approach a little better than having a special function syntax but this is definitely something that could be argued either way.

@bgw
bgw commented Aug 7, 2013

@almost, I don't have much experience to back this, but I feel like your approach of automatic detection would be better and more coffeescript-y.

@bjmiller
bjmiller commented Aug 7, 2013

After thinking about it for a while, I think that I would prefer it to be more explicit. The idea being that it should be easy to see looking at the code and have the difference jump out at you as you're reading it. You're right about one thing, though. There are definitely good arguments for either side.

@ricardobeat
Contributor

is there any interest in having a shortcut for yield? like so (in lieu of backcalls)

app.run (request) ->
  body = <- request.parseContent()
  <- sleep(200)
  JSON.stringify(body)
@bgw
bgw commented Aug 9, 2013

@ricardobeat, I think the shortcut syntaxes you present trade readability for characters. It seems like something livescript would do, but I don't think it would be a good fit for a syntactically minimal language like coffeescript.

@explicitcall

Is this PR available anywhere rebased on top of 1.6.3? Pulling @jashkenas/coffee-script/master fails with conflicts :(

@almost
almost commented Aug 16, 2013

@explicitcall I'll do that this weekend

@almost
almost commented Aug 28, 2013

Sorry, "this weekend" was obviously massively optimistic. I made a start but didn't finish it, the code I'm modifying has changed a lot in CoffeeScript (for the better I think!) so there's a bit more work to do (especially for the iteration syntax which was pretty messy to start with).

@jcw
jcw commented Sep 5, 2013

+1 for a way to explore generators from CS >= 1.6.3, or a somewhat stable branch or fork

@Starefossen

What is the current status of this PR?

@joyrexus

@almost wrote:

should CoffeeScript have generator expressions
(which right now would compile to generator functions)
and if so what would the syntax look like?

Having genexps in CS would indeed be awesome!

As far as syntax, would it be out of the question to have an asterisk as a prefix to what would otherwise be a list comprehension?

*(x for x in [1..10])
@alubbe
Contributor
alubbe commented Nov 15, 2013

Hey guys,

thanks to libraries such as suspend and especially bluebird using generators is becoming more and more common. It plays nice with promises and performs better than async's waterfalling (faster & less RAM needed). I have been using in production with no hick ups but it is a shame that it will not work with coffeescript.

So what's the status? Thomas, can I give you a hand with finishing the code? Nami, are you still generally interested in this becoming part of CoffeeScript?

@vendethiel
Collaborator

I think it should make it to the core, sooner or later. (I'm not a coffee user so I'm not really "interested", I just think it's good for the language to have)

@almost
almost commented Nov 15, 2013

I'm up for porting the patch to the latest version of coffee-script is there's a likelihood of it being merged. I don't think I have the energy to maintain a fork so there's not much point if that's not going to happen.

I think probably the "yield" and "yieldfrom" keywords and the generator functions would be good to have but I'm not so sure the interaction syntax makes sense in coffeescript (since that can easily be handled by a library)

@alubbe
Contributor
alubbe commented Nov 15, 2013

Ok so I have gone ahead and pieced together a working version from almost and some of the changes the team has introduced in the last few months -> see #3240

I think it would be best not to have a test for this feature, so that the test suite does not break outside of node 0.11.2+

At the same time, the new functionality is clean, simple and allows node.js developers like me to code very fast and memory-efficient callback solutions (see bluebird)

What do you guys think?

@alubbe
Contributor
alubbe commented Nov 15, 2013

Just to illustrate the point of why this is awesome, using bluebird and this pull request you can write the below code. Super simple to read, and supports the tried & true try, catch, finally for error handling.

delay = (ms) ->
  new Promise (f) ->
    setTimeout f, ms

Promise.spawn ->
  yield delay 500
  console.log "Well, "
  yield delay 500
  console.log "isn't this"
  yield delay 500
  console.log "awesome!"
.then(morePromises)
@marchaefner
Collaborator

Yes, coffee-script ought to have generators in the foreseeable future, but:

  1. I'm not a big fan of the automagic function-to-generator conversion. (My vote is for an explicit ->*)

    • It should be easy to write an empty generator and not -> return; yield null (Though this could be also resolved by adding something like the neat looking genexps syntax suggested by @joyrexus.)
    • I think this a clear cut case of "Explicit is better than implicit": When reading code I'd like to know upfront what I'm looking at -- and a coroutine is just not a "normal" function (e.g. implicit returns make no sense in generators).
    • Maybe I'm missing something, but I don't see how this could be "argued either way" (I find because-we-can and one-less-character very unconvincing.)
  2. We need to have a discussion about compile targets first, as indicated by the somewhat gruesome "..it would be best not to have a test.." Furthermore it would be extremely odd to have the __indexOf helper next to a largely unsupported yield.

    (Quick sketch: A coffee --next that does just the usual compilation plus generators, less helpers and maybe even named closures and "use strict" by default.)

@jashkenas
Owner

I'm not a big fan of the automagic function-to-generator conversion.

Very interesting. You listed a few reasons, but I'm not sure that I quite buy it...

If generators are such a different thing than functions, then why on earth do you type function to create them in JavaScript? Why not a generator(){ ... } keyword?

Why do you yield from a normal block in Ruby?

Why is a generator in Python simply a normal function that contains a yield? Ditto PHP.

@xixixao
Contributor
xixixao commented Nov 16, 2013

Facebook's HPHP has naming convention for generator functions, but the compiler was doing the conversion automagically as in Javascript et al (even though yield result() stood for return).

@alubbe
Contributor
alubbe commented Nov 16, 2013

here are my two cents to Marc's comments:

  • You will almost always know what you are looking at. Outside of control-flow libraries, all production code will use said control-flow libraries on generators, like in my example. And starting with Promise.spawn is quite expressive, don't you think? And when you are programming a control-flow library, you are expecting generators anyways.
  • Empty generators can simply be -> yield 0, which compiles to function*(){return yield 0}. This generator, once finished, does not return 0, but instead undefined - therefore doing exactly want you would want.
  • Needing to code empty generators seems like a rare edge case to me. Should it become a problem, '->*' could be added as an alternative, but I would not start off this way
  • I generally think that coffee --next this is a good idea, but generators are so important for node.js development that they should be added now. The second something like --next becomes a reality, I will code you a test, but let's face it, the code is simple enough that you don't need to worry about breaking anything.
  • Also, this implementation of generators breaks absolutely no existing code nor will it lead to issues with anyone using coffeescript outside of node.

In conclusion, I would learn from Ruby and Python and turn functions into generators as soon as yield is spotted. The sooner we can agree on a standard, the sooner we can reap the benefits from code that runs faster than async and Q/when/all-the-others.

@vendethiel
Collaborator

if we're worried about yield tests breaking other stuff we can probably get to output yield (a);

@alubbe
Contributor
alubbe commented Nov 16, 2013

no that does not work because you have to wrap it in function*(){ ... } and the * causes a runtime error in the parser

@vendethiel
Collaborator

Ahah; true (problem wouldn't exist if function were implicits in JS too :p)

@alubbe
Contributor
alubbe commented Nov 16, 2013

well that's what CoffeeScript and this PR are about :)

@probablycorey

I agree with @marchaefner about making generator syntax explicit.

Traditional JavaScript (and CoffeeScript) has always executed code as a unthreaded imperative program. It is expected that a function is completely executed after it is called. Any side effects from the call will occur on the next run of the event loop. Generators change that long standing behavior, because of this I think the syntax for generator creation should be different and obvious.

I'm not sure why ecmascript decided to use function* instead of generator. But the fact that they choose to use function* instead of function is telling. They want people to know a generator is different than a function.

Ruby's yield is very different from JavaScript's. In Ruby yield calls the block given as the final argument of a method. This makes it easy to spot methods that contains yields, just look for a method that takes a block as the final argument. Ruby has a separate Generator class that behaves like JavaScript generators.

I can't speak to Python or PHP because I've never used their generators.

What I do agree with is function* and ->* are ugly and unappealing. Alternatives like ->>, -->, <->, or even -*> look better to me.

@epidemian
Contributor

I'm not sure why ecmascript decided to use function* instead of generator.

Because adding new keywords is a very high cost for languages that strive to be backward-compatible. New runtimes need to be able to run old code seamlessly.

Adding a new keyword is a high cost for any language though, as the list of all language keywords is something that programmers must (or should) know to avoid syntax errors when choosing their variable names.

In the case of Coffee, i don't think there's much problem because the notation for functions doesn't use a keyword, and the proposed notations for generators are symbolic too.

@alubbe
Contributor
alubbe commented Nov 17, 2013

@probablycorey I do not think that ECMA should be our role model here, since CoffeeScript is really all about improving their works.

If you want to learn more about the Python generators, skim through this: http://sdiehl.github.io/coroutine-tutorial/#generators

As you will see here, normal functions and generators come with the same syntax, making it very simple and intuitive to use.

I think that this PR can be added without breaking anything, and if for whatever reason we later notice that we really should add an explicit declaration like ->* or -->, then that can always be added as an alternative notion to this automagic conversion.

@jashkenas what's your take?

@almost
almost commented Nov 17, 2013

I do think Python is a good language to look at for this, Python generators are pretty much the same as the new JS generators. Personally I've never had an instance of confusion due to not realising that a function is a generator despite using them quite frequently. If that's the normal experience then I think it's a good reason to do the automatic conversion.

That being said I don't think it matters hugely either way and it's mostly a matter of taste.

@marchaefner
Collaborator

@jashkenas:

Why not a generator(){ ... } keyword?

What @probablycorey said...

(Can't speak to PHP either, due to a deep-seated disdain for its design.)

Python is a good point. With the current ES6 draft JavaScript would have a very similar iterator protocol and the for .. of loop for everything iterable. (Though right now V8 has only generator functions.)

But CoffeeScript differs significantly from Python (and JavaScript) because of implicit returns. The compilation for -> yield x for x in l would just be absurd unless we dropped the implicit return, making generators even more special. (And I'd rather have an implicit yield like ->* x for x in l)

And (highly subjective): To me -> means "mapping" (as in value-in -> value-out), but within a generator multiple values are returned and received (as in coroutine). Implicit generators feel a bit like functions that become class-bodies if they contain a constructor: ->

@alubbe:

Empty generators can simply be -> yield 0, which compiles to function*(){return yield 0}

IMO a good example why generators should not have implicit returns. Contrary to your description, it does first yield 0 and then returns.

Needing to code empty generators seems like a rare edge case to me.

This might seem so, but this is also a question of language design. Empty generators should be as straight-forward as empty functions, strings, arrays, objects, ...

@nfour
nfour commented Nov 20, 2013

+1 for ->*

Making it explicit should make code far more readable, as not knowing if a function is indeed a generator until you come across a yield keyword is just cumbersome, and too far away from ES6. Additionally, ->* is the natural expected translation of function*(){}.

@ctartist621

@almost So, trying to run the generator.coffee example using the bin/coffee included in your fork. I get the following error:

vagrant@precise64:/vagrant$ ./node_modules/coffee-script/bin/coffee generator.coffee

/vagrant/generator.coffee:4
  generator = function*() {
                      ^
SyntaxError: Unexpected token *
    at exports.runInThisContext (vm.js:69:16)
    at Module._compile (module.js:432:25)
    at Object.exports.run (/vagrant/node_modules/coffee-script/lib/coffee-script/coffee-script.js:87:25)
    at compileScript (/vagrant/node_modules/coffee-script/lib/coffee-script/command.js:177:29)
    at /vagrant/node_modules/coffee-script/lib/coffee-script/command.js:152:18
    at fs.js:253:14
    at Object.oncomplete (fs.js:96:15)
vagrant@precise64:/vagrant$ node -v
v0.11.8
vagrant@precise64:/vagrant$ ./node_modules/coffee-script/bin/coffee --harmony-generators generator.coffee

/vagrant/node_modules/coffee-script/lib/coffee-script/optparse.js:50
            throw new Error("unrecognized option: " + arg);
                  ^
Error: unrecognized option: --harmony-generators
    at OptionParser.exports.OptionParser.OptionParser.parse (/vagrant/node_modules/coffee-script/lib/coffee-script/optparse.js:50:19)
    at parseOptions (/vagrant/node_modules/coffee-script/lib/coffee-script/command.js:463:29)
    at Object.exports.run (/vagrant/node_modules/coffee-script/lib/coffee-script/command.js:53:5)
    at Object.<anonymous> (/vagrant/node_modules/coffee-script/bin/coffee:7:41)
    at Module._compile (module.js:449:26)
    at Object.Module._extensions..js (module.js:467:10)
    at Module.load (module.js:349:32)
    at Function.Module._load (module.js:305:12)
    at Function.Module.runMain (module.js:490:10)
    at startup (node.js:121:16)

Additionally, it does not appear the send method is being properly handled

vagrant@precise64:/vagrant$ node --harmony-generators generator.js
{ value: 'hello', done: false }
{ value: 'to', done: false }

/vagrant/generator.js:18
  console.log(g.send("you"));
                ^
TypeError: Object #<GeneratorFunctionPrototype> has no method 'send'
    at Object.<anonymous> (/vagrant/generator.js:18:17)
    at Object.<anonymous> (/vagrant/generator.js:41:4)
    at Module._compile (module.js:449:26)
    at Object.Module._extensions..js (module.js:467:10)
    at Module.load (module.js:349:32)
    at Function.Module._load (module.js:305:12)
    at Function.Module.runMain (module.js:490:10)
    at startup (node.js:121:16)
    at node.js:757:3

Where am I going off the reservation?

@artoale
artoale commented Dec 13, 2013

The send method is no longer part of the standard. it has been replaced by .next(argument)

@slezica
slezica commented Dec 19, 2013

I think ~> looks better than ->* (three consecutive symbols?). It also makes sense given that => is an altered form of ->.

@MadRabbit

I'd vote for ~> makes perfect sense to me /cc @slezica

@xixixao
Contributor
xixixao commented Dec 19, 2013

@slezica @MadRabbit ~> doesn't support bound generators (=> equivalent).

@MadRabbit

@xixixao here you go! ≈> :)

but yeah, you've got a good point, although ~> kinda makes sense to me visually. how about ~=> for the bound generators?

@xixixao
Contributor
xixixao commented Dec 20, 2013

@MadRabbit Inconsistent. Why does tilda make sense? I think it makes as much sense as all the other proposals - they are arbitrary, none of them conveys yield, all of them convey special. (but, tilda has the disadvantage of not being extensible to binding).

@gunta
gunta commented Dec 20, 2013

Ok, now it's time.
The library that needs generators has come from the express guys.

http://koajs.com/

@mklement0
Contributor

+1 for ->* / =>*: it parallels the underlying JavaScript syntax - suffixing the the existing function keyword - which, in the case of CS, has the added advantage of directly incorporating the familiar operators for unbound and bound functions and thus making that distinction readily perceptible in the generator case also.

@xixixao
Contributor
xixixao commented Dec 20, 2013

In javascript, the order is function keyword * [name] (list of parametrs) body of the function
Since coffee doesn't follow the JavaScript syntax, to me *-> ->* are equally intuitive (and ugly).

Edit: fixed the order, the argument still holds

@nfour
nfour commented Dec 20, 2013

I think something just needs to be decided on already and implemented, and 1.7.0 needs to be released with yield and the new chaining syntax asap.

@mklement0
Contributor

@xixixao: It's actually function* ..., e.g.: function* fibonacci() ... - see http://wiki.ecmascript.org/doku.php?id=harmony:generators

Agreed that CS doesn't have to mimic JS, but if the same symbol (*) IS used, it should go in the analogous place; otherwise you're inviting confusion.

@alubbe
Contributor
alubbe commented Dec 20, 2013

@gunta @mklement0 @MadRabbit @slezica come over to 'vote' for your syntax at this more up to date PR #3240

@mklement0
Contributor

Thanks, @alubbe.

@mikesmullin

what's the status here? still at the cryptic syntax argument stage? how about ()=*> the diamond dick?

btw es6 featuresets are here. we need to get support for let const yield function* and the real for of added or just get coffeescript out of the way and let us inject keywords wherever we want using backticks or macros.

we can start supporting the features everybody agrees on today. we don't need to wait for perfection. we're losing mindshare. this should be implemented by December 2014.

until then there is BlackCoffee, or...shudder....JavaScript.

@xixixao
Contributor
xixixao commented Jun 15, 2014

I wholeheartedly agree we need to get these features implemented (the ones which are not provided by CS already). I think there should be a version of CS which includes the ES6 features but can compile to JS runnable in old IE (current main branch of CS must). Whether this will be in the main repo or in a fork (CS nightly) will largely depend on Jeremy. The main problem is time. Whoever is willing to implement these features will surely be supported by the community (but I don't see many people lining up).

@xixixao
Contributor
xixixao commented Jun 15, 2014

To be clear, the ones I know of we need are export, yield, for..of. Any others?

@mikesmullin

compare the featureset here with green labels "Consensus":
https://developer.mozilla.org/en-US/docs/Web/JavaScript/ECMAScript_6_support_in_Mozilla

to your build of node.js here:
node --v8-options | grep harmony

note that the stable 0.10.x branch has fewer features than the latest nightly 0.11.x branch, as discussed here:
http://stackoverflow.com/questions/17379277/destructuring-in-node-js

there is also a nice one-pager here:
http://espadrine.github.io/New-In-A-Spec/es6/

@xixixao
Contributor
xixixao commented Jul 1, 2014

Looking over the one-pager I don't see any other features CS doesn't have already (+ classes should be compatible).

@mkoryak
mkoryak commented Jul 2, 2014

I think the burden should be put on the developers to make sure that they dont write coffeescript that will compile down to es6 if they want that javascript to run browsers that dont support it.
It has always been this way with all other javascript features... want to use the File API? Go ahead, but just make sure you don't deploy that code to <IE9

Maybe implement this feature to only work when passing --harmony-generators to the coffeescript compiler?

Personally I love using coffeescript for nodejs development and I am sad that crappy browser support has found a way to make my life more difficult even there.

@iki
iki commented Aug 1, 2014
@xpepermint

status on this feature?

@nicohvi
nicohvi commented Aug 14, 2014

+1

@DylanPiercey
Contributor

+1

@skrat
skrat commented Aug 15, 2014

@jashkenas There wasn't any feedback from the devs for over 7 months on this issue. It certainly would be nice and warmly welcomed if someone could give one.

@darthmaim

@skrat check this more active pull request with feedback from the devs: #3240

@GabrielRatener
Contributor

Shouldn't support for async/await be added as sugar ontop of yield with promises. This would really make async programming easy. Any ideas?

I like Dart's draft specification which also includes support for async gennerators and a special for loop to iterate asynchronously through streams produced by such gennerators.

Dart's spec: "https://www.dartlang.org/docs/spec/Asyncdraft-TC52.pdf"

@Artazor
Contributor
Artazor commented Oct 8, 2014

@GabrielRatener I think, syntactic sugar is not necessary. For example, with so library the example from so could be rewritten as follows:

so = require 'so'
fs = require 'then-fs'

readJSON = so (path) ->* 
    JSON.parse yield fs.readFile path, 'utf8'

main = so ->*
    a = yield readJSON 'a.json'
    b = yield readJSON 'b.json'
    console.log {a,b}

main().catch (e) ->*
    console.log e.stack ? e.message ? e 

Note that I'm against inferring generators from yield usage. It should be explicit other syntax. I think ->* and =>* are the best choices.

@vendethiel vendethiel closed this Oct 8, 2014
@vendethiel
Collaborator

This has been added already :)

@Artazor
Contributor
Artazor commented Oct 8, 2014

Oh! Great news! Can you point to any docs? Did generators landed in 1.8.0?

@vendethiel
Collaborator

#3240 for now!

@Artazor
Contributor
Artazor commented Oct 8, 2014

Hm... I see.

And I'm disappointed.

Decision to infer generatorness from yield yields to orthogonality violation and will be a "Bad Part" of the CoffeScript itself, that will be compensated with unnecessary dirty hacks in third-party libraries (that is against the spirit of CoffeeScript).

For example lets consider so — the tiny and very generic co-routine library. The following js snippet is semantically homogenous and correct:

var so = require('so');
var fs = require('then-fs');

var case1 = so(function*(){
    var value = 'Immediate';
    return value;
});

var case2 = so(function*(){
    var value = yield fs.readFile('somefile.txt', 'utf8');
    return value;
});

var combined = so(function*(){
    var a = yield case1();
    var b = yield case2();
    return {a:a, b:b};
});

var main = so(function*(){
    console.log(yield combined());
});

main().done();

This is achieved because so has signature described as follows:

type PromiseGen a = a -> [Promise] -- consider generators that yields Promises
so::PromiseGen a -> (a -> Promise) -- convert generator to the function that returns a Promise
-- Here monadic nature of Promise is omitted for simplicity

In contrast, the following CoffeScript counterpart will fail without changing the semantics of so()

so = require 'so'
fs = require 'then-fs'

case1 = so -> 'Immediate' # fails since argument is not a GeneratorFunction
case2 = so -> yield fs.readFile 'somefile.txt', 'utf8'

combined = so -> 
    a = yield case1()
    b = yield case2()
    {a, b}

main = so -> console.log yield combined()

main().done();

I understand that this is too late to argue, but who knows. It would be nice to hear some words from @jashkenas and @ForbesLindesay, because I believe that co, so or similar generator transformer will become a mainstream soon, and chosen principle effectively spoils the implementation (the unnecessary check for argument's type to be a GeneratorFunction).

Generators without yield are strange in the case of generating sequences (i.e. using generators as intended), but are pretty valid corner case for asynchronous flow control based on generators.

I think so.

see Artazor/so#2

@jashkenas
Owner

@Artazor — You didn't read closely enough.

case1 = so -> yield return 'Immediate'

Voila.

@Artazor
Contributor
Artazor commented Oct 8, 2014

@jashkenas

That matters, thank you very much!
Btw, interesting solution!

Waiting for yield support on npm.

@lydell
Collaborator
lydell commented Oct 8, 2014

Waiting for yield support on npm.

npm install jashkenas/coffeescript

@GabrielRatener
Contributor

yield will be limited in use until we figure out how to handle expression closures.

See bug #3665

@micchyboy

Guys I need help. I'm following a book (Node.js the Right Way) and there's a part where generators are used, but whenever I run the file with (node --harmony countdown.js) it stil throws a (SyntaxError: Unexpected token *). Any suggestions on this? My version is v0.10.32 :(

@xpepermint

@micchyboy you are using the wrong version of node :). Use 0.11.9+.

@micchyboy

@xpepermint Yes thats the solution :). Thank you so much

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment