Guido van Rossum wrote:
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.
The costart function isn't strictly necessary, because you can always invoke __cocall__ directly. It's just there for convenience and for symmetry with all the other function/typeslot pairs. It's possible that 'codef' could be eliminated by following a similar rule to generators, i.e. if it contains 'codef' anywhere then it's a cofunction. But then we're back to the situation of having to insert a dummy statement to force cofunctionness some of the time. I expect this be needed much more frequently with cofunctions than with generators, because it's very rare that you call a generator for a side effect, whereas with coroutines it's quite normal. I also feel strongly that it would be too implicit. We manage to get away with it in generators because from the outside a generator is just a normal function that happens to return an iterator. But a cofunction has a very different interface, and the caller must be aware of that, so I would much rather make it explicit at the point of definition. A decorator could be provided to take care of the no-cocall case, but since it wouldn't be required in most cases, and people wouldn't bother to use it when they didn't need to, so it wouldn't serve to make the interface explicit in general. Maybe something could be done to force the decorator to be used on all cofunctions, such as making them neither callable nor cocallable until the decorator has been applied, but things are getting terribly convoluted by then. There would be something perverse about a definition that looks to all the world like a plain function, except that you can't actually do anything with it until it's been wrapped in a specific decorator.
- Is it possible to mix and match yield, yield from, and cocall in the same function? Should / shouldn't it be?
Yes, it is. A cofunction is a kind of generator, and 'yield' and 'yield from' work just the same way in a cofunction as they do in an ordinary generator.
(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...)
I understand how you feel, but this case seems fundamentally different to me. All of those decorators are pretty much agnostic about what they wrap -- they just take a callable object and externally modify its behaviour. A decorator with the same properties as codef would need to be much more intimately connected with the thing it's wrapping.
- If we had cocall, would yield from still be useful?
You need *some* way to suspend a cofunction, so if not yield, some other keyword would need to be invented. There doesn't seem to be any point to that. If you're asking whether it needs to be able to send and receive values when used in a cofunction, I suppose it's not strictly necessary, but again there doesn't seem to be any point in disallowing these things.
- 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,
No, if you examine the grammar in the PEP you'll see that the atom can be followed by a subset of the trailers allowed after atoms in other contexts, so it's possible to write things like x = cocall foo.blarg[42].stuff(y) which parses as x = cocall (foo.blarg[42].stuff)(y)
Also things lke 'cocall foo' (no call syntax) weird me out.
That's a syntax error -- a cocall *must* ultimately terminate with an argument list.
- 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*. :-)
I'll have to think about that and get back to you. -- Greg