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 abcalso involves a constant string "abc".But what about numeric constants?Well, a decimal value can be set thusly:
set c 10or
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 constantsCreate 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 traceBrent Welch suggests using write variable traces 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 3Thus, 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