The following strained example, in attempt to match my real situation, completes successfully but not the way I'd like it to; nor do I understand why or can find an answer in one of my books.
I can see that proc func was called from the global level and, in turn, initially called proc cofunc; and, perhaps, that sets what upvar will use. But proc finalize calls the coroutine again and passes the variable name nbr to cofunc--which calls add. In this respect add "seems" to be two levels from finalize.
The upvar 2 $varName n in proc add is the variable name nbr and takes its value from the global variable of that name. I'd like to get it from proc finalize (so 15, not 85) which seems to be two levels away from proc add but apparently is not even in the list of caller contexts.
I assume [yield] is the reason; but would you please explain why and, more importantly, if the value in proc finalize was not a number but a "large" value that shouldn't be passed around, is it possible to upvar or uplevel from a coroutine?
Thank you for considering my question.
proc cofunc {callback vals} {
set var [yield]
$callback $vals $var
}
proc func {callback vals} {
set nbr 22
coroutine coro cofunc $callback $vals
}
proc add {vals varName} {
upvar 2 $varName n
chan puts stdout "varName: $varName; n: $n"
# => varName: nbr; n: 85
chan puts stdout [expr {[lindex $vals 0]+[lindex $vals 1]+$n}];
# => 115
}
proc finalize {nbr} {
coro nbr
}
set nbr 85
func add {10 20}
after 500 finalize 15
after 1000 set forever 1
vwait forever
The
upvar(like theuplevelcommand) is defined in terms of the stack. The number argument says how many steps up the stack to take (or you can prefix the number with#to count down from the other end; that's a bit less common); it defaults to1, the caller of the current procedure. There are two sorts of stack frame, one for procedures (and related entities, such as methods and lambda expressions) and another for direct namespaces (the global scope,namespace eval, and a few other things). Allupvardoes is go to the indicated stack frame, look up the named variable there, and make a local variable that is a link to it. By "link", I mean that the local variable is really a tagged C pointer to the implementation of the other variable. Once a linked variable is set up, access is very fast, as the slow part of access is resolving names to implementations, and local variables are usually compiled into indexed accesses.The
globalandnamespace upvarcommands do something very similar, but with different lookup strategies. (Also,globalassumes that the global and local names should match.) That's also part of what thevariablecommand does.In your code sample, the
upvar 2is to get past thecofuncprocedure. I think it would be clearer if it wasupvar #0, or ifcofuncusedtailcall $callbackso a simpleupvarcould be used. And I'm pretty sure that thenbraccessed inaddis not the same one as infunc, as the latter's stack frame will be gone by then.