Version 46 of set

Updated 2010-05-14 19:05:26 by hat0

set - Read and write variables

http://www.purl.org/tcl/home/man/tcl8.5/TclCmd/set.htm

set varName ?value?

Returns the value of variable varName. If value is specified, then set the value of varName to value, creating a new variable if one doesn't already exist, and return its value. If varName contains an open parenthesis and ends with a close parenthesis, then it refers to an array element: the characters before the first open parenthesis are the name of the array, and the characters between the parentheses are the index within the array. Otherwise varName refers to a scalar variable. Normally, varName is unqualified (does not include the names of any containing namespaces), and the variable of that name in the current namespace is read or written. If varName includes namespace qualifiers (in the array name if it refers to an array element), the variable in the specified namespace is read or written.

If no procedure is active, then varName refers to a namespace variable (global variable if the current namespace is the global namespace). If a procedure is active, then varName refers to a parameter or local variable of the procedure unless the global command was invoked to declare varName to be global, or unless a variable command was invoked to declare varName to be a namespace variable.

rdt Or unless upvar provides an alias to "varName".


[ A web page which references the various valid forms that value can take, such as

        set a unquotedstring
        set b "substitution processed string"
        set c {quoted literal string}
        set d [results of a command]

as well as a discussion of Tcl string escapes such as the ever popular \n , \t , and those oh so hard to figure out \uHEXSTRING as well as other things, can be found at Dodekalogue, which is as close to the http://www.tcl.tk/man/tcl8.5/TclCmd/Tcl.htm as we get.

Lars H: That sounds like the Endekalogue, a.k.a. the Tcl.n man page [L1 ], to me. ]


The reading form set x (without value) works the same as $x, so you could write Tcl scripts without a single dollar sign. Its advantage is that it can be nested, so cascaded dereferencing is possible:

 set foo 1
 set bar foo
 puts [set $bar] ;# retrieves indirectly the value of foo
 puts [set [set bar]] ;# equivalent, but less readable

See also "An essay on Tcl dereferencing" [L2 ]


As Tcl has no reserved words, you can even write your own set command (make sure its effects are like the original, otherwise most Tcl code might break). For instance, this version says what it does, on stdout:

 rename set _set
 proc set {var args} {
   puts [list set $var $args]
   uplevel 1 _set $var $args
 } ;#RS

Might help in finding what was going on before a crash. When sourced in a wish app, shows what's up on the Tcl side of Tk (as long as you can find the program's stdout). Pretty educative!


How can I do a double indirect? - Why doesn't $$var work?

There is one and only one level of substitution possible with every pass through the interpreter. Also, when doing variable substitution, the interpreter finds the dollar sign and then takes everything following it up until the next invalid character (where invalid is defined as anything other than a letter, digit, or underscore) as the name of the variable - well, that is, unless it finds array notation or the ${varname} form.

In the case of $$var, the character after the first dollar sign is an invalid character (another dollar sign), so there is no variable name and variable substitution is not performed (the dollar sign is left as is) and scanning starts again for any dollar signs and a following variable name. It is immediately found at that second dollar sign, the substitution is performed, and scanning for dollar signs resumes with whatever was after the variable name. Since there isn't anything else, substition is done for this pass through the interpreter (remember it's only done once).

The eval command runs its arguments through the interpreter, so you could use eval to cause a second pass through the interpreter, and thus, have $$var work:

    % set a 5
    5
    % set var a
    a
    % puts $$var              ;# This doesn't work
    $a
    % eval puts $$var         ;# This does  - but it's dangerous
    5

However, if the contents of var contain any special characters (e.g. whitespace, semicolon) you'll run into problems.

A better method is to take advantage of the behaviour of the set command when given only one argument, and combine command substitution with variable substitution:

    % puts [set $var]         ;# This works safely
    5

or, in fact, you could use just command substitution (which is performed once for each [ ] pair):

    % puts [set [set var]]    ;# as does this
    5

Similarly, to print the values of var1, var2, and var3:

    set var1 3.14159
    set var2 hello
    set var3 13
    foreach num {1 2 3} {
        puts "var$num = [set var$num]"
    }

will output:

    var1 = 3.14159
    var2 = hello
    var3 = 13

The upvar command can also be used to derefence variables.

In addition, the interpreter includes the command subst which can be used to perform substitutions.

Note that all of the above applies to array variables also.


Clif Flynt: When I'm teaching a class, I point out that

  set set set

