Version 1 of Traces

Updated 1999-08-12 09:28:17

Hmm. Let's set the ball rolling...


A simple way to get a read-only global variable is to use a suitable trace:

     set ROglobal someValue
     trace variable ROglobal w "[list set ::ROglobal $ROglobal];error read-only;#"

Notice the time- and hair-saving tricks:

  1. Using [list] to construct a guaranteed safe command for later execution.
  2. Using colon notation to force a reference to a global variable, whatever the context.
  3. Inserting the global name of the variable in the trace command, instead of working with its local referent.
  4. Using a trailing ";#" to trim the undesirable extra arguments from the trace command.

DKF


Neat... I remember having had trouble when the trace was set on ::var, yet usage was based on global var, or the other way around. Does anyone know what the exact behavior is when mixing these two approaches?

JCG

The thing to remember is that traces are always called in the context of the operation that is performing the read, write or unset, and the standard parameters passed in from the Tcl system refer to the way in which the variable was referred to in that context. Whenever you are setting up a trace on a variable that exists independently of the stack (i.e. a global or namespace variable - the two are really the same thing anyway) then it is much less hassle to pass a fixed name for that variable in as one of your own parameters to the trace (you can sort-of form closures in Tcl using appropriate scripts, and some of the more cunning lambda-evaluation schemes I've seen have been based on this.) The only time this is not going to work (in standard Tcl) is when you are dealing with variables that only exist on the call-stack - procedure locals. With these, you have to carefully build an [upvar] reference to the variable and then use that name to figure out what is going on. This is not easy, but it has been adequately covered in many books, like BOOK Tcl and the Tk Toolkit which is where I learnt all this stuff (if I can remember that far back!)

DKF


Donal, you might be talking about the following:

    trace variable ::var w monitor 
    proc monitor {name args} {
            upvar $name value
            puts "'[info level 1]' changes '$name' to '$value'"
    }

    ### Change 1
    proc change1 {} {
            set ::var bar
    }
    change1

    ### Change 2
    proc change2 {} {
            global var
            set var foo
    }
    change2

    ### Change 3
    proc change3 {} {
            upvar var local
            set local bar
    }
    change3

The above code produces the following output:

    'change1' changes '::var' to 'bar'
    'change2' changes 'var' to 'foo'
    'change3' changes 'local' to 'bar'

The problem is that there is no way of knowing which variable is actually being modified, all one has access to is its alias name as used in the set command which trigers the trace, i.e., in monitor the parameter name is equal to local, how do you resolve that name back to the original ::var variable?

-- JC

There is no standard mechanism for doing this. So I instead pass a globally-valid (fully-quantified) variable name as part of the trace script that I supply. Like that, I don't care about how the variable was accessed since I can always access it myself using a clearly defined route. And, because of the restrictions involved with the use of variables and Tk, I find sticking to only putting traces on globally-valid names to not be a problem.

Note that [namespace current] is very useful when you want to put in code to automatically figure out what the globally-valid variable name actually is.

DKF


Come on people, this page isn't just "Ask Dr. Donal" - fill in more of this stuff yourselves!

DKF