set

Difference between version 90 and 91 - Previous - Next
'''`[http://www.tcl.tk/man/tcl/TclCmd/set.htm%|%set]`''', a [Tcl Commands%|%built-in] command, reads and writes variables.



** Synopsis **

    :   '''set''' ''varName'' ?''value''?



** Documentation **

   [http://www.tcl.tk/man/tcl/TclCmd/set.htm%|%official reference]:   



** See Also **

   [array]:   

   [?set]:   

   [expr]:   

   [lset]:   Can be used as a variant of `set` that returns an error if the variable doesn't already exist.

   [trace]:   

   [unset]:   

   [take]:   

   [foreach]:   often used to set multiple variables in one stroke



** Description **

`[set]` [return%|%returns] the value of the variable named ''varName'', or if ''value'' is
given, stores that value to the named variable, first creating the variable if
it doesn't already exist.  When creating a variable, `set` resolves the name
relative to the current namespace.

If ''varName'' is not [namespace%|%fully qualified], `set` searches first for a
variable having that name in the [namespace current%|%current namespace], and
then in the [global] namespace.  This may lead to [Dangers of creative writing%|%inadvertent modification of a
variable] in the [global] namespace.  To ensure that this doesn't happen, use
`[variable]` to first declare a variable in a namespace before using `set` to
modify that variable.

If ''varName'' contains an open parenthesis and ends with a close parenthesis,
then it refers to a variable in an [array]:  The characters the first open
parenthesis are the name of the array, and the characters between the first
parenthesis and the parenthesis at the end of the word are the name of the
variable in the array.

If no procedure is active, then ''varName'' refers to a namespace variable,
which may be in the global namespace.  If a procedure is active, then
''varName'' refers to a parameter or local variable of the procedure.  Commands
such as  `[global]`, `[variable]`, or `[upvar]`, can be used to link other
variables into the local scope of the procedure.

`[set]` can entirely replace [dodekalogue%|%variable substitution].



** Basic Examples **

======
set greeting hello
set greeting ;# ->hello
set person(name) bob
set person(name) ;#-> bob
set (name) bob ;# the is an array variable, where the array name is the empty string
set (name) ;#-> bob
set {} hello 
set {} ;#->hello
======



** Gotcha: Variable Resolution **

See Also, [Dangers of creative writing]

[ulis] 2003-11-16:

Try this:

======
set ::version 1.0
namespace eval ns {
    set version 0.9
}
puts $::version
catch {puts $ns::version} msg
puts $msg
======

Result:

======none
0.9
can't read "ns::version": no such variable
======

Explanation:

As stated in the Tcl manual: ''if the name does not start with a :: (i.e., is
relative), Tcl follows a fixed rule for looking it up: Command and variable
names are always resolved by looking first in the current namespace, and then
in the global namespace. ''

In the above script the variable ''version'' wasn't defined inside the
namespace so Tcl used the existing global variable.

To avoid that, ''always'' declare namespace variables with the '''variable'''
command:

======
set ::version 1.0
namespace eval ns {
    variable version 0.9
}
puts $::version
catch { puts $ns::version } msg
puts $msg
======

New result:

======none
1.0
0.9
======
I ([ulis]):  I think that it would be better if the search in the global space was
used when refering a variable and avoided when setting a variable.



** Setting Multiple Variables at Once **

[MSW]: For those who dislike doing multiple assignments at once with
`[foreach]` in the style

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

and who don't want to use `[lassign]`, 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 [list tcl::set [lindex $args 0]]]}
        2 {return [uplevel [list tcl::set [lindex $args 0] [lindex $args 1]]]}
        default {
            uplevel [list tcl::set [lindex $args 0] [lindex $args 1]]
            return [uplevel [list set [lrange $args 2 end]]]
        }
    }
}
======

Use like this

======none
% 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 [http://www.tcl.tk/cgi-bin/tct/tip/58%|%TIP
#58] to extend `set` 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 [eval]ing
or [expand]ing. 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, `set` 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 `set` with the arguments
reversed. The example above then translates to:

======none
% 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].



