Updated 2012-08-18 02:59:41 by RLE

SS 2004-01-20: Let creates a new lexical scope for a list of variables. The command takes two arguments, a list of variables names, and a script. Let will execute the script in a context where the specified list of variables can be used without to interfere with variables with the same name outside the script.

Larry Smith Like the idea, hate the name. "let" has a well-established history (and is available in Tcl already: let) and it doesn't jibe with creating a temporary scope. I suggest "own", "without" or "protecting", rather than let. (RS calls it with in Block-local variables).

An example:
 set var 10
 let var {
    set var 5
    puts $var; # Will output 5
 }
 puts $var; # Will output 10

The output of the script will be 5 and 10: what happens with the variable *var* inside the let's script is not reflected outside.

For default, the variables specified as the first argument of the command, does not exists in the context of the script. So the following script will cause an exception:
 set foo 1
 set bar 2
 let {foo bar} {
    puts "$foo $bar"
 }

Because the variables *foo* and *bar* don't exist in the context of the let's script. To initialize the variables directly it's possible to use a special form where instead of the name of the variables, a two-elements list with the name and the value for the initialization is passed.
 let {{foo 2} {bar 10}} {
    puts [expr {$foo+$bar}]
 }

Note that let performs subst substitution against the intializer, so you can write:
 set x 5
 let {{foo $x} {bar [llength $x]}} {
    # some script
 }

For what let is useful in the real world? let can be used to rewrite very complex procedures in a way that makes more simple to understand where variables are used across the code. I find it useful for debugging, expecially at top-level, when I need to add some code that may help to track a bug but I'm not sure if I can use a given variable name without to break something.
    # let - Creates a new lexical scope for a list of variables
    #
    # usage: let varNamesList body
    #
    # Copyright(C) 2003 Salvatore Sanfilippo <antirez@invece.org>

    proc let {vars body} {
        array set saved {}
        set notsetvars {}
        foreach var $vars {
            if {[llength $var] == 1} {
                if {[uplevel [list info exists $var]]} {
                    set saved($var) [uplevel [list set $var]]
                    uplevel [list unset $var]
                } else {
                    lappend notsetvars $var
                }
            } elseif {[llength $var] == 2} {
                foreach {name default} $var break
                if {[uplevel [list info exists $name]]} {
                    set saved($name) [uplevel [list set $name]]
                } else {
                    lappend notsetvars $name
                }
                uplevel [list set $name [uplevel [list subst $default]]]
            } else {
                error {bad variables list for [let]}
            }
        }
        set result [uplevel $body]
        foreach name $notsetvars {
            if {[uplevel [list info exists $name]]} {
                uplevel [list unset $name]
            }
        }
        foreach {name value} [array get saved] {
            uplevel [list set $name $value]
        }
        return $result
    }

    ### Example usage ###

    set h hello
    set a 10
    set b 4
    let {{a 3} {b 6} {h {[string length $h]}}} {
        puts $h
        puts $a$b
    }
    puts $a
    puts $b
    puts $h

    let i {
        for {set i 0} {$i < 2} {incr i} {
            let i {
                for {set i 5} {$i < 7} {incr i} {
                    puts $i
                }
            }
        }
    }