+---------- On Sep 3, Brian Fenton said:
> I haven't been able to find much help on this on the web, so I'm turning to
> the mailing list.
> We've been getting terribly frustrated with scoping and passing variables
> around in TCL. I have a form that accepts a lot of inputs and am calculating
> stuff based on those inputs. My problem basically is I want to avoid passing
> all the variables around to all the procs (especially procs that don't even
> use them and just pass them on somewhere else!) or making a global copy of
> every variable.
Don't make a global "copy". Just make a global variable and use it.
> I think I don't understand uplevel, upvar
That's probably for the best, because using uplevel or upvar to do what
you describe would be very poor style. Here are some guidelines for
using upvar:
1. Only access variables in the caller's environment. In other words,
always pass "1" as the first argument to upvar. Otherwise you create a
dependency on the program's call path.
2. Only access variables using names that the caller passed in; do not
make assumptions about the caller's variable names. Otherwise you
create a dependency on the caller's variable names. For example, do
this:
proc goodExample {a b qName rName} {
upvar 1 $qName quotient
upvar 1 $rName remainder
set quotient [expr {$a / $b}]
set remainder [expr {$a % $b}]
}
Do not do this:
proc badExample {a b} {
upvar 1 quotient quotient
upvar 1 remainder remainder
set quotient [expr {$a / $b}]
set remainder [expr {$a % $b}]
}
3. Use upvar if you need to access an array in the caller's environment.
Example:
proc arrayValues {arrayName} {
upvar 1 $arrayName a
set values {}
foreach {name value} [array get a] {
lappend values $value
}
return $values
}
array set myarray {george bush william clinton ronald reagan}
set v [arrayValues myarray]
# $v = {reagan bush clinton}
4. Use upvar if you need to set multiple variables in the caller's
environment. Example:
proc unlist {list args} {
foreach value $list name $args {
if {![string length $name]} return
upvar $name var
set var $value
}
}
unlist {scrambled corned buttered} eggs beef toast
# $eggs = scrambled
# $beef = corned
# $toast = buttered
5. Don't use upvar for anything else.
Here are some guidelines for using uplevel:
1. Only access the caller's environment. In other words, always pass
"1" as the first argument to uplevel. Otherwise you create a dependency
on the program's call path.
2. Only run code in the caller's environment that the caller provided,
or code that accesses no local variables. Otherwise you create a
dependency on the caller's variable names.
3. Only use uplevel to create new control structures. For example:
proc try {block args} {
if {[llength $args] % 3 != 0} {
error "usage: try block [pattern resultvar block] ..."
}
set code [catch { uplevel 1 $block } result]
if {$code != 1} {
return -code $code $result
}
foreach {pattern name handler} $args {
if {[string match $pattern $result]} {
upvar 1 $name var
set var $result
uplevel 1 $handler
return
}
}
error $result $::errorInfo $::errorCode
}
try {
expr {1 / 0}
} "divide by zero" e {
puts "got divide-by-zero error"
} * e {
puts "unexpected error: $e"
}
> Here's my problem:
> If I use uplevel around a chunk of code, I find it ignores parameters passed
> into the procedure.
>From the manual page:
"The uplevel command causes the invoking procedure to
disappear from the procedure calling stack while the
command is being executed."
Since the procedure is no longer on the stack, its variables are
inaccessible.
> test2 is defined as:
> proc test2 {w} {
> uplevel {
> set result [expr w + x +y +z]
> return $result
> }
> }
>
> This proc breaks because w does not exist on the level above.
No, it doesn't. It breaks with this error message:
syntax error in expression "w + x +y +z"
The problem here is that you didn't include the "$" symbol in front of
each variable name. In the future, please cut and paste the actual code
and error messages instead of making up new code and omitting the error
messages.
> I then tried to use upvar:
> proc test2 {w} {
> upvar w w
> uplevel {
> set result [expr w + x +y +z]
> return $result
> }
> }
> This works fine if I call test2 once,
No, it doesn't. It breaks on the first invocation with this error
message:
variable "w" already exists
This is because upvar is trying to create a variable named "w" but you
already have a variable named "w" as a formal argument.
> My colleague came up with the following fix using namespaces which seems to
> work - he redefines the ACS procedure set_form_variables.
> (apologies to non-ACS people for including ACS code here ;-) )
Using a namespace is ok, if you are trying not to deviate too much from
the ACS design. The ACS design is bad because it pollutes the namespace
with arbitrary variable names. The ACS should have been designed from
the start to put the form variables into a single array instead of into
arbitrary variables in the caller's environment.
However, I think the code you pasted has a serious flaw: it doesn't
clear out existing namespace variables. AOLserver does not clear
namespace variables after each request, so if you set any, you need to
clear them yourself. Otherwise you'll have random-seeming bugs because
sometimes you'll have variables with values left over from previous
requests.
> Is this the best solution? I don't like having to refer to the namespace
> everywhere I need to access x, y and z. Surely there must be a solution
> using uplevel etc.?
Not really.
rob