Tcl Gems

Many long time Tcl users are positively passionate about the language because of some of the wonderful things you can do with Tcl and Tk. It isn't so much the portability, or the extensions, or the internationalization (unicode) support, or even the fact that Tcl programmers are better looking than most users of those other languages. The simplicity of the Tcl language itself, coupled with the highly dynamic and introspective nature of the interpreter make wonderful things possible.

Unfortunately, all these little gems are not obvious to the first-time Tcl user. In fact, it sometimes takes quite a while to realize just how powerful Tcl can be in the "right hands." There is nothing at all wrong with using Tcl and Tk as you would any other programming language, but if you learn and use a few little tricks like these, you will be a more productive person, and you might even have a little more fun while you're at it.

This page lists a few of those "Tcl Gems" that have been developed over years and years and years and years of experienced Tcl use. Some of it is just pointers to other pages, and some is just a description of what you can do. Call this advocacy if you like, or just a collection of interesting reading, but I hope you enjoy these.

You might also want to check out Tk Gems if you're interested in some clever little tricks with different kinds of widgets.

RWT

Besides gems, sometimes a good pebble would be o.k. Have you checked the Bag of algorithms, Tcl examples, and Bag of Tk algorithms, just in case...? RS


auto update

Richard Suchenwirth shows how to set up a source file that automatically reloads itself into the interpreter every time you save it. Great development aid! Look for autoupdate on Braintwisters. There are some other cute ideas on that page, including dynamic variables and intgen which returns a new unique integer each time called.


re-source

Jeffrey Hobbs posted on comp.lang.tcl that TkCon has a command tkcon resource that he uses all the time in development to reread the script in (it is extremely handy to not have to exit and restart wish). It not only resources in the main interpreter, but reinitializes the primary slave environments. TkCon is a gem in itself. If you don't have it, get it.


Reload a package

See package require.


editing procedures

Richard Suchenwirth says: In my Tcl sandbox, I often don't even care to source a file at runtime, I typically rather

  • display the full proc (name, args, defaults, body) into a text widget
  • edit it (maybe just insert a [puts])
  • eval the text widget's content, so the proc is redefined.

This way debugging doesn't leave traces in the original files, it's even faster than sourcing, and very intuitive. Oh, if it was a successful change, I'd also make it persistent in the source file - just ask auto_index where the proc came from ;-)

and Harald Kirsch says that one of his favorite procs is

 proc learnSelection {} {
   set s [selection get]
   uplevel \#0 eval [list $s]
 }

DKF: Whyever not the following instead?

 proc learnSelection {} {
    uplevel \#0 [selection get]
 }

