Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python-like generators and generator expressions #983

Closed
alexbft opened this issue Dec 27, 2010 · 12 comments
Closed

Python-like generators and generator expressions #983

alexbft opened this issue Dec 27, 2010 · 12 comments

Comments

@alexbft
Copy link

alexbft commented Dec 27, 2010

You already have list comprehensions, why not take another step and add generator expressions to the language? Yes, I know, there is a lack of yield statement. But look, it is possible to implement generators without real continuations.

Profits: infinite sequences, lazy evaluation, more functional programming :)

Here's the code:

//
// range = (a, b) ->
//     for (i = a; i < b; ++i)
//         yield i
//
// a = (x * 2 for x in range(0, 10) if x % 2 != 0)
//
// test = []
// for (i in a)
//     test.push(i)
// alert(test)
//

var __ok = {};

function range(a, b)
{
    return function(yield)
    {
        for (var i = a; i < b; ++i)
            if (yield(i) !== __ok) return;
    }
}

function a(yield)
{
    __for(range(0, 10), function(x)
    {
        if (x % 2 != 0)
        {
            if (yield(x * 2) !== __ok) return;
        }
        return __ok;
    })
}

var test = [];
__for(a, function(i)
{
    while (true)
    {
        test.push(i);
        return __ok;
    }
})
alert(test);

//
// count = (x) ->
//     tmp = x
//     while true
//         yield tmp
//         tmp += 1
//
// test = []
// for (i in count(0))
//     if (i >= 10) break;
//     test.push(i)
// alert(test)
//

function count(x)
{
    return function(yield)
    {
        var tmp = x;
        while (true)
        {
            if (yield(tmp) !== __ok) return;
            tmp += 1;
        }
    }
}

test = [];
__for(count(0), function(i)
{
    while (true)
    {
        if (i >= 10) break;
        test.push(i);
        return __ok;
    }
})
alert(test);

function __for(gen, body)
{
    if (typeof gen == "function")
    {
        gen(body);
    }
    else
    {
        //code for arrays & objects
    }
}
@odf
Copy link

odf commented Dec 28, 2010

Or, you could simply do something like this:
log = (s) -> console.log " >> #{s} <<"

suspend = (code) ->
  val = null
  ->
    if code
      val = code()
      code = null
    val

cons = (first, rest) ->
  first: -> first
  rest:  suspend rest
  toString: -> "cons(#{first}, #{rest})"

take = (seq, n) ->
  log "take(#{seq}, #{n})"
  if seq and n > 0
    cons seq.first(), -> take seq.rest(), n - 1
  else
    null

each = (seq, f) ->
  log "each{#{seq}, #{f})"
  if seq
    f seq.first()
    each seq.rest(), f

from = (n) ->
  log "from(#{n})"
  cons n, -> from n+1

sequence = take from(10), 5

each sequence, (n) -> console.log n
each sequence, (n) -> console.log n
each sequence, (n) -> console.log n

Lazy evaluation, infinite sequences, and more functional programming, all without adding any new language features. ;)

@alexbft
Copy link
Author

alexbft commented Dec 28, 2010

A good implementation of lazy sequences. But where's my generator expressions? :) I want to write stuff like
s = sum(x + y for x in 1..10 for y in (row.length for row in table))

@StanAngeloff
Copy link
Contributor

All of the examples can be written in CoffeeScript already:

# a = (x * 2 for x in range(0, 10) if x % 2 != 0)
a = (x * 2 for x in [0...10] when x % 2 isnt 0)

# s = sum(x + y for x in 1..10 for y in (row.length for row in table))
s = 0; s += sum(x + y) for x in [1..10] for y in [0...row.length] for row in table

No need to have lazy generators. If you really need them, consider async callbacks.

I'd be interested to see any examples where generators would be useful and the current syntax or continuations can't deal with the code in an elegant way.

@odf
Copy link

odf commented Dec 28, 2010

Well, I for one think it would be fantastic if Coffeescript's list comprehensions were lazy and worked with user-defined types. While we're at it, how about a pony?

@StanAngeloff
Copy link
Contributor

Nah, I like turtles.

@jashkenas
Copy link
Owner

Sorry, but I'm afraid you can't base CoffeeScript around generators and lazy evaluation, given that JavaScript eagerly evaluates everything (as it probably should). Making a change like this would make CoffeeScript libraries far more incompatible with JS libs than they need to be, and would slow down general iteration by an order of magnitude.

So, I'm afraid this particular ticket is a wontfix ... but that said, CoffeeScript's simple function literals are intended to make this sort of thing easy to accomplish cleanly in your own code base:

range start, end, (number) ->
  ...

@odf
Copy link

odf commented Dec 29, 2010

Yep, I've been working on a suite of functional data structures for several weeks now, and I can attest that it's a pleasure to do this in Coffeescript. Even plain Javascript would be far less clunky than Ruby (which I did my original experiments in), not to mention Python, but Coffeescript's function literals make it so much more concise and readable.

That said, if we could come up with a variant syntax for lazy comprehension so that 'regular' loops won't be affected, that would still be lovely. I'm not exactly holding my breath, but I'd very much appreciate if it happened.

@alexbft
Copy link
Author

alexbft commented Dec 29, 2010

@jashkenas
@odf
thanks anyway, you gave me some new ideas about functional programming in JS.

@odf
Copy link

odf commented Dec 29, 2010

Note that the implementation of each in my code example is not optimal, since Javascript does not do tail call optimization, and some implementations - or so I hear - use a pretty small stack at runtime. There are several possible approaches for fixing that: a traditional loop or a trampoline, for example. My Sequence class uses exactly one while loop, everything else is done via lazy evaluation or simulated tail calls.

@niklasl
Copy link

niklasl commented Feb 13, 2011

I agree with the conclusion that this specific proposal goes beyond JS a bit too much.

But Javascript 1.7 does have list and generator expressions, and the yield statement. See the Mozilla JS Guide on Array comprehensions and Iterators and Generators. (The syntax for these is directly based on Python's.)

So I think it would be good for CoffeeScript to somehow align with that, and (soonish) implement support for outputting JS using that syntax.

Given that CoffeScript's current list comprehensions look like the generator version of JS 1.7 (and writing JS1.7-style list expressions results in a comprehension nested in a list), this might call for something backwards-incompatible. Perhaps some kind of pragma/future mechanism would be usable (e.g. similar to the new JS "use strict" directive).

@OnesimusUnbound
Copy link

Well, you can simulate it, taking advantage of JS's closures

count = (start = 0, step = 1) ->
    start -= step
    -> start += step

countFrom10By2 = count 10, 2

countFrom10By2()
# => 10

countFrom10By2()
# => 12

countFrom10By2()
# => 14

of course, this is too simple.

Either way, if you wanted to use generator (yield in particular), then the back tick is your friend

count = (start = 0, step = 1) ->
    loop
        `yield start;`
        start += step

However, you'll be constrained in Mozilla's JS engines (SpiderMonkey and Rhino).

@showell
Copy link

showell commented Feb 25, 2012

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants