How do you create constants in Tcl programs? There are, of course, several ways. ---- [LV]: Well, one way to create a ''constant'' in a Tcl program is to just code it. For instance, in the line of code: ====== set a "abc" ====== The string "`abc`" is a constant. Note, in fact, that the string need not be in quotes: ====== set b abc ====== also involves a constant string "`abc`". But what about numeric constants? Well, a decimal value can be set thusly: ====== set c 10 ====== or ====== set d "10" ====== and, when `$c` or `$d` are used in Tcl commands which expect numeric values, the value of the variable will be treated as the number '''10'''. Note that Tcl versions up through 8.4.1 treat a leading 0 as indicating that the numeric value is octal: ====== set e "010" ====== does not result in $e being equal to $d; instead, it is treated as being two less than $d. Are there ways to express numeric constants in other bases, such as binary or hexadecimal? ====== $ tclsh % set a 0xbad 0xbad % incr a 2990 % ====== So it appears that, with appropriate notation, one can get hexadecimal. For binary, one needs to do this: ====== proc fromBinary { digitString } { set r 0 foreach d [split $digitString {}] { incr r $r incr r $d } return $r } puts [fromBinary 0101001010101010] puts [format %x [fromBinary 0101001010101010]] ====== or ====== proc bits2int {bits} { #returns integer equivalent of a bitlist set bits [format %032s [join $bits {}]] binary scan [binary format B* $bits] I1 x set x } ====== ---- However, what if you want to create variables which, once set, can be ''guaranteed'' to retain their value? Obviously the guarantee is conditional, since in many/most cases, something that one can script in Tcl is going to be able to be unscripted with sufficient effort... ---- '''procedure constants''' Create a procedure for each constant, which returns the value. ====== proc FLAG1 {} { return 0x0001 } ====== You then access the constant by calling the procedure, like [[FLAG1]. This is easy to code, but gets clumsy when you have lots of constants. Another possibility is to use an array in one constant function, but this is fairly inflexible. ====== proc CONST { key } { array set constant { FLAG1 0x001 FLAG2 0x002 PI 3.14159 } return $constant($key) } ====== ---- '''readonly trace''' [Brent Welch] suggests using write variable [trace]s to implement read-only variables. But since the write trace fires ''after'' the variable value has changed, you need to keep a cache of the original value somewhere. [George Howlett] made a suggestion (at a Tcl conference tutorial) that read traces are your friend. He suggested a couple of very flexible procedures. (According to [Don Porter]'s c.l.t. post) ====== % proc _constant_read_trace {val name1 name2 ops} { upvar $name1 var set var $val } % proc constant {varName value} { uplevel [list trace variable $varName r [list _constant_read_trace $value]] } % constant PI 3.14159 % set PI 3.14159 % set PI 3; # Only in Indiana :) 3 % set PI 3.14159 ====== ---- However, note the following: ====== % set v [set PI 3] 3 % puts $v 3 ====== Thus, while PI continues to have a constant value, note that the result from the set ''appears'' as if PI had been given a different value. So one needs to be careful how one plans on using this code. ---- [RS]: Slightly modified the above, so attempts to vary a constant raise an error (it probably was one ;-): ====== proc _constant_trace {val name1 name2 ops} { upvar $name1 var if {$ops=="w"} { return -code error "constant $val may not be changed" } set var $val } proc constant {varName value} { uplevel [list trace variable $varName rw [list _constant_trace $value]] } % set PI 1.23 can't set "PI": constant 3.14159 may not be changed ====== ---- [RS] again: This raises no error, but keeps a constant with minimal code: ====== proc const {name value} { uplevel 1 [list set $name $value] uplevel 1 [list trace var $name w "set $name [list $value];#" ] } ====== Simple error-raising variation: ====== proc const {name value} { uplevel 1 [list set $name $value] uplevel 1 [list trace var $name w {error constant ;#} ] } % const x 11 % incr x can't set "x": constant ====== ---- [kruzalex] This prevents constant without raising an error too: ====== rename set _set proc set {var args} { if {[llength $args]!=0} { uplevel 1 [list _set $var $args] } else { return [uplevel 1 [list _set $var]] } } proc _constant_read_trace {val name1 name2 ops} { upvar $name1 var uplevel 1 [list _set $name1 $val] } proc constant {varName value} { uplevel [list trace variable $varName r [list _constant_read_trace $value]] } constant PI 3.14159 puts #step1 puts [set PI] set PI 3; # Only in Indiana :) puts #step2 puts [set PI] puts #step3 set some [set PI 3] puts [set PI] puts "some: $some" puts #step4 set some 4 puts "some: $some" puts #step5 set some [set PI] puts "some: $some" ====== ---- [RS]: From a [comp.lang.tcl] post, this variation is not about preventing changes, but to import defined "constants" in [proc] scope: ====== interp alias {} define {} lappend ::defines proc use_defines {} { foreach {key val} $::defines {uplevel 1 [list set $key $val]} } #-- Test and demo: define PI 3.14 define e 2.781 proc try {} { use_defines return "PI=$PI, e=$e" } % try PI=3.14, e=2.781 ====== ---- [Karl Lehenbauer], I think, gets credit for one invention of read-only variables [[cite references]]. ---- ====== #By George Peter Staplin set ::constants [list] proc constant {name value} { global constants lappend constants $name $value } proc constproc {name argpat body} { global constants proc $name $argpat [string map $constants $body] } constant PI 3.14159 constant PROCESS [pid] constant FLAG1 2 constant FLAG2 4 constant FLAG3 8 proc & {a b} { expr {$a & $b} } constproc test {} { puts "PI PROCESS" set n 107 puts "[& $n FLAG1] [& $n FLAG2] [& $n FLAG3]" } test ====== ---- [NEM] notes that command names are the natural choice for constants: * they live in a separate namespace to variables; * they are rarely redefined, and few commands do so; * global commands are available everywhere without importing; * they offer the possibility of byte-code optimisation, i.e. inlining (no idea if this is or could be done). ====== proc def {name = args} { interp alias {} $name {} const [expr $args] } proc const a { return $a } def PI = acos(-1) def PROCESS = [pid] def FLAG1 = 0x01 def FLAG2 = 0x02 def FLAG3 = [FLAG1] | [FLAG2] proc test {} { puts "[PI] [PROCESS]" puts "FLAG3 = [FLAG3]" } test ====== ---- [wdb] Good idea ... when making a function, why not making it even more handy? ====== proc pi {args} [subst -novariable { expr [expr {atan2(0,-1)}] $args }] ====== Now, use it: ====== % pi 3.14159265359 % pi / 2 1.5707963268 ====== ---- [Stu] 2011-01-11 On similar lines, I came up with this recently. ====== proc aliasconst {name val args} { if {[llength $args] % 2 != 0} { error "must be %2!" } foreach {n v} [linsert $args 0 $name $val] { interp alias {} $n {} return -level 0 $v } } % aliasconst ^Z \x1a ^J \n me Stu % me Stu ====== ---- See also [Tcl and octal numbers]. and [Binary representation of numbers]. Some important constants are available from the [tcllib] [math::constants] package. ---- [male] - 2007-07-26 - My two cent on [const package] ---- [math::constants] <> Concept