constants

Difference between version 34 and 35 - Previous - Next
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