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 the uplevel
command) 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 to 1
, 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). All upvar
does 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 global
and namespace upvar
commands do something very similar, but with different lookup strategies. (Also, global
assumes that the global and local names should match.) That's also part of what the variable
command does.
In your code sample, the upvar 2
is to get past the cofunc
procedure. I think it would be clearer if it was upvar #0
, or if cofunc
used tailcall $callback
so a simple upvar
could be used. And I'm pretty sure that the nbr
accessed in add
is not the same one as in func
, as the latter's stack frame will be gone by then.