[Richard Suchenwirth] 2001-03-01 - After a weekend without fun project, here's a little midnight study on local variables - inspired by reading on Scheme's ''let'' construct. I preferred the name ''with'' which sounded even more commonsense to me. Tcl variables are either global, or local to a procedure. You can shorten a variable's lifetime with ''unset''. In LISP/Scheme as well as in C (as unequal they otherwise are), you can have variables local to a block of code - in LISP enclosed by ''let'', in C by just bracing a block. And you can have that in Tcl too, as shall presently be demonstrated. The ''with'' command takes a non-empty list of variable names (with optional initial values, like a ''proc'' arglist) and a body which must refer to those variables, executes the body in caller's scope, and unsets the "local" variables afterwards. proc with {locals body} { set cleanup [list unset] foreach i $locals { set name [lindex $i 0] if [uplevel info exists $name] { return -code error "local variable $name shadows outer var." } lappend cleanup $name if [llength $i]==2 { uplevel 1 set $i ;# uplevel works like concat, so no eval } } set res [uplevel 1 $body] uplevel 1 $cleanup set res } # usage example and test code: set x 17 with {xa xb {xc 5}} { set xa [expr $xc*$xc] set xb [incr xa $x] puts xb=$xb } puts "now we have [info vars x*]" ;# should not show xa, xb, xc '''Discussion''': The handling of local variables with same name as one in outer scope is more strict than in C (where it may raise a warning) or Lisp (where it's no problem at all - the local temporarily supersedes the outer, which is later available unchanged). Since ''body'' is just ''uplevel''ed, any other treatment seemed dangerous or overly difficult - but feel free to improve on that... Likewise, specifying a local variable which is not set to a value in ''body'', or not giving any locals at all, raises an error on unsetting. As usual, another error is raised when retrieving a variable's value that has not been set before. How useful this is, is another question. Good efficient code should be written inside procedures, and variables inside procedures are local anyway unless explicitly globalized. ---- [[Anonymous]] Here's my take on let, where local variables in the block do supersede the ones at higher levels. proc let {vars body} { uplevel [subst -nocommands { namespace eval __tmp__ { variable __vv {} __var {} __value {} if {[namespace parent] != "::"} { foreach __vv [info vars [namespace parent]::*] { # variable [namespace tail \$__vv] [set \$__vv] # using variable just copies the var, we want it to reference the other var upvar #0 \$__vv [namespace tail \$__vv] } } foreach __vv [list $vars] { foreach {__var __value} \$__vv break variable \$__var \$__value } unset __vv __var __value $body namespace delete [namespace current] } }] } As a bonus, since it uses namespace you get block-local procedures also. I imagine this still isn't too efficient, creating and destroying the namespace each time through. The quoting isn't ''too'' ugly here, especially considering what its doing.. but is there a clean way to get out of [Quoting hell] in this case? oops, there was a bug - using ''variable'' would copy the variables in the enclosing namespace to the current rather than making the same variables visible. ''upvar'' fixes that. ---- [RS] 2005-03-28 - Years later, revisiting this page, I'm surprised how overengineered my code above (and also the anonymous answer) was. I've come to think that Tcl's strict scoping rule (in a proc, every variable is local, except if declared otherwise) is a good thing. So I thought up lightweight [lambda]s in two flavors (different only by the setting of the environment which "play by the rules" and therefore are of course very simple: proc with {argl body args} { if {[llength $argl]!=[llength $args]} { error "wrong #args, must be: $argl" } foreach $argl $args {} eval $body } #-- Testing: % with {x y} {expr {hypot($x,$y)}} 3 4 5.0 with list {lindex $list 0} {a b c d} a if 0 {A concrete use case for ''with'' might be where you want to '''curry''' function calls, but the order of arguments does not put the really wanted one in the end:} interp alias {} first {} with L {lindex $L 0} puts [first {x y z}] % interp alias {} hypot {} with {x y} {expr {hypot($x,$y)}} hypot % hypot 3 4 5.0 if 0 {Come to think, we can now code without [proc] - turning every command we write into an [interp alias]ed ''with''... The 'let' variation just takes an alternating {var val var val..} list ("environment"), which in the future might well be a [dict]:} proc let {bindings body} { foreach {var val} $bindings {set $var $val} eval $body } #-- Testing: puts [let {x 3 y 4} {expr {hypot($x,$y)}}] ---- [Local procedures] - [Arts and crafts of Tcl-Tk programming]