multi-argument set

Richard Suchenwirth 2005-12-12 - In the Tcl chatroom, CMcC said:

 You know, in retrospect, I think [set x a b c] should be equivalent to [set x [list a b c]]

Well, Tcl is where wishes come true :) Here's my experimental code that overloads set, and seems to work fine, as far as I've tested. I had to reuse the _var name for upvar, so the error message comes right if the variable doesn't exist yet:

 rename set _set
 proc set {_var args} {
    upvar 1 $_var $_var
    _set ll [llength [info level 0]]
    if {$ll==3} {
        _set $_var [lindex $args 0]
    } elseif {$ll>3} {_set $_var $args}
    _set $_var
 }

Lars H: Ehh... Why this insanely complicated solution? The user-supplied name for a local variable is just asking for trouble -- consider what happens for [set ll]!! The straightforward solution is rather:

 proc set {varname args} {
    upvar 1 $varname var
    if {[llength $args] == 0} then {
       return $var
    } elseif {[llength $args] == 1} then {
       _set var [lindex $args 0]
    } else {
       _set var $args
    }
 }

RS: It is to avoid the misleading error message below - but I agree that variable ll opens a vulnerability, so your code is safer.

 % set foo
 can't read "var": no such variable

Ingemar H: The above small problem is easily solved by this:

    if {[llength $args] == 0} then {
      if {[info exists var]} {
        return $var
      } else {
        _set $varname
      }

The distinction between $ll==3 and $ll>3 is necessary so this frequent case comes right:

 set something {}

Testing:

 % source myset.tcl
 % set foo
 can't read "foo": no such variable
 % set foo 1
 1
 % set foo
 1
 % set bar 2 3 4
 2 3 4
 % set bar
 2 3 4
 % set grill {{}}
 {}
 % set grill
 {}
 % set nix {}
 % set nix
 %

So far it looks as expected. But bytecode compilation may lose performance if a fundamental like set is pulled from under its feet...


AMG: While I appreciate this demonstration of Tcl's power to modify itself, I think it's ill advised to make this particular change. There's no way to distinguish between $x having been set to a single value that happened to be a list versus having been set via multiple arguments. You may say that there's no need to distinguish, since these two operations are defined to be equivalent. But what if the multiple arguments came as the result of {*} or its predecessor [eval]? And what if the "multiple" arguments happened to be only one argument after expansion? Now you're in trouble, if this first and only argument is not a well-formed list (e.g. has an unmatched open brace) or is a well-formed list with [llength]!=1? Just use ordinary [set] with [list], and avoid all the headaches.