I'm convinced of the utility. I still find the mechanism somehow odd or clumsy; the need for two new keywords (codef and cocall), a new builtin (costart), and a new api (__cocall__) doesn't sit well. Please don't consider this a -1; I think there's something that can be done (either to my mind or to the proposal :-). I suppose I should look again at goroutines and see what syntax and other rules they use. In the mean time let me ask a few more questions (sorry if these have been answered before, my attention is focusing in and out of this thread): - Is it possible to mix and match yield, yield from, and cocall in the same function? Should / shouldn't it be? - Would it be sufficient if codef was a decorator instead of a keyword? (This new keyword in particular chafes me, since we've been so successful at overloading 'def' for so many meanings -- functions, methods, class methods, static methods, properties...) - If we had cocall, would yield from still be useful? (I suppose yield from is the thing of choice when using generators-as-iterators, e.g. when walking a tree. But what bout yield from for coroutines?) - The cocall keyword eerily reminds me of Fortran. I know that's not fair, but still... - The syntax worries me. Your PEP suggests that cocall binds tightly to an atom. That would mean that if the cofunction is really a comethod, you'd have to parenthesis it, like cocall (obj.method)(args) ? Huuuu, ugly. Also things lke 'cocall foo' (no call syntax) weird me out. - How much of the improved error flagging of codef/cocall can be obtained by judicious use of decorators and helper functions? (I need this in Python 2.5 *now*. :-) --Guido On Tue, Aug 10, 2010 at 12:22 AM, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Guido van Rossum wrote:
We'll see. I still cannot get my head around why cofunctions are so great.
I think I can offer some evidence. I've been playing around with two versions of a discrete-event simulation kernel, one using yield-from and the other using cofunctions. Here's the main function of one of my test cases. I've introduced a deliberate bug -- can you spot it?
def customer(i): print("Customer", i, "arriving at", now()) yield from tables.acquire(1) print("Customer", i, "sits down at a table at", now()) yield from waiters.acquire(1) print("Customer", i, "orders spam at", now()) hold(random.normalvariate(20, 2)) waiters.release(1) print("Customer", i, "gets served spam at", now()) yield from hold(random.normalvariate(10, 5)) print("Customer", i, "finished eating at", now()) tables.release(1)
The bug is that the first call to hold() is missing a 'yield from' in front of it. If I run this, I don't get any exception -- it produces plausible-looking but incorrect results.
Here's another version, with a very similar bug in a different place -- this time it's the second call to hold() that's missing a 'yield from'.
def customer(i): print("Customer", i, "arriving at", now()) yield from tables.acquire(1) print("Customer", i, "sits down at a table at", now()) yield from waiters.acquire(1) print("Customer", i, "orders spam at", now()) yield from hold(random.normalvariate(20, 2)) waiters.release(1) print("Customer", i, "gets served spam at", now()) hold(random.normalvariate(10, 5)) print("Customer", i, "finished eating at", now()) tables.release(1)
If I run this one, I do get an exception, but it's a rather unhelpful one:
Traceback (most recent call last): File "restaurant2.py", line 35, in <module> run() File "/Local/Projects/D/Python/YieldFrom/3.1/YieldFrom-3.1.2/Examples/Simulation/simulation.py", line 25, in run next(current_process) File "restaurant2.py", line 32, in customer tables.release(1) File "/Local/Projects/D/Python/YieldFrom/3.1/YieldFrom-3.1.2/Examples/Simulation/resource.py", line 25, in release wakeup(self.queue[0]) File "/Local/Projects/D/Python/YieldFrom/3.1/YieldFrom-3.1.2/Examples/Simulation/simulation.py", line 34, in wakeup schedule(process, now()) File "/Local/Projects/D/Python/YieldFrom/3.1/YieldFrom-3.1.2/Examples/Simulation/simulation.py", line 16, in schedule heappush(event_queue, (time, process)) TypeError: unorderable types: generator() < generator()
If you examine the traceback, you'll find that *nowhere* does it mention the location where the error actually is! Instead, a mysterious error emanates from some place deep inside the scheduler. I would hate to have to track down a problem like this in a large program.
Here's the equivalent thing using cofunctions, complete with a corresponding missing 'cocall':
codef customer(i): print("Customer", i, "arriving at", now()) cocall tables.acquire(1) print("Customer", i, "sits down at a table at", now()) cocall waiters.acquire(1) print("Customer", i, "orders spam at", now()) cocall hold(random.normalvariate(20, 2)) cocall waiters.release(1) print("Customer", i, "gets served spam at", now()) hold(random.normalvariate(10, 5)) print("Customer", i, "finished eating at", now()) cocall tables.release(1)
The exception and traceback resulting from this is crystal clear:
Traceback (most recent call last): File "restaurant2.py", line 34, in <module> run() File "/Local/Projects/D/Python/YieldFrom/3.1/Cofunctions-3.1.2/Examples/Simulation/simulation.py", line 25, in run next(current_process) File "restaurant2.py", line 29, in customer hold(random.normalvariate(10, 5)) TypeError: Cofunction must be called with cocall or costart
If this doesn't convince you of the utility of cofunctions or something like them, I don't know what will.
-- Greg _______________________________________________ Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas
-- --Guido van Rossum (python.org/~guido)