constants

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 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:

% 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