use_refs

Richard Suchenwirth 2005-12-15 - A colleague wanted some sugar for upvar variables to pass by reference, but as the use of xproc prevented the names of such procedures to end up in tclIndex, we cooked up (it was lunch) this minimally invasive version:

 proc use_refs {{char &}} {
    foreach v [uplevel 1 {info locals}] {
        if [string match $char* $v] {
            uplevel 1 "upvar 1 \${$v} [string range $v 1 end]"
        }
    }
 }

I insert here a somewhat different (and not equivalent) approach:

 proc refs {args} {
    foreach var $args {
       # use an K-combinator (in form of [lindex [list $x $y] 0]) to 
       #  pass the parameter name to upvar and unset it in time before
       #  upvar creates an equally-named link.
       set lvar [list $var];# to shorten the following line a little
       uplevel 1 "upvar 1 \[lindex \[list \[set $lvar\] \[unset $lvar\]\] 0\] $lvar"
    }
 }

This variant will accept a list of variablenames, which it assumes to be names of those parameters, that will be replaced by the linked variables they previously contained as values... sounds quite complicated, but isn't:

 proc myappend {var1 var2 elem} { refs var1 var2; append var1 $elem; lappend var2 $elem }

The remainder of this page deals with the original approach, so if you like to comment on the "refs", then please do so above this line.

That's all. This command is preferrably called first inside a proc, and upvars all arguments that begin with a specific character, the default being "&" - it runs code like

 upvar 1 ${&foo} foo

in the caller's scope. Testing:

 proc test_refs {a &b} {
    use_refs
    puts a=$a,b=$b
    set b new_value
 }
 % set bar 42
 42
 % test_refs foo bar
 a=foo,b=42

So the values of a (by value) and b (by reference) are readable; and the side effect of changing b in the caller did also happen:

 % set bar
 new_value

MG offers an alternative on the same day (Dec 15 '05) which differs slightly from the above - it probably won't be of much use, but it was quite interesting to write :) This code allows passing by reference in the actual call to the proc, rather than in the proc's definition, by overloading the proc command. For example:

  % proc test_refs2 {baz} {
    set baz "$baz !"
  }
  % test_refs2 "hello"
  hello !
  % set foo "bar"
  bar
  % test_refs2 $foo
  bar !
  % set foo
  bar
  % test_refs2 &foo
  bar !
  % set foo
  bar !

And the code:

 rename proc _proc
 _proc proc {name args body} {
 
   set prefix ""
   foreach x $args {
       set varname [lindex $x 0]
       set string {
   if { [string match "&*" $XNAME] } {
        upvar 1 [string range $XNAME 1 end] "XNAME[unset XNAME]"
      }
 }   ;# set string ...
       set string [string map [list XNAME $x] $string]
       append prefix $string
    };# foreach
     
   uplevel 1 [list ::_proc $name $args $prefix$body]
 };# _proc

I'm not sure whether that uplevel at the end is necessary or not, but it's intended purpose is to make sure new procs come out in the namespace the proc command was called from. Whether it works or not is untested.