** Double Indirection **

See also: [http://www.phaseit.net/claird/comp.lang.tcl/tcl_deref.html%|%An
Essay on Tcl Dereferencing]

In some languages, notably [PHP], an additional dollar sign can be added to a
variable to achieve double-indirection. e.g. `$$var`.  Tcl doesn't support such
syntax, but `set` can be used to the same effect:

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

Or:

  proc $ {var {# 1}} {for {set i 1} {$i<=${#}} {upvar $var v;set var $v};uplevel return $v}   ;# multi indirection, which tcl doesn't support

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

The following example, which uses `set` instead of `$`, is equivalent:

======
% 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]"
}
======

output:

======none
var1 = 3.14159
var2 = hello
var3 = 13
======

`[upvar]` can also provides access to to other variables, even when they are
in the same scope:

======
set var1 hello
upvar 0 var1 var2
======

`[eval]` could also be used to achieve double indirection (but there are
major caveats):

======
% 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
======

One caveat is that if `$var` has a value containing any special characters
(e.g.  whitespace, semicolon), they'll get interpreted, and where this is
inadvertent, could result in an error or an [Injection Attack%|%exploit].



** An Alternative to `[return]` **

`[proc]` returns its last evaluated result, so it's a common [idiom] to use
`[set]` instead of `[return]` as last command.

======
set res
======

======
return $res
======

Some [Tcl] style guides recommend using the explicit `[return]` alternative.
The rationale is is that using an explicit `[return]` guards against
inadvertantly adding addtional code after the `[set]` command.

Before `[return]` got byte-compiled (i.e., before Tcl 8.4), the `[set]`
idiom was faster than the `[return]` idiom for returning a variable value.
This is no-longer true.



** A Verbose `set` **

[RS]:

As Tcl has no reserved words, you can even write your own set command (make
sure its effects are like the original, or most Tcl code might break).  For
instance, this version reports its actions on [stdout]:

======
rename set _set
proc set {var args} {
    puts [list set $var $args]
    uplevel 1 _set $var $args
}
======

This 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]).



** Swap the Contents of Two Variables **

Thanks to [copy-on-write] and the special-case of appending an empty string,
this is efficient:

======
set a one
set b two
set a $b[set b $a; lindex {}]
======

In the previous example, `[lindex]` is used as the [identity function].

[AMG]: Can also take advantage of the fact that [[[list]]] with zero arguments returns empty string:

======
set a $b[set b $a; list]
======

** Bug: `[array]` and $ **

As of Tcl [Changes in Tcl/Tk 8.6.3%|%8.6.3], `set myarray($) one` results in an
array variable named the [empty string] because `set` interprets `$`, even at
the end of `index`, as variable substitution, and removes it.  Meanwhile,
[variable substitution] handles the same syntax just fine:

======
% set a($) one
one
% puts $a($)
can't read "a($)": no such element in array
% set {a($)} two
two
% puts $a($)
two
% 
======

[AMG]: This is extremely confusing to me.  [[set]] has no need to interpret `$`, ever.  If a `$` survives the Tcl interpreter and manages to be part of [[set]]'s first or second argument, it should be treated literally.  The above `set {a($)} two` command demonstrates this fact.

Let me attempt to reproduce using Tcl 8.6.1::

======
% set a($) one; array get a
{} one
% set {b($)} one; array get b
{$} one
% set c(\$) one; array get c
{$} one
======

So yeah, the bug is there.  But what's going on?  Let's ask [[[list]]] what the arguments are:

======
% list set a($) one
set {a($)} one
% list set {b($)} one
set {b($)} one
% list set c(\$) one
set {c($)} one
======

Uh oh.  If [[list]] gives the same results regardless of quoting, it means the different quoting methods (or lack thereof) are equivalent, so [[[set]]] should not be able to discern.

Okay, another test:

