On Fri, Nov 30, 2012 at 11:18 AM, Steve Dower <Steve.Dower@microsoft.com> wrote:
Guido van Rossum wrote:
Futures or callbacks, that's the question...
I know the C++ standards committee is looking at the same thing right now, and they're probably going to provide both: futures for those who prefer them (which is basically how the code looks) and callbacks for when every cycle is critical or if the developer prefers them. C++ has the advantage that futures can often be optimized out, so implementing a Future-based wrapper around a callback-based function is very cheap, but the two-level API will probably happen.
Well, for Python 3 we will definitely have two layers already: callbacks and yield-from-based-coroutines. The question is whether there's room for Futures in between (I like layers of abstraction, but I don't like having too many layers).
Richard and I have even been considering APIs like this:
res = obj.some_call(<args>) if isinstance(res, Future): res = yield res
or
res = obj.some_call(<args>) if res is None: res = yield <magic>
where <magic> is some call on the scheduler/eventloop/proactor that pulls the future out of a hat.
The idea of the first version is simply to avoid the Future when the result happens to be immediately ready (e.g. when calling readline() on some buffering stream, most of the time the next line is already in the buffer); the point of the second version is that "res is None" is way faster than "isinstance(res, Future)" -- however the magic is a little awkward.
The debate is still open.
How about:
value, future = obj.some_call(...) if value is None: value = yield future
Also considered; I don't really like having to allocate a tuple here (which is impossible to optimize out completely, even though its allocation may use a fast free list).
Or:
future = obj.some_call(...) if future.done(): value = future.result() else: value = yield future
That seems the most expensive option of all because of the call to done() that's always there.
I like the second one because it doesn't require the methods to do anything special to support always yielding vs. only yielding futures that aren't ready - the caller gets to decide how performant they want to be. (I would also like to see Future['s base class] be implemented in C and possibly even preallocated to reduce overhead. 'done()' could also be an attribute rather than a method, though that would break the existing Future class.)
Note that in all cases the places where this idiom is *used* should be few and far between -- it should only be needed in the "glue" between the callback-based world and the coroutine-based world. You'd only be writing new calls like this if you're writing new glue, which should only be necessary if you are writing wrappers for (probably platform-specific) new primitive operations supported by the lowest level event loop. This is why I am looking for the pattern that executes fastest rather than the pattern that is easiest to write for end users -- the latter would be to always return a Future and let the user write res = yield obj.some_call(<args>) -- --Guido van Rossum (python.org/~guido)