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


Reply via email to