+---------- 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

Reply via email to