Sometimes you just have to wonder about people...

 proc learnSelection {} {uplevel #0 [selection get]}

is also equivalent - hashes matter only at beginning of command - RS

unless you're using a syntax-sensitive editor like emacs or Source Navigator, which is not quite clever enough ...


new language constructs

Tcl is almost unique in that you can create new language constructs if you really need them.


Unknown possibilities

One of the few Tcl rules says that the first word of a command is the command name, and you can even legally break that, allowing for constructs like

 i = $j+1 ==== set i [expr $j+1]
 a ( 2 )  ==== lindex $a 2
 {1 .. 5}   ==== {1 2 3 4 5}

without having procs i, a, 1 - that would be the known way. See Radical language modification, where this is done with the unknown command. Gadgets o.t.o.h. do "infix assignment" more orthodoxly, with "light-weight objects". Let unknown know if you want to build language modifications incrementally...


Tcl as OS glue

In the amazing ETLinux project [L1 ], an embedded system is developed that packs stripped-down Linux and Tcl (7.6, enhanced by mount, ifconfig, route, uuen/decode, dup, fork, kill, nice, ...) into a tiny 386SX/2 MB RAM/2 MB disk configuration. Tcl scripts do the init (first process at all), mail server, web server, ...


No-Op Prompts

DKF: When I'm running tclsh or wish interactively, I have my prompt set to the usual default of % and have the following little procedure in my .tclshrc (see "The RC File" for more on tclshrc)

 proc % args {uplevel 1 $args}

This lets me copy and paste whole lines of Tcl with the mouse triple-clicks without having to worry about stripping leading prompt symbols, so making it faster for me to edit sessions interactively (particularly when you want to change part way through an existing procedure definition.) Power-user type facility? You betcha! Useful? Certainly so for me. I just wish I could do this with other interactive scripting systems...

mikeH: Note that you can also set your prompt to the semi-colon and then a space ; to achieve the same goal without doing extra evals etc.. this trick also works nicely for traditional sh style shells like bash


Swapping variables

 foreach {a b} [list $b $a] break ;# RS

or, maybe not a gem:

 eval set a \{$b\} \; set b \{$a\} ;# Donald Arseneau

That eval only puts you in Quoting hell, DA. For it to work, you need to do

 eval [list set a $b] \; [list set b $a]

DKF: In 8.5 or with TclX, with lassign:

 lassign [list $b $a] a b

Constants

Constants that shall be visible anywhere in the code must be global. Global variables need however be declared every time you use them, either with global Pi once per proc or ::Pi once per use. Or, you can proc'ify them, since procs are global too:

  proc constant {name value} {proc $name {} [list return $value]}
  constant Pi 3.1415926535
  puts "Pi = [Pi]"

wdb: My approach:

 proc pi args {
     expr 3.14159265359 $args
 }
 proc -pi args {
     expr -3.14159265359 $args
 }

If I say, [pi / 4], the function returns correctly 0.785398163398.


Whole or in pieces

A proc may be called with either a variable number of non-list arguments, which is convenient interactively, or a list of such arguments (which is good if you have the list anyway - no need for eval), if you interpret the args parameter as follows:

 proc lsum {args} {
     if {[llength $args]==1} {set args [lindex $args 0]}
     expr [join $args +]+0
 } ;# RS
 lsum 1 2 3
 lsum $a_list_I_got_from_somewhere

The canvas is a gem

An analog clock in Tk is a beautifully simple and simply beautiful piece of canvas demo code.


Optional arguments

HD: Laurent Demailly has written the package opt, which looks very powerful and comprehensive, but I find the following approach very easy to implement and adequate for my needs.

Let's say I start with a proc that takes a long list of arguments, each having a default value:

 proc myProc {{arg1 dflt1} {arg2 dflt2} ... {arg9 dflt9}} {

This requires the user to know the order of arguments, and it's a pain if you only want to specify a non-default argument for arg9. It would be nicer if we call myProc in this case as:

 myProc -arg9 Fred

So let's rewrite myProc as:

 proc myProc {args} {

     # Set up default values
     array set the_args {
         -arg1 dflt1
         -arg2 dftl2
         (etc ...)
         -arg9 dflt9
     }

     # Now overwrite with user-supplied values
     array set the_args $args

     # And off we go...
     puts "The ninth argument is $the_args(-arg9)"
 }

In practice I usually have procs like this in a package:

 package provide XYZ 1.1

 namespace eval XYZ {
     variable Global
     array set Global {etc ...}
 }

 proc XYZ::myProc {args} {
      variable Global
      array set the_args [array get Global -*]
      array set the_args $args
      # Carry on as before
 }

Use profile with ?# command

Chang LI [email protected]

I defined the ?# command as

 proc ?# {args} {
    if {[info exists ::tcl_platform(debug)]} {
       uplevel 1 eval $args
    }
 } 

US Very useful, but one round of evaluation too much. Should be:

 proc ?# {args} {
    if {[info exists :tcl_platform(debug)]} {
       uplevel 1 $args
    }
 }

? means the alternative comment. It was quite useful to add profile code and you do not need to delete them when you distribute the code later, just set the tcl_platform(debug) as 0.

 set tcl_platform(debug) 1

 ?# package require Tclx
 ?# profile -commands on

   # your tcl code here

 ?# profile off report
 ?# parray report

LISPlike lists

Kevin Kenny emulates mutable lists built of cons cells with procs in Tcl and Lisp.


List Comparision

