The proposal for coroutines, which seems to be one of the "accepted" ones, bothers me.
When a suspended block is called again, parameters are ignored and it resumes where it left off. Great for iterator, right? Well, what happens if you want to use the same iterator within the body being controlled by the iterator already?
Basically, a line like
while (my $node = $root->next_inorder()) {
print $node->{data}; }
found in a module will test fine, but will suddenly and mysteriously fail if that code is called (even indirectly, by many levels) from another use of the iterator, even if it's on a different collection.
Or, are they implying that a specific occurance of the function call in the code keeps its own state, like the flip-flop operator does today? I wonder, because their example is calling itself recursivly and passing it an argument (which presumably is not ignored!).
We also need a "quit" feature. Say I'm iterating over a collection and break out of the loop when I find the element I want. Just last it, and don't look at the rest. Well, next time that code is called, it will still be incomplete...
—John
Re (tilly) 1: Perl 6 coroutines (RFC 31)
by tilly (Archbishop) on Jul 13, 2001 at 05:51 UTC
|
My understanding is that co-routines are called co-routines
because they are meant to be called from within another
closely related routine, and they pass control back and
forth between each other.
If you want to get to know them better, I would recommend
playing around with a language that has them. The two most
obvious ones are Scheme and Ruby. You might also find the
paper Coroutines
in C interesting (thanks to japhy for mentioning it
once). It discusses an actual problem which coroutines
would be a natural fit for, and then proceeds to show how
you can mimic them in C. (The program that paper is about,
PuTTY,
is fairly widely used. If you are on Windows and
occasionally need to telnet, or would prefer to ssh
instead of telnet, then I strongly recommend looking into
it.) | [reply] |
|
my $iterator= $container->iterate();
while (my $item= $iterator++) { process $$item; }
The code behind ++ would have to save its state and return each time, so it's harder to write than having a yeild. But you declare the iterator instance and can use that one (or another one).
What if yeild caused the call to return a token, and that is what you make subsequent calls on? You can have another one going without conflict.
I agree that it doesn't sound like co-routines as described in classic literature. Those would "call" each other, rather than having a yeild statement at all. Such co-routines as members of the same object would also make sense.
—John | [reply] [d/l] |
|
Personally I don't think that the issue of avoiding conflict is that bad. If you really want multiple instances of the same thing, then just have a function that returns a closure, and each closure maintains its own state.
Anyways the yield idea can work really nicely. As I said before, try Ruby. Ruby combines yield with a nice piece of syntactic sugar. In Ruby you can call functions 2 ways. In one way you just call the function. In the other you call the function and pass a block as an extra argument. When you do the latter then any yield will call the block. (You can also detect which way you are being called and function appropriately.)
So what you get is effectively the same as creating a closure in your context and passing it to the function, and having it call the closure. However it works really nicely because, even though that is what you are doing, there is a lot less machinery needed to set it up. And it seems that people who have a hard time getting their heads around the idea, You encapsulate a closure and then pass it in have no problem with the idea of passing a block to a method named each and having your block called on each thing encapsulated in your object.
As Larry Wall has noted, sometimes sugar is worth it in its own right. Even if you cannot readily use yield for the full generality of what you want, it can work really nicely.
As I said before, you can play around with Ruby now to get a sense of what this feature will be like in Perl 6. (Of course many of the other features of Perl 6 are missing in Ruby...)
| [reply] |
|
Re: Perl 6 coroutines (RFC 31)
by MeowChow (Vicar) on Jul 13, 2001 at 11:58 UTC
|
... one could write:
%newhash = map {yield transform_key($_); transform_val($_)}
+%oldhash;
This flattens the %oldhash to a sequence of key/value pairs
This does not make sense to me, since $_ is a localized stack-based variable, which should in principle be wrapped up in the saved local context/state. Thus calls to transform_val would be operating on keys, not values.
MeowChow
s aamecha.s a..a\u$&owag.print | [reply] [d/l] |
|
I think that Damian's proposal assumes that Perl is going
to maintain a global stack, and what is saved is just the
internal state of the function. Then $_, being a global,
will be whatever it is currently in Perl while where you
start in the block will be preserved.
| [reply] |
|
I believe that would create alot of nasty confusion, and make localized globals all but useless inside of coroutines. For instance, the following would be quite troublesome:
sub my_iter {
for (@_) {
yield $_;
# upon return, $_ may no longer be localized. if we
# modify it, we could be modifying a global value
# outside our scope. there's no way to tell, and
# that's rather nasty.
}
}
Of course, one could avoid using localized globals inside coroutines/generators in the first place, but where's the fun in that, I ask ;)
MeowChow
s aamecha.s a..a\u$&owag.print | [reply] [d/l] |
|
I couldn't follow the continuation lab you mentioned. But I think the idea of a continuation primitive is exactly what we need, and my comments to Tilly earlier on this thread seems to be that.
Specifically, the first yeild returns a continuation object. Use that object to "resume", and it knows exactly which instance you mean. The resume should pass parameters, too. It would re-bind @_ in the function, so the line after yeild could look at that (again) if desired.
The resumer thing could be an object with various members, including call and reset and who knows what else. Or, it could return a function reference and just calling that will resume.
my $next= $container->iterate ('inorder');
while (my $node= $next->()) { ... }
Details: the iterate() function would do set-up and yeild with a token, using another built-in function to generate it. Or it could be a separate statement that creates it and yeilds at once. Then it goes into the traversal loop.
What do you think?
—John | [reply] [d/l] [select] |
|
MeowChow
s aamecha.s a..a\u$&owag.print | [reply] |
|
|
|
|
Could you elaborate on the distinction between "coroutines", "generators", and "call with concurrent continuation"?
| [reply] |
|
YAIUL (Yet Another Informative USENET Link ;-)
MeowChow
s aamecha.s a..a\u$&owag.print | [reply] |
Re: Perl 6 coroutines (RFC 31)
by Abigail (Deacon) on Jul 13, 2001 at 16:37 UTC
|
Basically, a line like
while (my $node = $root->next_inorder()) {
print $node->{data};
}
found in a module will test fine, but will suddenly and mysteriously fail
if that code is called (even indirectly, by many levels) from another
use of the iterator, even if it's on a different collection.
Are you aware that *exactly* the same problem already exists in
Perl? We have several iterators in Perl, for instance, each,
readdir, <>, m//g, globbing,
and I probably forgot a few. They already act as coroutines. All
Damian is proposing is to make that available in Perls user space too.
You should also realize that someone who uses coroutines knows what
he/she is doing. Programmers don't randomly call subs. They call subs
because they know what the sub does - and they should also know a sub
is a coroutine, and program appropriately.
We also need a "quit" feature. Say I'm iterating over a collection and
break out of the loop when I find the element I want. Just last it,
and don't look at the rest. Well, next time that code is called, it will
still be incomplete...
Eh, no we don't. For two reasons. First there is of course
return, but more to the point, coroutine like behaviour
only happens when yield is used. In the example you
present, no yield happens, so it will behave like any
other subroutine.
-- Abigail
| [reply] [d/l] [select] |
|
Yea, I know each has a similar problem, but it's not as bad because it has a separate state per hash. Called code can iterate over a different hash without messing it up.
Likewise, readdir is given a handle, and you can have multiple handles open. It's no different from a normal file read, really. m//g is even better, in that not only is it per string (and a string can be copied to a local temp so nobody else will even try to iterate over the same copy), but you can use pos() to reset the state, or cancel it by matching a RE without a /g. How is any of that "*exactly* the same"?
My issue with the proposal is that the iterator would be global for all instances. It would be like each maintaining a single global state, rather than per-hash. IAC, Perl 6 will fix nested each's on the same collection.
but more to the point, coroutine like behaviour only happens when yield is used. In the example you present, no yield happens, so it will behave like any other subroutine
Why do you say that? next_inorder() yeilds.
there is of course return ah, that makes sence. You can't pass parameters into the iteration in-progress, but you could have a separate reset function that sets a shared global and calls the iterator, which sees the flag and returns.
—John
| [reply] [d/l] [select] |
|
My issue with the proposal is that the iterator would be global for
all instances. It would be like each maintaining a single global state,
rather than per-hash.
Ah, but not if you create your iterators as anonymous closures! Then
you can have several instances, each working on separate data, and
each keeping state.
but more to the point, coroutine like behaviour only happens when yield
is used. In the example you present, no yield happens, so it will behave
like any other subroutine
Why do you say that? next_inorder() yeilds.
I was referring to your:
Say I'm iterating over a collection and break out of the loop when I
find the element I want. Just last it, and don't look at the rest.
No yield here.
-- Abigail
| [reply] [d/l] |
|
Re: Perl 6 coroutines (RFC 31)
by Hofmator (Curate) on Jul 13, 2001 at 17:12 UTC
|
I just wanted to mention Coro by Marc Lehmann,
a try at implementing coroutines in perl5.
-- Hofmator
| [reply] |
|
I looked at Coro, and what is being done with the XS code in it is that the context of the current subroutine is being saved. These contexts can then be jumped back to. I don't know how well it works, but it passes its own tests <g>
| [reply] |
|
|