Re: NRE committed: PLEASE TEST



On Thu, 04 Sep 2008 11:15:56 +0100,
Neil Madden <nem@xxxxxxxxxxxxx> wrote:

Fredderic wrote:
On Wed, 03 Sep 2008 20:19:33 +0100,
Neil Madden <nem@xxxxxxxxxxxxx> wrote:
Fredderic wrote:
My wish here would be served by the [coroutine] command being able
to displace an existing proc or command until it completes.
A limitation of this approach is that you couldn't have 2 or more
simultaneous coroutine instances based on the same command.
Not at all... The musings I muttered on coroutines ages ago over on
the wiki were along these lines (with the unenlightened assumption
that [yield] would only effect the proc it was invoked from).
Link?

Urgh... Nope. My browser keeps crashing right back out. Not quite
sure what's going on just yet.


In those musings a [coroutine] command would be used to explicitly
create a simultaneous instance until a new name, where the original
command would simply resume the default one. No reason the same
couldn't be applied here.
If the original command has been replaced by a default coroutine, how
do you construct other coroutines based on it?

By the same magic that makes everything happen. ;)

My thinking at the time put the coroutine structure as an extension of
a proc, in which case such information would have been available.
That aside...

This whole idea hinges on the displaced proc being held somewhere in
the coroutine structure for this to work at all, as it needs to be put
back when the coroutine finishes. Therefore the capacity exists for
[coroutine] to pluck it back out and construct a fresh coroutine under
another name.


Another difficulty is that the original proc may take different
arguments than the resume command.
Actually that came out rather cleanly in the wash. The original
proc takes initialisation arguments, while the resumed coroutine
gets any arguments via the return value from [yield]. Unless it
takes specific action, those arguments will be ignored in the
resuming case anyhow.
Two cases:
1. resume only takes a single argument, whereas the original proc
takes 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.


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].

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...

The decision seems to be between being able to make the coroutine behave
the way you want, vs. being locked into a small box that needs pointless
wrapping to escape out of.

*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.


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.

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 ...
}

Although it's certainly not a show-stopper... The alternative might be
something like;

proc wrap-proc {...} {
if { [info command wrap-coro] eq "" } {
... initialise internal state ...
coroutine wrap-coro apply {{... locals ...} {
while { 1 } {
... do regular iteration stuff ...
yield $retVal
... validate and handle new arguments ...
}
}} ... those pre-computed locals ...
}
return [wrap-coro ...]
}

It's just not quite as neat, though I'll grant this particular issue is
a fairly minor one.


Therefore, if the resume command could take multiple arguments then
it would be impossible to distinguish between multiple separate
arguments and a list. You could maybe extend yield, something like:
No. You simply ALWAYS return a list, ala [proc]s special "args"
argument. It might be slightly inconvenient, but it's an awful lot
more convenient than not being able to do it at all. There are
other possibilities also, such as allowing [yield] to take an
arguments list after the return value. A bit odd, but it's an idea
if you're desperate. ;)
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?!? 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. There either
needs to be other arguments that do something, or allow multiple
arguments to be passed through like every other command in the history
of TCL. Like I said, what on earth is it supposed to be maintaining
symmetry with?!? How many people would appreciate [proc] being
arbitrarily restricted to only accepting a single argument? I really
don't see the difference.

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.)

If the coroutine argument is mandatory, then that's going to be
annoying in any case were you don't need arguments, which I suspect is
probably quite a large slice of the use cases. People will be forced
to pass in useless junk arguments which don't make any sense and just
confuse things.

If the coroutine argument is optional, then how does your coroutine
tell whether an argument was actually supplied or not? Unless it's
being passed through as a list already, or [yield] is taking a
confusing probably optional pass-by-name side-channel variable, or
perhaps returning a [maybe], then it's just plain broken. And if it
returns a list so that you know whether an argument was being passed
through or not, then why the heck not accept multiple arguments?!?


Coroutines should be possible to use as an implementation detail,
as in my worked example converting a simple synchronous program to
use the event loop: http://wiki.tcl.tk/21532
It gets a little more complicated when you're not writing the entire
application from scratch. When you're trying to retro-fit new ideas
into old code (which I tend to do a lot of), things get a little
more interesting.
Did you read the page? The entire point of the example is that you
can rewrite the implementation without touching the main logic. I.e.,
using coroutines makes it *easier* to "retrofit new ideas into old
code".

Unfortunately no, not yet. I'm having some problems with my browser
the last few days. Happened once before, too, just trying to figure out
how I fixed it last time. Not that I can imagine it making a
difference to the issue at hand. A command that only accepts one
argument is going to have a hard time replacing one that requires two
or more, without an extra layer of wrapping. Whether it's a coroutine
or not is irrelevant.


--
Fredderic

November, n.:
The eleventh twelfth of a weariness.
-- Ambrose Bierce, "The Devil's Dictionary"

Debian/unstable (LC#384816) on i686 2.6.23-z2 2007 (up 63 days, 5:52)

.