Salt and Sugar


Can one sugar coat away the set command? LV

I sometimes see people ask about being able to write:

 a = 124
 b = $a + 60

etc.

And I wondered, what games could be played for this? The first thing I thought of was something where one created a variable via some kind of command. This command would, instead of creating a vanilla Tcl variable, create an object/major command (ala Tk widget creation) whose name was that of the variable.

Within that proc would be sub/minor commands. With this kind of construct, one might write something like:

 create int a b c
 a = 123
 b = $a + 27
 c = $b / $a    ; # Result would be 1
 a = 3.1415     ; # Result would be 3

 create float d
 d = 3.1415     ; # Result would be 3.1415 ...

The problem comes when trying to use these things in other Tcl commands which do not know about them. I've not figured out what to do about that yet...

Apparently though, RS beat me to the punch - see Gadgets and Radical LAnguage Modification for more details - also check out procc.

You might also check out let.

RS replies: In my first years with Tcl, I sometimes longed for that infix style of assignment (that's why I wrote these two pages), but in practical work I have no objections against the set command at all. Both of the solutions I offer have limitations and cost performance, so I'm not wild for set sugar. Well-readable code is more determined by careful variable and proc naming, and sensible arrangement of statements, than the assignment syntax, I think.


Why sugar coat Tcl? 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 :-)


Some years later, I have learnt that range constraints can be had simpler in pure Tcl:

 set x0 [expr {$x0<0? 0: $x0>1024? 1024: $x0}]

which is not as nice reading as limit above, but more efficient "hard-core" Tcl. The advantage is that you don't have to look around for what limit exactly does. (RS)


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, Steps towards functional programming -- 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]

GWM Mar 2008 How long have these typo/bugs been here? The correct proc is:

        proc $ {_L n} {upvar $_L L; lindex $L $n}
        set j {1 2 3 4 5 6}
        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


Predicate sugar: see Predicates: on being and having on how to wrap one-argument and element predicates into pretty natural English, like

 if {[is array A] && [has A 4711]} {...}

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")

Whitespace sugar: Here's a trivial algorithm for reading a text file to a list, but note the "whitespace sugar": mentions of a variable are vertically aligned to indicate data flow ;-)

 proc file:lines                     {fn} {
        set             f [open      $fn r]
        set    t [read $f [file size $fn]]
        close          $f
        split $t \n
 } ;#RS

Either sugar: Here's a lightweight try that makes catches, info exist or other routine tests look more pleasing:

 proc either {body1 "or" body2} {
    if {[catch {uplevel $body1} t]||$t==""} {uplevel $body2}  
 }
 # e.g.: either {set x $y} or {set x 0}

KBK (10 January 2001) -- Watch out for [break], [continue] and [return] in body1 and body2. A production-grade implementation of either would adapt the code from try ... finally ..., which handles anything but the problematic [return -code].


Custom curry is also spicy for incorporating constant arguments into a proc or interp alias, e.g.

interp alias {} -> {} set res
-> 0 ;# equivalent to: set res 0