MikeL writes:
> > I say this because Damian's coroutine proposal could be greatly
> > simplified (IMHO making it clearer and easier) if calling the sub
> > didn't imply starting an implicit coroutine the first time.  I might
> > write something that exemplifies this.
> 
> I'd be quite interested in this -- please do.  I *like* Damian's latest 
> coroutine proposal quite a bit, enough so that it really got me 
> thinking about how perplexingly lame typical thread syntax is.

Here we go:

Damian's current coroutine proposal suggests that to recurse, you
start a coroutine of your own code and yield what it yields:

    coro pre_traverse(%data) {
        yield %data{value};
        yield $_ for <&_.clone(%data{left})>;
        yield $_ for <&_.clone(%data{right})>;
    }

Now, imagine yourself a beginner and take a long look at this line:

        yield $_ for <&_.clone(%data{left})>;

What the heck?  That uses an awful lot of "advanced" Perl 6 features
all in one line.  And that's the easiest way to recurse?

Worse yet, what if you yield I<and> you need to pay attention to
return values, which isn't an uncommon thing:

    coro fibo_tree($n) {
        return $n if $n <= 1;
        yield $n;

        my @ret = <&_.clone($n-1)>;
        my $n1 = pop @ret;
        @ret = <&_.clone($n-2)>;
        my $n2 = pop @ret;
        return $n1 + $n2;
    }

And all this pain comes from support for this idiom:

    my $fibo_1 = fibo();
    my $fibo_2 = fibo();

Which, as I've described, is "bad form" anyway, because subs ideally
keep no state.  That's what objects were invented for.

C<let>'s remove that ability, hypothetically.

Now there's a distinction between calling a coroutine as a coroutine
or as a regular sub.  When it looks like a sub call, it is a sub call;
when it's used as an iterator (perhaps cloned), it's a coroutine.  We
now have:

    coro pre_traverse(%data) {
        yield %data{value};
        pre_traverse(%data{left});
        pre_traverse(%data{right});
    }

    coro fibo_tree($n) {
        return $n if $n <= 1;
        yield $n;

        return fibo_tree($n-1) + fibo_tree($n-2);
    }

Those are remarkably simpler, and use no "advanced" syntax.  To use
them, as before:

    for <&pre_traverse.clone(%tree)>      { ... }
    for <&fibo_tree.clone($n)>            { ... }

Or better yet:

    for < coro { pre_traverse(%tree) } >  { ... }
    for < coro { fibo_tree($n) } >        { ... }

That's not as nice as it could be.  I'm not here to rework that part
of the syntax, though.  It also supports factories for cases like
this:

    sub pre_traverse(%data) {
        coro (?%arg = %data) {
            yield %arg{value};
            _(%arg{left});
            _(%arg{right});
        }
    }

    for <pre_traverse(%tree)> {...}

The overall simplification is that it now supports saving the whole
stack again, which results in much clearer and easier recursion.  It
poses no burden on other kinds of implementations, and removes one bit
of the interface that secretly stores state when it shouldn't.

Luke

Reply via email to