Recursion in generators?

# Axel Rauschmayer (13 years ago)

The following generator produces an iterator for nested arrays. Is this the best way to do recursion? Doesn’t seem terribly elegant.

function* iterTree(tree) {
    if (Array.isArray(tree)) {
        // inner node
        for(let i=0; i < tree.length; i++) {
            for(let elem of iterTree(tree[i])) {
                yield elem;
            }
        }
    } else {
        // leaf
        yield tree;
    }
}

Interaction:

$ let g = iterTree([[0, 1], 2]);
$ g.next()
0
$ g.next()
1
$ g.next()
2
$ g.next()
[object StopIteration]
# Erik Arvidsson (13 years ago)

You can use yield* here.

# Axel Rauschmayer (13 years ago)

Cool. Like this?

function* iterTree(tree) { if (Array.isArray(tree)) { // inner node for(let i=0; i < tree.length; i++) { yield* iterTree(tree[i]); } } else { // leaf yield tree; } }

# Erik Arvidsson (13 years ago)

On Tue, May 8, 2012 at 4:24 PM, Axel Rauschmayer <axel at rauschma.de> wrote:

Cool. Like this?

Yup.

code.google.com/p/traceur-compiler/source/browse/test/feature/Yield/Tree.js#13

# Axel Rauschmayer (13 years ago)

Nice. It took me a few moments to figure out what is going on, though. I would prefer the following – slightly more explicit – code: if (this.left) { yield* this.leftiterator; } Instead of: if (this.left) { yield* this.left; }

That example also demonstrates why the @ notation makes sense: this.left. at iterator()

# Jason Orendorff (13 years ago)

On Wed, May 9, 2012 at 2:07 AM, Axel Rauschmayer <axel at rauschma.de> wrote:

Nice. It took me a few moments to figure out what is going on, though. I would prefer the following – slightly more explicit – code:  if (this.left) {    yield* this.leftiterator;  } Instead of:  if (this.left) {    yield* this.left;  }

That example also demonstrates why the @ notation makes sense: this.left. at iterator()

Hmm. It seems like the language should accept anything iterable there, so that you can just say:

if (this.left)
    yield* this.left;

I think if you have to call a .iterator method explicitly in the course of doing simple stuff like this, we've gotten something wrong.

# Brendan Eich (13 years ago)

Jason Orendorff wrote:

On Wed, May 9, 2012 at 2:07 AM, Axel Rauschmayer<axel at rauschma.de> wrote:

Nice. It took me a few moments to figure out what is going on, though. I would prefer the following – slightly more explicit – code:

if (this.left) { yield* this.leftiterator; } Instead of: if (this.left) { yield* this.left; }

That example also demonstrates why the @ notation makes sense: this.left. at iterator()

Hmm. It seems like the language should accept anything iterable there, so that you can just say:

 if (this.left)
     yield* this.left;

I think if you have to call a .iterator method explicitly in the course of doing simple stuff like this, we've gotten something wrong.

AGREED.

Ahem.

# Axel Rauschmayer (13 years ago)

I don’t like the implicit iteration of the operand that happens for yield* this.left. To me, superficially, it looks like this.left is yielded. Instead, it is more a self-recursive call. Compare:

function visit(visitor) {
    if (this.left) {
        iterFunc(this.left, visitor);
    }
    visitor(this.label);
    if (this.right) {
        iterFunc(this.right, visitor);
    }
}

function* iterate() {
    if (this.left) {
        yield* iterGen(this.left);
    }
    yield this.label;
    if (this.right) {
        yield* iterGen(this.right);
    }
}

Tree.prototype.visit = visit;
Tree.prototype[iterator] = iterate;
# Erik Arvidsson (13 years ago)

On Thu, May 10, 2012 at 12:48 PM, Axel Rauschmayer <axel at rauschma.de> wrote:

I don’t like the implicit iteration of the operand that happens for yield* this.left. To me, superficially, it looks like this.left is yielded.

That is what the star is for.

yield* requires an iterable operand, just like for-of. If you pass something non iterable it throws.

# Brendan Eich (13 years ago)

What are iterFunc and iterGen?

The * means to exhaust the yield operand's value, otherwise you're just yielding that value. PEP-380 is the basis:

"The following new expression syntax will be allowed in the body of a generator:

yield from<expr>

where <expr> is an expression evaluating to an iterable, from which an

iterator is extracted. The iterator is run to exhaustion, during which time it yields and receives values directly to or from the caller of the generator containing the yield from expression (the "delegating generator")."

There's no point requiring an iterator not an iterable given the special form. Are you actually concerned about the * being too little syntax for the special form's semantics?

# Axel Rauschmayer (13 years ago)

On May 10, 2012, at 22:13 , Brendan Eich wrote:

What are iterFunc and iterGen?

I fixed the code, see below.

The * means to exhaust the yield operand's value, otherwise you're just yielding that value. PEP-380 is the basis:

"The following new expression syntax will be allowed in the body of a generator:

yield from<expr>

where <expr> is an expression evaluating to an iterable, from which an iterator is extracted. The iterator is run to exhaustion, during which time it yields and receives values directly to or from the caller of the generator containing the yield from expression (the "delegating generator")."

There's no point requiring an iterator not an iterable given the special form. Are you actually concerned about the * being too little syntax for the special form's semantics?

I just like the idea of seeing where recursion recursion happens and that generator recursion looks similar to function recursion. More a coding style thing than about ES.next syntax or semantics.

# Erik Arvidsson (13 years ago)

On Thu, May 10, 2012 at 2:16 PM, Axel Rauschmayer <axel at rauschma.de> wrote:

function* iter() {

if (this.left) {

yield* iter(this.left);

I'm confused how you expect this to work. iter does not take a parameter.

}

yield this.label;

if (this.right) {

yield* iter(this.right);

}

}

Tree.prototype.visit = visit;

Tree.prototype[iterator] = iter;

Maybe you intended to write something like this?

function* iter(tree) { if (tree.left) { yield* iter(tree.left); } yield tree.label; if (tree.right) { yield* iter(tree.right); } }

# Axel Rauschmayer (13 years ago)

Oh my, silly code, yes. Sorry.

function* iter() {
    if (this.left) {
        yield* iter.call(this.left);
        // OR: yield* this.left[iterator]();
    }
    yield this.label;
    if (this.right) {
        yield* iter.call(this.right);
    }
}

Tree.prototype[iterator] = iter;