Version 10 of multi-arg coroutines

Updated 2010-04-15 05:08:43 by CMcC

I wish to consider amending "TIP 328 Coroutines" http://www.tcl.tk/cgi-bin/tct/tip/328.html , 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. [increase in 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 this reason, 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.

-- Demonstration of point 2 --

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

Counterfactual side-benefits: yield stays exactly as it is.

Yield to populate args becomes simpler.

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