On Fri, 2009-01-02 at 22:56 +0100, Aristotle Pagaltzis wrote:
> > When I asked this question on #perl6, pmurias suggested using
> > gather/take syntax, but that didn't feel right to me either --
> > it's contrived in a similar way to using a one-off closure.
>
> Contrived how?
Meaning, the gather/take syntax doesn't make much sense, because we're
not "gathering" anything; the PID file handler has nothing to return.
We'd only be using it for the "side effect" of being able to pause the
callee's execution and resume it later.
> When you have an explicit entity representing the continuation,
> all of these questions resolve themselves in at once: all calls
> to the original routine create a new continuation, and all calls
> via the state object are resumptions. There is no ambiguity or
> subtlety to think about.
I like this argument. I'm not sure it's applicable in every case, but
it certainly applies to the class of situations containing my problem.
> So from the perspective of the caller, I consider the “one-off”
> closure ideal: the first call yields an object that can be used
> to resume the call.
>
> However, I agree that having to use an extra block inside the
> routine and return it explicity is suboptimal. It would be nice
> if there was a `yield` keyword that not only threw a resumable
> exception, but also closed over the exception object in a
> function that, when called, resumes the original function.
>
> That way, you get this combination:
>
> sub pid_file_handler ( $filename ) {
> # ... top half ...
> yield;
> # ... bottom half ...
> }
>
> sub init_server {
> # ...
> my $write_pid = pid_file_handler( $options<pid_file> );
> become_daemon();
> $write_pid();
> # ...
> }
That's pretty nice. Perhaps we can make it even cleaner with a few
small tweaks to init_server():
sub init_server(:$pid_file, ...) {
# ...
my &write_pid := pid_file_handler($pid_file);
become_daemon();
write_pid();
# ...
}
So far, this variant is winning for me, I think. It's slightly more
verbose on the caller's side than the yield variant I had proposed, but
it's also more explicit, and allows (as you said) a clean syntactic
separation between starting the PID file handler and continuing it.
It does bring up a question, though. What if pid_file_handler() needed
to be broken into three or more pieces, thus containing multiple yield
statements? Does only the first one return a continuation object, which
can be called repeatedly to continue after each yield like this?
sub init_server(:$pid_file, ...) {
# ...
my &more_pid_stuff := pid_file_handler($pid_file);
become_daemon();
more_pid_stuff();
do_something();
more_pid_stuff();
do_something_else();
more_pid_stuff();
# ...
}
Or does each yield produce a fresh new continuation object like this?
sub init_server(:$pid_file, ...) {
# ...
my &write_pid := pid_file_handler($pid_file);
become_daemon();
my &fold_pid := write_pid();
do_something();
my &spindle_pid := fold_pid();
do_something_else();
spindle_pid();
# ...
}
(Note that I assume you can simply ignore the returned object if you
don't plan to continue the operation any more, without raising a
warning.)
Certainly the first version has less visual clutter, so I tend to lean
that way by default. But the second design would allow one to create a
tree of partial executions, by calling any earlier continuation object
again. That's a very powerful concept that I don't want to give up on.
Supporting both feels like it might be an adverb on the invocation
(possibly with a frosty sugar coating available). It would be nice to
support invoking a continuation in "ratcheting" and "forgetful" modes.
Thoughts?
-'f