Version 16 of multi-arg coroutines

Updated 2010-04-15 11:59:08 by CMcC

I wish to consider amending "TIP 328 Coroutines" [L1 ], along these lines:

Invocation of a coroutine should accept multiple arguments, those arguments should be returned to the coroutine's yield as a list of actual parameters.

The current implementation forbids invoking a coroutine with more than one argument. I have found, in practice, that many of my coroutine invocations are naturally like other command invocations, and take multiple arguments.

The case for providing multi-arg'd coroutines is:

  1. coroutines should be able to emulate any command, not just any single-arg'd command [generality]
  2. to implement single-arg's coroutines in multi-arg'd coroutines is trivial - nothing needs to be done. The converse (implementing multi-arg'd coroutines under coroutine is inefficient and difficult. [increased expressive power]
  3. there is no sound reason that the invocation of a coroutine should not resemble that of any other command [principle of minimal surprise]

For these reasons, the core should be modified to accept multiple actual parameters to a coroutine invocation, ::yield should be modified to resemble ::yield2 below, and a new interface ::yieldm should be created to return the actual parameter list as it's passed.

The only arguments in favour of single-arg'd coroutine I've heard are:

  1. extra cost in unpacking the list of actual parameters in ::yield. Total cost is 1 lindex in C. I consider this to be insignificant compared to the cost of passing more than one argument, which is the more usual case in my experience.
  2. the potential that coroutine invocation might require options to control the invocation in ways simple command call doesn't. Examples, IIRC, were [$coro -terminate] which would be interpreted as an unconditional termination. I find this hypothetical need unconvincing, as it would be as easily served by other kinds of interface to the coroutine (much as info has been pressed into service to provide information about a coroutine, some other hypothetical command like [corocontrol $coro] could be used to control the coro.

Demonstration of point 2 [increased expressive power]

CASE 1: (counterfactual)

Assume a [Coroutine] which generates a command taking multiple args, to implement coroutine as we have it implemented:

[Coroutine] would require no wrapping or changes to function as coroutine does now. Only yield would have to change.

To provide precisely the same functionality as yield currently does it is necessary to strip off a single layer of list:

proc ::yield2 {value} {
    return [lindex [::yield $value] 0]
}

No other changes are necessary. More likely, one would define ::yield like that, and create a new ::yield-variant which returned the whole invocation arg list.

CASE 2: [Coroutine] in [coroutine] - implementing multi-arg'd coroutines over singe-arg'd coroutine

proc Coroutine {name command args} {
    set ns [namespace qualifiers $name]
    if {$ns eq ""} {
        set ns [uplevel 1 {namespace current}]
    }
    set name [namespace tail $name]

    set coco [::coroutine ${ns}::_C$name $command {*}args]
    trace add command ${ns}::_C$name delete "rename ${ns}::name {}"
    proc ${ns}::$name {args} {
        set name [lindex [info level 0] 0]
        set ns [namespace qualifiers $name]
        if {$ns eq ""} {
            set ns [uplevel 1 {namespace current}]
        }
        set name [namespace tail $name]
        
        ${ns}::_C$name $args
    }
}

The predominant cost in this is that of tracing intermediate commands to avoid leakage. Even if this were not the case, the cost of calling a proc to wrap the extra args is considerable. The only alternative is to wrap the args on *each* invocation.

One can provide variable assignment by signature (or Occam-like protocol):

proc entrypoint {value args} {
    uplevel 1 lassign [::yield $value] {*}$args
}

This is possible in current coroutine the same way, but requires the caller to form args into lists on each invocation.


jmn 2010-04-15 I totally agree. I was thoroughly dismayed by the single arg coroutine thing. It just seems to go against the grain of the 'Tcl way' - for no real advantage. If it was multi-arg'd it would present an interesting way to build some command alternatives along the lines of existing mechanisms such as 'interp alias'. Having to wrap it to achieve this is ugly enough to discourage this sort of innovation especially if the whole point of the innovation was to do so in a situation where dispatch performance matters.