======
% proc myset {args} {tailcall set {*}$args}
% myset d($) one; array get d
{$} one
======

Wow.  This seems to indicate the bug is in the [bytecode] compiler for [[set]].

Yet another test, to bypass any attempts at optimization:

======
% interp hide {} set
% interp invokehidden {} set e($) one; array get e
{$} one
======

Pretty much confirms it.

[PYK] 2015-03-26:   That was a nice writeup.
[http://core.tcl.tk/tcl/tktview?name=0c043a175a%|%This bug] is fixed in
[Changes in Tcl/Tk 8.6.4%|%8.6.4], but I'd like to see this analysis preserved
on some page about investigating the behaviour of the interpreter, maybe with
some additional notes about the role of tailcall.



** Why [C] Programmers Hate Tcl Variable Scope Resolution! **

[JoGusto]: Yes, that is meant to be provocative. But, it is a serious problem for
software developers who have spent years, or decades, programming in languages such
as C, where a global variable is just that: GLOBAL!

It is absolutely NOT intuitive to the average C programmer that all global variables
disappear from scope (YES! Ridiculous!) when you enter a [proc]... in C, a global
remains in scope, with no special syntax required, unless that global name is 
hidden because of a more enclosed declaration inside a block. Even then, the global
is accessible (in C++ at least) via the same notation Tcl uses: the double colon.

This scope resolution problem is just really abhorrent to those of us who "Think in C"
and try valiantly to script in Tcl, only to repeatedly bump up against that old bugaboo:
"I forgot the double colon! My global variable is "undefined"" It is ridiculous to 
have to repeatedly declare variables that are global within the local scope just to
access them.

Yea, I know it's way too late to change, but its a common complaint I get from many 
people about Tcl, and Python, that "they got the whole global variable thing WRONG..."
and it's definitely something that trips up a lot of people who are not used to it.

[dbohdan] 2015-02-16: OTOH, having to explicitly declare your globals prevents accidental mutation of global state. This is important because in Tcl unlike in C variables are generally set without prior declaration and the compiler won't catch a redeclared variable for you.

[[name redacted]]: there are always differences going from programming language A to programming language B. The correct way to deal with them is to adapt, not to demand that B be reworked to more closely resemble A. It's quite too late to change the exclusive-scope model that Tcl uses, but more importantly it would be a bad idea since it works well. And if you're going to put C forward as an example of handling variables in a better way, I'll just whisper "`extern`..." `;)`

[AMG]: "Global" means a variable ''can'' be reached from any stack level.  That is not an abuse of the term.  The difference between Tcl and many other languages is whether or not the global frame is ''automatically'' searched if the variable can't be found locally.

[PYK] 2015-03-11:  "This thing in language X is abhorrent to those of us who
think in language Y!".

[http://i.imgur.com/fUTNN.gif%|% width=50 height=50]

Tcl design decisions often come down on the side of allowing programmers to
have it their way,  You want to access global variables from inside a function?
No problem.  You want zero pollution from any other scope?  Also no problem.
Or maybe you specifically want to access a certain variable [upvar%|%two levels
up the call stack].  Go right ahead.  If you want [C]-style variable scoping,
write a command that gives you that, and use that command to read or write
variables.  Variable scoping rules are
[http://en.wikipedia.org/wiki/Hygienic_macro%|%notoriously hard to get right],
but fortunately, Tcl did (except for the [dangers of creative
writing%|%creative writing] issue).

----
'''[Jnk] - 2016-12-02 23:01:07'''

How can we use set like read command in bash?
Eg. In BASH shell using the read command as

======none
read -e -p "Enter your name:" -i "Bob" name
======

[AMG]: Use [[[gets]]].

======
puts -nonewline "Enter your name:"
flush stdout
set name [gets stdin]
======

There's also [tclreadline] which may be of service for advanced editing, but I've never tried it.

<<categories>> Tcl syntax | Arts and Crafts of Tcl-Tk Programming | Command