Ed Suominen (a TCL beginner as of Sept. 2001) contributes the following simple but fast proc for comparing lists. The result are two lists containing items exclusively in one argument list or the other.

 # PROCEDURE: LISTCOMP
 # CALLS:  none
 # RETURNS:  lists of unique elements in each list
 # USAGE:  list1, list2 are the lists to compare.
 # outname1, outname2 are names of lists to use as the
 # output of the procedure
 # Copyright (c) 2001 Edwin A. Suominen, http://eepatents.com
 # This procedure code is freeware under the terms of the no-endorsement
 # version of the BSD License, incorporated herein by reference, with 
 # <OWNER> = Edwin A. Suominen, <ORGANIZATION> = author, <YEAR> = 2001.
 # Inventors of any inventions that employ or are based on this 
 # procedure retain their patent rights to such inventions.

 proc listcomp { list1 list2 out1Name out2Name } {

   ### Define empty lists in case one has no unique elements
   set out1 {}; set out2 {}

   ### Test each element of each list against all elements of other list
 
   foreach {i} $list1 {j} $list2 {

     # First, test for unique element in list1
     if { [ lsearch  -exact $list2 $i ] < 0 } { lappend out1 $i }
     # Then test for unique element in list2
     if { [ lsearch  -exact $list1 $j ] < 0 } { lappend out2 $j }
   }

   ### Put results in specified lists
   upvar #0 $out1Name x 
   set x $out1
   upvar #0 $out2Name x 
   set x $out2

   ### END LISTCOMP
   return
 }

Setok Wouldn't it be better to sort the list first (Ologn) or build an array with the elements as keys? As far as I can tell the above can lead to On^2 time.

Michael Schlenker You're absolutly right. See Some ways to do set comparision for some much faster alternatives (more than a magnitude faster). So perhaps this proc should be moved from the Gems section, especially because it returns incorrect results!


lshift for command-line argument and procedure args parsing

 #==========================================================
 # NAME    : lshift
 # PURPOSE : shift list and return first element
 # AUTHOR  : Richard Booth
 #           http://www.lehigh.edu/~rvb2
 #           [email protected] [email protected]
 # ---------------------------------------------------------
 # ARGUMENTS :
 #   % inputlist
 #       List to be shifted.
 # RESULTS :
 #   * Sets inputlist to 2nd to last elements of original inputlist
 #   * Returns first element in inputlist
 # NOTES :
 #   * useful for command-line arguments and procedure args processing
 # EXAMPLE-CALL :
 #
 #  while {[llength $argv] > 0} {
 #    set arg [lshift argv]
 #    switch -- $arg {
 #      -lib  {set lib [lshift argv]}
 #      -show {set show 1}
 #      default {lappend tests $arg}
 #    }
 #  }
 #
 #==========================================================
 proc lshift {inputlist} {
   upvar $inputlist argv
   set arg  [lindex $argv 0]
  #set argv [lrange $argv 1 end] ;# below is much faster - lreplace can make use of unshared Tcl_Obj to avoid alloc'ing the result
   set argv [lreplace $argv[set argv {}] 0 0]
   return $arg
 }

See also Stacks and queues. JCW - Here's a non-recursive directory walker based on it:

 proc walkdirs {args} {
   set files {}
   while {[set dir [lshift args]] != ""} {
     foreach x [glob -nocomplain [file join $dir *]] {
       lappend [lindex {files args} [file isdir $x]] $x
     }
   }
   return $files
 }

RS considers this code by Lars H from Googol magnitude a true gem - it turns positive decimal integers (of any length) to bit strings:

 proc dec2bin {num} {
    while {[regexp {[0-9]} $num]} {
       set num\
         [string map {o0 0 o1 1 o2 2 o3 3 o4 4 i0 5 i1 6 i2 7 i3 8 i4 9 0 ""}\
           [string map {0 0o 1 0i 2 1o 3 1i 4 2o 5 2i 6 3o 7 3i 8 4o 9 4i} $num]]
    }
    string map {i 1 o 0} $num
 }

APN Agreed. In Tcl 8.6, the %lb format specifier provides the same function.


RLH - or even the fact that Tcl programmers are better looking than most users of those other languages - Maybe I need to find another language then! ;-)