On Wednesday, May 28, 2003, at 01:01 PM, Austin Hastings wrote:
Exampling:

sub traverse(Hash $tree) {
  return unless $tree;

  traverse $tree{left} if $tree{left};
  yield $tree{node};
  traverse $tree{right} if $tree{right};
}

my %hash is Tree;
my &cotrav := coro &traverse(%hash);
print $_ for <ctrav.resume>;

my &thtrav := thread &traverse(%hash);
print $_ for <thtrav.resume>;


Hmm. I think that having _anything_ on the caller side that has to change based on whether the called thing is a subroutine vs. a coroutine probably defeats one of the most central purposes of coroutines -- that nifty subroutine-like abstraction that makes it "just work". Consider, under Damian's latest model:

for <foo()> {...}

It doesn't matter whether foo() is a closure or function returning a list, lazy list, or iterator, or is a coroutine returning it's .next value. Which is excellent, and, I'd argue, the whole point; I'm not sure that we can have any coroutine syntax that _doesn't_ do that, can we?

But, as Luke pointed out, some of the other syntax required to make that work is isn't particularly friendly:

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

If I work backwards, the syntax I'd _want_ for something like that would be much like Luke proposed:

    sub pre_traverse(%data) is coroutine {
        yield %data{ value };
        pre_traverse( %data{ left  } );
        pre_traverse( %data{ right } );
    }

... where the internal pre_traverses are yielding the _original_ pre_traverse. Whoa, though, that doesn't really work, because you'd have to implicitly do the clone, which screws up the normal iterator case! And I don't immediately know how to have a syntax do the right thing in _both_ cases.

So, if I have to choose between the two, I think I'd rather iteration be easy than recursion be easy. If lines like

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

are too scary, we might be able to make a keyword that does that, like:

    sub pre_traverse(%data) is coroutine {
        yield %data{ value };
        delegate pre_traverse( %data{ left  } );
        delegate pre_traverse( %data{ right } );
    }

Maybe. But in truth, that seems no more intuitive than the first.

(s/coroutine/thread/g for the same rough arguments, e.g. "why should the caller care if what they're doing invokes parallelization, so long as it does the right thing?")

MikeL



Reply via email to