Wow, what a flood.

The idea of keep the various degrees of code
parallelism similar in form yet distinct in detail
sounds good to me.

I would like to suggest a radically different
mechanism, that there be operators: fork, tfork, and
cfork to split off a process, thread, or coroutine
respectively.  (The first might be called pfork
if we're willing to give up history for forward
consistancy.)

Each would return a reference to the original code
flow unit, the child code flow unit, and an indication
of which one you are or if there was an error.
(This is much like the current fork, except that I'd
like the child to be provided with the ID of the
parent, as well as the parent being provided with
the ID of the child.)

Fork, of course, would result in totally separate
process encapsulation (or something transparently
equivalent as is done in Perl 5 on Windows, I
believe, where they use threads to simulate fork).
All variables are independent copies after the fork,
system calls from one process should not block the
other, etc.  Switching execution from one process to
the other is at the whim of the operating system and
is not necessarily going to happen at perl operation
boundaries.  Data transfer must be done external
(either an already open pipeline or through the file
system or such).

Tfork would result in separate threads.  If the OS
provides them, they would likely be used, otherwise
the interpreter might have to simulate them.  All "my"
variables are independent copies after the tfork, but
"our" variables are shared.  Operations that access
"our" variables must be managed by the interpreter
so that no language level actions are seen as broken
up into separate portions by an ill-timed process
switch to another thread.  Especially if OS threads
are used, the interpreter may have to take special
action to ensure that switches do not expose an "our"
update that is partially complete.  While pipelines
might work (depends upon whether the OS is supporting
threads) and file system communication would work,
normally communication would be done through "our"
variables.

Cfork would result in separate coroutines.  They would
have separate call stacks, and a separate copy of the
current stack frame, but otherwise all "my" amd "our"
variables would be fully shared.  Process switching
only occurs under control of the process themselves,
so no operation level protection must be done.

I'll assume the cfork is defined to return to the parent.

The auto forking coroutine that Damian described could
be a syntactic sugar based on this mechanism (while not
being the only way of getting coroutines) so that:

cosub func {
    # func body ...
    yield
    # func body ...
}

is treated as a macro that gets turned into:

{
    my $parent = 0;
    my $child = 0;

    sub func {
        unless $state {
            my ( $p, $c, $which ) = cfork;
            if( $which eq IS_PARENT ) {
                $child.next;
                return $child;
            } elsif( $which eq IS_CHILD ) {
                @_ -> sub {
                            yield;
                            # func body
                            yield;
                            # func body
                            };
                yield undef while 1;
            }
        }
        $child.next( @_ );
    }
}

which takes care of the magic "first time starts the
coroutine, subsequent times resume it from where it
left off" semantic for this special case, without
requiring that anything that is a coroutine has to
have that magic behaviour.

The "next" method would be code of the form:

next( $dest, @stuff ) {
    resume( $dest, @stuff );
}

But resume needs a bit of magic.  I'll write as
a special variable $CUR_COROUTINE which always
contains that object for the currently executing
coroutine (one has to be generated for the initial
mainline, either when perl starts or the first time
that a cfork occurs).  Every coroutine object has an
attribute caller.  When resume is called, it updates
the caller attribute of the coroutine that is being
resumed with a reference to $CUR_COROUTINE.

Within a coroutine, then, you can always determine
the ID of the coroutine that last resumed you with
$CUR_ROUTINE.caller.

This means that the yield operator could be macro
that expands to:

# yield( stuff )
resume( $CUR_COROUINE.caller, stuff )

Providing resume as the underlying mechanism for
next/yield allows for non subordinate coroutine flow,
like a round robin if you use resume (which loses the
advantage/restriction of yield in which the coroutine
that is the target is implicitly that coroutine that
called you last); while still providing the simpler
subordinate viewpoint for the more common simple cases
(like generators).

Reply via email to