Variable substitution in -command scripts

ramsan says: A typical problem when trying to define the command script is when you want to mix variables that need to be substituted now and others that need to be substituted later. (KBK points out [2004-06-05] that there's a detailed discussion of this problem, with solutions, under Quoting Hell - and that the examples on this page are, in his opinion, examples of poor practice.)

Some examples:

  • If variables need to be substituted now:
button $b -command [list $b configure -background red]
button $b -command "[list $b configure -background red] ; [list puts \
   "pressed $b"]"
  • If variables need to be substituted later:
button $b -command {
    set aa $some_global_var
}
  • If there is a mixture:
button $b -command [list some_function $b] ;# recommended solution
  • If you really want to avoid to create new functions and want an inline solution, there are some possibilities:
button $b -command [string map [list %W [list $b]] {
    set aa $some_global_var
    %W configure -background red
}]

or:

set cmd {
    set aa $some_global_var
    %W configure -background red
}
button $b -command [string map [list %W [list $b]] $cmd]

it is the same but different disposition.

There are other solutions that become easily complex and error prone:

button $b -command "
    set aa \$some_global_var
    $b configure -background red
"

DGP Actually, your last three examples are all buggy. They will all fail if the value of $b contains white space. Avoid Quoting Hell. Stick with the helper proc.

ramsan The two examples with string map should be OK now.

RJM One can also use the subst command to properly create a command string. Of course, the subst command does not handle variables with white spaces correctly as list does, but this can be corrected by using "-quoting around the variable. This is possible since the WHOLE command is not quoted with "s. Contrary to list, subst allows multiple line command strings without backslashing, and the -nocommand option helps leaving nested commands untouched (which list does not do). Regarding string map: I personally find this ugly.

The example from above is correspondingly modified. I also added a nested command here, which may be typical. For this purpose I wrote bind instead of button.

bind $b <3> [subst -nocommand {
    set aa \$some_global_var
    focus [winfo parent %W]
   "$b" configure -background red
}]

DGP Of course, the remaing safety problems with the example above arise when $b contains one of the characters " or [ or $.


KBK much prefers avoiding Quoting Hell with an auxiliary proc:

proc handle_b3 { w } {
    set ::aa $::some_global_var; # I reproduce your example, but I'd
                                 # use namespace-specific vars here
    focus [winfo parent $w]
    $w configure -background red
}
bind $b <3> [list [namespace code handle_b3] %W]

If you're planning to reuse a family of bindings, it's also wise to place them on a bindtag other than the widget name. That way, when you create a widget that uses the bindings, you need just the one line of code that adds the tag to the widget's bindtags. All of these solutions with subst, string map, and the like appear horribly unmaintainable. Moreover, as DGP points out, most of the examples on this page have bugs.

RJM - Strictly said: the latter example does no longer touch the original page theme (immediate and late substitution). My example was prone to ambiguous interpretation. I could have been written %W instead of "$b". It was my intention to explore immediate variable substitution extending ramsan's example. Transferred to KBK's proc preferenced example, he would need two arguments: one to transfer %w and one to transfer a variable's value to document immediate and late substitution in one example. I still prefer it to define bindings up to about 5 lines inline, especially when more than one %-substitution is involved. A proc may not always appear close together with the bind definition in your source text, which lowers readability. Finally, each proc invocation takes considerable time related to simple commands (I know, it isn't interesting in most cases, since bindings are typically user related events).

rdt The complaint about the proc not being close to the binding, reminds me that, since procs are in the global (disregarding namespace) namespace even if defined within another proc, what about just defining the proc for the command on the line immediately following the binding??

Overviewing quoting topics one must still conclude that newbies in Tcl matters would tend to refuse using Tcl after reading this page. For newbies, Quoting Hell shows even more dramatically how experienced Tcl'ers are incomplete in their assumptions.

CL doesn't follow. It's OK with me for you to "just defin[e] the proc for the command on the line immediately following the binding." I generally don't. I don't mind if you do. Also, I like the "Quoting Hell" page, because it properly begins with an emphasis on the simplicity of Tcl's syntax.

RJM: Syntax IS simple indeed, but the term "Quoting Hell" says quite much about difficulties in correctly understanding certain code. The radical simplicity of the interpreter makes code sometimes look complex. For the proliferation of Tcl this is a serious problem, since today other scripting languages (however not as flexible as Tcl) partially provide a clearer syntax, especially Lua, which - to my opinion - will have a bright future.


EB: To solve variable substitution problem, as KBK and DGP said, use a proc to avoid quoting hell. But if the proc is uniquely used, then lambda is a good solution:

bind $b <3> [list [lambda {w} {
      set ::aa $::some_global_var
      focus [winfo parent $w]
      $w configure -background red
}] $b]