is a valid command, and that

        set x set
        set y a
        set z b
        $x $y $z

is a valid Tcl command to assign the value b to variable a. Looking at this makes the students stop and think. And stare at me like I'm crazy.


MSW For those who dislike doing multiple assignments at once with foreach in the style

 foreach {a b c} {1 2 3} {}

here is a multiple argument set (with help from RS):

 if {[info procs tcl::set]=={}} then {rename set tcl::set}
 proc set {args} {
  switch [llength $args] {
    0 { return -code error {wrong # args: should be set varname ?newvalue? ?varname ?newvalue?? ...}}
    1 { return [uplevel "tcl::set [lindex $args 0]"] }
    2 { return [uplevel "tcl::set [lindex $args 0] [lindex $args 1]"] }
    default {
      uplevel "tcl::set [lindex $args 0] [lindex $args 1]"
      return [uplevel "set [lrange $args 2 end]"]
    }
  }
 }

Use like this

 % set a 1 b 2 c 3
 => 3
 % set d 15 e [expr int(100*rand())] c
 => 3
 % list $a $b $c $d
 => 1 2 3 15

Duoas The foreach..break idiom is so prevalent in Tcl, and so common, that experienced Tcler's automatically recognize it as a set replacement idiom:

  set ls [list 1 2 3]
  foreach {var1 var2 ...} $ls break

However, something about it has always bothered me: I just dislike programming to the side-effects. I've submitted a TIP [L3 ] [is that the right TIP?] to extend the set command such that it can assign to multiple variables, but not as above, where the values to assign are interleaved with the variable names. Usually the values come from a list and the above implementation would require zipping variable names and values together before use, then evaling or expanding. It doesn't obviate the need to use that silly foreach..break idiom.

Littered throughout my own code is the use of this simple little routine:

  proc sets args {
    set names  [lrange $args 0 end-1]
    set values [lindex $args end]
    uplevel 1 [list foreach $names $values break]
    return [lrange $values [llength $names] end]
    }

And an example of use:

  sets x0 y0 x1 y1 [.canvas coords my-rectangle-tag]

This is much more Tclish and intuitive. Note also that you can get what is not used for later use (foreach requires you use it now or not at all):

  set ls [sets a b $ls]
  # do something with $a and $b, and maybe sometime later with the rest of $ls

A more concrete example:

  % set ls [sets a b {1 2 3 4 5}]
  3 4 5
  % puts $ls
  3 4 5
  % puts $b
  2

As per my TIP submission, the set command is easily extended to have such functionality without slowing it down when used as per the current specification (well, except one or two processor instructions when errors occur). When used in the extended form it is faster than using foreach, which has a lot of extra stuff to handle multiple, concurrent lists.


MJ - in 8.5 we have lassign which is sets with the arguments reversed. The example above then translates to:

 % set ls [lassign {1 2 3 4 5} a b]
 3 4 5
 % puts $ls
 3 4 5
 % puts $b
 2

Duoas Me feels stupid for having missed that... I learned Tcl moving into 8.0 and I'm still a little behind in a lot of 8.5 improvements.

MJ - No need to feel stupid, Tcl 8.5 has a lot of new goodies, see Changes in Tcl/Tk 8.5.


Draft of text which might be easier to read for beginners to read.

The keyword set stamps a pattern onto a token.

Programs are machines which do not work with the grinding of physical gears, but rather they create and change information. Because of this, it's important to have something with which to pass around information between various parts of the machine. Most computer people call these tokens "variables." However, I prefer to use a typical English noun, instead of a word which is ordinarily an adjective ;-). In TCL, set is the tool you use to stamp these tokens with a pattern.

example:

 set variable "bananas were peeled"
 set a "oranges"
 set fruit "pears" 
 set money 1000

If you'd like, you can use braces there, instead of quotation marks... or, if you only have one word, you don't have to use any marks at all.

 set mountain kilimanjaro
 set phrase {What shall we do today?}

There is a difference between using braces and using quotation marks in longer bits of text. Quotations allow you to use tokens within the text itself, or even embed tcl instructions.

like this:

 set phrase "I want to journey to $mountain"
 set phrase "Last year I went to Africa [expr 1+1] times"

Not only can you stamp a pattern onto a token with set - you can also get out the magnifying glass and read that token, using set. Some people will recognize this method from times when they have used msdos.

look here:

 set fruit


See also: