Pike now (8.1) has a decent promise/future subsystem. It's always had
great handling of multiple asynchronous operations (GUI, socket, time
delay, etc) with the convenience of just returning -1 from main.
Interested in people's opinions on whether it would be of value to
introduce generators and (built on them) asynchronous functions.
I'm borrowing terminology heavily from Python and ECMAScript here, and
both of them have drawn from prior art.
A *generator function* is a function that can be partially run, then
set aside until resumed. Effectively, its stack frame can be
encapsulated in an object for future use. It would look something like
this:
int get_numbers() {
write("Starting\n");
yield 1;
write("Continuing\n");
yield 2;
write("Finishing\n");
return 3;
}
Calling get_numbers would prepare an execution context, but not
actually begin running the function at all. The caller would get back
a *generator object*. Asking this generator object to produce its next
value would begin executing the function, triggering the call to
write, and would stop at the first yield point, returning that value
(1). Asking for another value resumes the function, triggering the
second write, and returns the next yielded value. The third time you
request a value, it would run the function to completion, and report
that it returned (not yielded) 3.
(The types yielded don't have to match the type of the return value.
I'm not sure if there's a good way to declare the yielded types, or if
it's best to just assume 'mixed'.)
A generator object would be an Iterator, allowing you to loop over the
yielded values easily:
foreach (get_numbers() ;; int num)
The 'yield' keyword would be an expression/operator, allowing you to
send a value back into the generator function as the result of the
yield expression.
The awesomeness of this becomes more apparent when combined with
Concurrent.Future objects. An asynchronous function would be a
generator which yields Futures until it is ready to return a value.
Calling such a function automatically returns a ready-to-go Future,
set up such that it is automatically transform_with'd the next yielded
Future, or transform'd into the returned value. It would be used
somewhat thus:
void show_channel_info(string name) {
int id = yield get_channel_id(name);
mapping data = yield query_channel(id);
write("Status: %s\n", data->status);
mapping stream = yield get_stream_info(id);
if (stream) write("%s is online, %s\n", name, stream->uptime);
else write("%s is offline.\n", name);
}
This is WAY more readable than the equivalent with a bunch of lambda
functions to capture the intermediate values.
Thoughts? Concerns? Questions?
ChrisA