'''[http://www.tcl.tk/man/tcl/TclCmd/trace.htm%|%trace]''' a [Tcl Commands%|%built-in] [Tcl] command, monitors [variable] accesses [command] definition and execution. ** See Also ** [An equation solver]: with an example of using trace to update dependent resources [Traces]: examples and discussion [Tracing inappropriate variable access]: [Whole-Script Tracing]: by [DKF] [An example of data objects]: [AM] Using the trace command, I implemented an idea by George Howlett (the author of [BLT]) ** Synopsis ** : '''trace''' ''option'' ''?arg arg ...?'' ** Documentation ** [http://www.tcl.tk/man/tcl/TclCmd/trace.htm%|%official documentation%|%]: ** Description ** Examples of what to do with `trace`: * Communicate between parts of a [GUI] and the internal state of the app. (Simplified [MVC], observer). In general Communicate between different parts of an app without coupling them strongly. * Compute simple constraints for a number of variables ("if this flag is on and that one is on, then no other is allowed to be set", and some such). * `[bind]` [Canvas] text items to a variable, effecting dynamic updates * Debug - call a proc when a variable is modified (detect setting from wrong routine). * Trace works in Itcl [Itcl trace] but not quite trivially. ** Examples ** *** order of processing *** [JCW]: ====== proc tellme {id a e op} { puts " $id a=$a e=$e op=$op\ ax=[info exists ::$a] ex=[info exists ::${a}($e)]" } proc do {args} { puts "<$args>" uplevel $args } trace var a wu {tellme array} trace var a(1) wu {tellme element} puts [trace vinfo a] puts [trace vinfo a(1)] do set a(0) zero do set a(1) one do set a(2) two do unset a(0) do unset a(2) do unset a # output is: # # {wu {tellme array}} # {wu {tellme element}} # # array a=a e=0 op=w ax=1 ex=1 # # array a=a e=1 op=w ax=1 ex=1 # element a=a e=1 op=w ax=1 ex=1 # # array a=a e=2 op=w ax=1 ex=1 # # array a=a e=0 op=u ax=1 ex=0 # # array a=a e=2 op=u ax=1 ex=0 # # array a=a e= op=u ax=0 ex=0 # element a=a e=1 op=u ax=0 ex=0 ====== ** Variable Trace Arguments vs Upvar ** The parameters provided to variable traces are of questionable utility. If the traced variable is aliased with `upvar`, your trace will see that name! ====== package require lambda unset -nocomplain x y trace add variable x {read write unset} [lambda args {puts "trace: $args"}] incr x # trace: x {} read # trace: x {} write ====== so far, so good ... ====== upvar 0 x y incr y # trace: y {} read # trace: y {} write ====== Now the trace fires, but the name is no longer useful to identify which variable is being accessed. A more robust approach is to embed the name in the trace callback: ====== trace remove variable x {read write unset} [lambda args {puts "trace: $args"}] trace add variable x {read write unset} [lambda {varname args} {puts "$varname trace: $args"} x] incr y # x trace: y {} read # x trace: y {} write ====== [PYK] 2014-08-16: The phrases "questionable utility" and "no longer useful" are a little strong for this behaviour. A trace often just needs a handle to the variable, and doesn't care what it's called. [upvar%|%upvar'ed] variables are more like filesystem hard links than symbolic links in that one name is just as good as another for manipulating the variable, and no name is more "real" than the other. Furthermore, for cases where the program has been designed such that the name does somehow matter, which is possibly an indictment of the design, the trace has access via `[uplevel]` to the context of the variable operation. ** Local Variable Traces ** On [comp.lang.tcl], 2004-05, [CLN] answers [Erik Leunissen]'s question: Erik Leunissen wrote: ====== > The following passage from the man page to the trace command is > mystifying to me: > > "If an unset occurs because of a procedure return, then the trace will > be invoked in the variable context of the procedure being returned to: > the stack frame of the returning procedure will no longer exist." > > I've read it again and again; I can't imagine how a procedure return > affects/causes an unset. > ... proc foo { } { set x 1 trace variable x u {some code here} } When foo is invoked, x is created (locally), has a trace associated with it, then becomes unset as foo exits. ====== ** Arrays ** Non-array variables give a null string for the ''name2'' argument in the trace invocation, but the null string is a perfectly valid array index (it is also a valid array variable name), so a null value for ''name2'' doesn't necessarily indicate that the traced variable is scalar. To determine whether a variable is an array, use: ====== if { [array exists $varname] } {... ====== All of the following operations result in the argument values `a {} u`: ====== unset a ;# regular var a array unset a unset a() ====== including a null index string. [[`[array exists]`] always returns false for the first two cases, and true for the third (even if the null index was the only array element). There is no way for the trace to be sure which operation was performed. [Lars H]: Hmm... might this be a sign that the format of these parameter lists is not well designed? An alternative would have been to put the operation first and the variable name second, so that there needn't be an index for non-array accesses. Probably too late to change now, though. (Adding a second interface which is just like the current except that it produces parameter lists in a new format is possible, but probably seen as superfluous.) ** Arrays and Upvar ** A surprising (?) result that is documented in the [upvar] manpage: ------ If otherVar refers to an element of an array, then variable traces set for the entire array will not be invoked when myVar is accessed (but traces on the particular element will still be invoked). ------ This means that using a trace on an array is *not* sufficient to capture all the ways elements can be accessed. For instance: ====== package require lambda unset -nocomplain a x y array set a {x 1 y 2} trace add variable a {array read write unset} [lambda args {puts "::a trace: $args"}] ====== Any direct use of `a` will trigger the trace: ====== % incr a(x) ;# existing element ::a trace: a x read ::a trace: a x write 2 % incr a(z) ;# new element ::a trace: a z read ::a trace: a z write 1 % unset a(x) ::a trace: a x unset ====== so far so good. But throw [upvar] into the mix: ====== % upvar 0 a(y) y ;# existing element % upvar 0 a(w) w ;# new element % incr y 3 % incr w 1 % unset y % unset w ====== No traces were fired! If you're tracing an array whose elements might be [upvar]ed, beware. ** What about a "variable create" Trace? ** [male] 2006-01-24: I wanted to trace the creation of an array element and used the write event, but ... the write event is fired after the new array element was already created! What's about a new event like "create"? Since a trace may be created on non-existent variables, this could be useful not only for arrays. ---- [Donald Arseneau]: Yes, write traces fire after the variable has already been set, so if you want validation of variables' values, in analogy with Tk's [entry validation], then you must maintain shadow copies of the previous values, in order to undo improper settings. ** Triggering Traces when using a variable at the [C] level ** On [comp.lang.tcl], [Kevin Kenny] answers someone wanting to link a C variable and a Tcl variable, and have a Tcl proc invoked when the C code modified the variable: : Look in the manual for Tcl_UpdateLinkedVar. The Tcl engine has no way of telling that you've changed the variable in C; if you call Tcl_UpdateLinkedVar, that tells it your value has changed, and it fires the traces. ** Simple file I/O in traces: ** ====== trace var stdout w {puts stdout $stdout ;#} trace var stdin r {gets stdin stdin ;#} ====== The variables are named like the file handles. Little demo, that waits for input and prints it capitalized: ====== set stdout [string toupper $stdin] ====== ** Managing Traces ** Traces are like [widget]s and [image]s in that they are resources that can be leaked and/or need clean-up. Counter-intuitive results sometimes arise because traces are additive rather than substitutive; a particular trace can fire a whole chain of commands. To wipe the global space clean of traces, ====== foreach variable [info glob] { foreach pair [trace info variable ::$variable] { foreach {op traced_command} $pair {} set op [string range $op 0 0] trace vdelete ::$variable $op $traced_command } } ====== ** Traces of Command Executions ** *** step traces *** '''enterstep''' and '''leavestep''' traces fire for all steps, [recursion%|%recursively%|%]. When this is undesired, the depth of the recursion can be constrained by having the trace procedure look at [[info level]]. : [http://groups.google.com/d/msg/comp.lang.tcl/d8KWS-VmxDs/rx0aZp7qOdUJ%|%Interesting experience with execution traces], [comp.lang.tcl], 2003-11-24 ---- [Donald Arseneau]: Another tricky trap is that errors in traces may give error messages, but no context; the only context is for whatever triggered the trace. Thus, if you ever see Tk error messages like ====== can't set "xv": invalid command name "oops" while executing "incr xv" ====== then you should look for a variable trace on the xv variable. ---- [Schnexel] Oh the tricky trace traps! I tried to automaticly update a derivedData array by setting a trace on the parentData array (scenario simplified)... Now I get a surreal result: ====== set bla "What happened:\n" namespace eval bbb { array set lala [list 1 v1 2 v2 3 v3] trace add variable ::bbb::lala {read array} ::bbb::tra proc tra { args } { append ::bla "\n (TRACE $args)" array unset ::bbb::lala ;# also deletes trace (yet the "array" op still fires) foreach {n v} [list 1 trv1 2 trv2 3 trv3] { set ::bbb::lala($n) $v } } } namespace eval aaa { append ::bla "\n\[info exists ::bbb::lala(1)\]==..."; append ::bla ... [info exists ::bbb::lala(1)] append ::bla "\n\[info exists ::bbb::lala(1)\]==..."; append ::bla ... [info exists ::bbb::lala(1)] append ::bla "\n\$::bbb::lala(1)==..."; append ::bla ... $::bbb::lala(1) } puts $bla ====== which gives the output ======none What happened: [info exists ::bbb::lala(1)]==... (TRACE ::bbb::lala 1 read) (TRACE ::bbb::lala {} array)...0 [info exists ::bbb::lala(1)]==......1 $::bbb::lala(1)==......trv1 ====== So, upon first read access of lala, it does not exist anymore, whilst upon second read it is there. Can anybody make sense of this? [Lars H]: Regarding why the "array" op fires: It it fires at the very [[array unset ::bbb::lala]] where you comment upon this, i.e., before the [foreach], which is consistent with the [trace] manpage (only read and write traces are disabled inside a read or write trace). But why [info exists] reports 0 I don't know... Perhaps some caching issue (the variable that was looked up is not the one that is there when the result is returned)? You'll probably need to read the source to find out. [Schnexel]: Wrrr... HereĀ“s a simpler example. Array trace is bugged! ====== array set ::lala [list 1 v1 2 v2] array set ::lala [list 3 v3 4 v4] puts "\$::lala==[array get ::lala]" ;# O.K. trace add variable ::lala read ::tra proc tra { args } { puts " (TRACE $args)" trace remove variable ::lala read ::tra array set ::lala [list A trvA B trvB] puts " within trace: \$::lala==[array get ::lala]" ;# O.K. } puts "1st read outside: \$::lala==[array get ::lala]" ;# not O.K. ! puts "2nd read outside: \$::lala==[array get ::lala]" ;# O.K. ====== Output: ======none $::lala==4 v4 1 v1 2 v2 3 v3 reading ::lala (TRACE ::lala 4 read) within trace: $::lala==4 v4 A trvA 1 v1 B trvB 2 v2 3 v3 1st read outside: $::lala==4 v4 1 v1 2 v2 3 v3 2nd read outside: $::lala==4 v4 A trvA 1 v1 B trvB 2 v2 3 v3 ====== ** Proposal: Modify `trace command ... enter` to Act as a Command Filter ** [PYK] 2013-12-22: The result of a command run as a trace are currently discarded. It could instead be used as the command to actually call. For exampe, the result of the following script would be `12`, not `21` ====== proc add {args} { ::tcl::mathop::+ {*}$args } trace add execution add enter {apply {{cmd op} { set args [lassign $cmd name] foreach arg $args[set args {}] { if {$arg % 2 == 0} { lappend args $arg } } return [linsert $args 0 $name] }}} add 1 2 3 4 5 6 ====== <> Tcl syntax | Arts and crafts of Tcl-Tk programming | Command