Version 0 of Salt and Sugar

Updated 1999-08-11 08:21:05

The following code fragment

        if {$x0 > [lindex $description 1]} {
            set x0 [lindex $description 1]
        }
        if {$x0 < 0} {
            set x0 0
        }

is typical Tcl: dollars, brackets, braces, assignments with set. Any Tcl novice will understand it within seconds. And still, after looking at it again, I felt a slightly salty taste, like of sweat (or tears). Such adjusting a variable to upper/lower bounds occurs frequently and could be factored out to a proc - once defined, each call would save 5 out of 6 lines of source code.

When designing the (trivial) proc, I experimented with the "API look and feel", and finally wrote this:

  proc limit {_var "between" lower "and" upper} {
        upvar $_var var
        if {$var > $upper} {
                set var $upper
        } elseif {$var < $lower} {
                set var $lower
        }
        return $var
  }

The argument list may look surprising. It reads quite natural, but the "parameters" between and and are never used. They are just "syntactical sugar" thrown into the arg list (that I marked them with quotes is another sugar that's irrelevant for Tcl, but helpful for the reader), so you can call the proc like

        limit x0 to 0 .. 1024

After reading this non-pseudocode myself, I thought it kind of sweet, and even more so when I imagined how other people would feel reading this in sources which they did not write but tried to understand. (-: Our software is often self-documenting, because nobody else does it, but this executable code comes close to a good comment :-)

The cost of sugar: Everything has its price. Sugar arguments cost some extra time (about 24 microseconds per call in this case) and some extra bytes in memory. But they might still be worth it - if they save some seconds to people reading code, and maybe even give them a good taste in the mouth. Cf. upvar sugar -- Richard Suchenwirth


args sugar: When you collect a varying number of arguments with the args variable, and unpack them with the

   foreach {x y z} $args {break}

construct, remaining arguments are discarded. This means you can add sort of comments in the call, see A set of Set operations:

   if [Set $S has "white" as element] {...

args also helps to save curly braces, e.g. if you don't want to talk to Tcl like to a dog,

  proc please {args} {uplevel catch [list $args]}
  please close $f   

Cheap sugar: If you find yourself typing "expr" more often than you like, give it an alias (thanks to Jeff Hobbs for the tip!) like

   interp alias {} = {} expr

Looks, and does, magic: now expr is also reachable by the equal sign:

   set a [= $b+$c]

(yes, I know about expr{}, and sometimes care ;-) RS

Even cheaper (=free) are fancy variable names, e.g. when using regexp to extract substrings from a string:

   regexp -nocase {subject: ?(.*)} $header -> subject

A variable named "->" is assigned the whole matchstring, which we usually are not interested in. Looks good, costs nothing.


List access sugar: One Perlist once complained on comp.lang.tcl that accessing lists was so clumsy in Tcl, having to say

        set i [lindex $j 0]

for, as they'd say in Perl,

        $i = $j[0];

If you feel the same, you can say

        proc $ {_L n} {upvar $_L L; lindex L $i}
        set i [$ j 0]

Dollar is a legal proc name, if not followed by A-Za-z0-9_. A more elaborate version allows call by name or by value for a single element or a slice:

  proc $ {_L from {to {}}} {
        if {$to=={}} {set to $from}
        if [uplevel "info exists [list $_L]"] {
                    upvar $_L L
        } else { set L $_L}
        lrange $L $from $to
  }

so you can say

        set list {1 2 3 4 5}
        set i [$ list 1]              ==> 2
        set i [$ list 3 end]          ==> {4 5}
        set i [$ {a b c d e f g} 2 4] ==> {c d e}

Watch out for the space after the dollar, though. -- RS


Version sugar: Having defined the slightly ugly

  proc version {"of" pkg op vers} {
        expr [package vcompare [package provide $pkg] $vers] $op 0
   }

you can further on write (cf. Unicode file reader) the beautiful

   if [version of Tcl >= 8.1] {...

BTW: In other languages, this won't be any sweet ;-) RS

   version("of","Python",">=","8.1")