A '''constant''' is a method of accessing a value such that the value never changes.
** See Also **
[math::constants]:
[const package]: [male] 2007-07-26: My two cent.
[Tcl and octal numbers]:
[Binary representation of numbers]:
[tcllib] [math::constants]: Provides some important constants.
** Description **
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
======
`abc` is a constant.
But what about numeric constants?
A decimal value can be set thusly:
======
set c 10
======
When `$c` is passed to a routine that expects a numeric value,
the value is 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 `$c`; instead, it is treated as `8`.
A number can also be represented in hexadecimal:
======
% 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
}
======
----
There are various ways to inhibit change to the value of a variable or to
access a value in some way designed to prevent modification of the value:
----
'''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%|%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 add variable $varName read [list _constant_read_trace $value]]
}
======
'''example:'''
======
% 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 eq {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]]
}
======
'''example:'''
======none
% 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 add variable $name write "set [list $name] [list $value];#"]
}
======
Simple error-raising variation:
======
proc const {name value} {
uplevel 1 [list set $name $value] uplevel 1 [list trace add variable $name write {error constant ;#} ]
}
% const x 11
% incr x
can't set "x": constant
======
----
[EG]: The problem with the above code is that a name used to modify a variable could be different that the one used to define it. For example you can define a constant in a global scope and modify it from inside a proc using :: qualifiers. So the trace ends up defining a different variable inside caller's scope.
A more robust version which prevents modification and also raises an error follows
======
proc constant {name value} {
uplevel 1 [list set $name $value]
uplevel 1 [list trace add variable $name write [list apply {
{value var idx op} {
# check whether var is an array
if {[uplevel 1 [list array exists $var]]} {
set var ${var}($idx)
}
# restore the constant's value
uplevel 1 [list set $var $value]
# raise an error
return -code error -level 2 -errorcode {TCL CONSTANT WRITE} "error trying to write to constant"
}
} $value]]
}
======
----
[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 add variable $varName read [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
======
** Page Authors **
[PYK]:
<<categories>> Concept