Re: NRE committed: PLEASE TEST



Fredderic wrote:
On Thu, 04 Sep 2008 11:15:56 +0100, Neil Madden <nem@xxxxxxxxxxxxx> wrote:
Fredderic wrote:
[...]
Two cases:
1. resume only takes a single argument, whereas the original proc
takes [more than] 1, in which case anybody trying to call the original will get an
error.

Unless there is some seriously useful functionality the requires the
coroutine taking arguments (and even I can't think of anything remotely
sane there ;) ), this would be a major PITA. It utterly blocks some
functionality, while saving you from only very minor inconvenience at
best.

What do you mean by "the coroutine taking arguments" being a "PITA"? Creation of a coroutine takes inital arguments - fair enough - and resuming a coroutine also takes an argument (which becomes the result of the yield that suspended it). This is essential to allow communication between coroutines without resorting to global vars.

2. resume takes multiple arguments and the coroutine expects >1,
whereas the original proc takes only 1 argument, will again result in
an error for callers of the original proc.

Well, the thing is that the coroutine doesn't take _any_ arguments.
This got me at first, too, in the implementation being offered up. The
coroutine isn't a proc, or a command, it's an [exec] floating in a
bucket-load of magic. It's only connection back to the caller, the
only "arguments" that it'll ever see, is what pops out of [yield].

Coroutine has nothing to do with [exec]. Under the hood it saves away an ExecEnv structure (AIUI), but that is just the name of the particular structure used as an execution context for Tcl code. From the Tcl script level it is indeed a command, and it does take an optional argument.


Making that a list of whatever was given to the coroutine command,
always, allows the coroutine to pretend to be a regular proc, ignore
the arguments totally, or anything in between. [lassign] can pluck out
passed words with ease, regular validation techniques can be applied to
the returned list beforehand. Whatever...

But you now have to have special code in the coroutine to handle the fact that someone called it expecting the original command, which it has replaced.

*IF* there is some functionality that the coroutine could do with, it's
almost certainly not going to be the regular case, and can quite
readily be retro-fitted with a new command. Perhaps [coroutine] needs
to be implemented as an ensemble where the current functionality is
moved to [coroutine create], so we can have [coroutine info] and
friends, and potentially a future [coroutine invoke] or something to
obtain the functionality that will be provided by those mythical future
useful extra arguments.

What future extra arguments? The coroutine takes a single argument right now.

The point is that the original proc and the coroutine resume command
can have wildly different signatures and expectations. Dynamically
replacing one with the other is likely to create incompatibilities in
most cases. What does it achieve?

If you're going to dynamically replace one with the other, then
presumably you already know what arguments you want. That's an utterly
trivial argument. In must such instances it just doesn't make sense to
change the signature, so don't. It really is that simple.

It's not that simple - the original proc is basically a constructor for a coroutine, whereas the coroutine resume command is an invocation of a running coroutine. The two are very different, and I find it hard to imagine many compelling use cases in which one would be interchangeable with the other.


I just personally think it's too handy a case to ignore off-hand; a
function that is able to initialise itself, and then preserve state
between invocations without having to worry about clashes in global
variable naming, namespace pollution, etc. I've got a bunch of
procs that basically look like this;

proc some-proc {...} {
upvar #0 some-***-ugly-long-global-variable-name myState
if { ! [info exists myState] } {
... initialise internal state ...
} else {
lassign $myState ...
}
... validate and handle new arguments ...
... do regular iteration stuff ...
set retVal ...
set myState [list ...]
return $retVal
}

In contrast, a self-replacing coroutine needs only do;

proc coro-proc {...} {
... initialise internal state ...
coroutine coro-proc apply {{...locals...} {
while { 1 } {
... do regular iteration stuff ...
yield $retVal
... validate and handle new arguments ...
}
}} ... those pre-computed locals ...
}

This works right now. *If* your initialisation needs no extra arguments, then this is a fine pattern. However, more generally you would separate the construction/initialisation from the coroutine/instance command.

[...]
The alternative is to leave things as they are and pass a list when
you want to pass multiple values. That has the benefit of symmetry.

Symmetry with what, exactly?!?

Symmetry between the way the coroutine is called and the way yield returns data -- i.e. you pass a single value to the resume command, and a single value is returned from [yield]. Rather than passing multiple arguments to the coroutine and getting a list (single value) back from yield.

As far as I can see, it's simply a
pointless restriction. I totally understand why it was done that way
originally, but it's just not sane to leave it that way.

Tone down the hyperbole, please. There is no loss of expressiveness by restricting the resumption command to a single argument. You can, after all, always restore your desired behaviour:

proc resume {coro args} { $coro $args }

This could be useful in certain cases (e.g. implementing Erlang style actors as coroutines), but in most uses I can think of a single argument is sufficient and so I'd prefer not to have to unpack it from a list all the time. This can of course be wrapped in a constructor:

proc actor {name args} {
interp alias {} $name {} resume [coroutine spawn {*}$args]
}

(See the wiki for [coroutine spawn] - it just generates a unique name for the coroutine).

[...]
Heck, talking about the present implementation you're so eager to
defend, is that single argument to the coroutine optional? (I haven't
noticed it actually stated in the thread one way or the other.)

I take it from this and other comments that you haven't even bothered to try the implementation?

-- Neil
.