Modeling COND with expr

Richard Suchenwirth - Another little investigation into Tcl and Lisp. Lisp's classic conditional, COND, takes any number of argument lists, where the first element is an expression, and evaluates to the first argument whose condition resulted in not-NIL. As an idiom, the last (default) condition often is just T, standing for "true" in Lisp. Example:

 (SETQ SGN (COND
             ((> X 0)  1)
             ((< X 0) -1)
             (T        0)
 ))

In contrast, Tcl's if does not return a value (wrong, see below), so cannot be used directly in an assignment. Rather, one would have to write

 if {$x>0} {
     set sgn  1
 } elseif {$x<0} {
     set sgn -1
 } else {
     set sgn  0
 }

glennj: actually, if does return a value -- it's just tricky to find a command that returns a value without other side effects like setting a variable:

 set x [if {1} {expr 42} {expr 24}] ;# ==> x is now 42
 set y [if {0} {expr 42} {expr 24}] ;# ==> y is now 24

RS: As very quick hack, I use subst - but identity is easily written in Tcl, of course:

 proc is x {set x}
 puts [if {$x} {is yes} else {is no}]

There is one shortcut, though: expr inherits from C also the peculiar x?y:z operator, which implements "if x then y else z" and can also be nested, so the above case could concisely be rewritten as

 set sgn [expr {$x>0? 1 : $x<0? -1 : 0}]

This is quite powerful, as you can spread the braced argument to expr over several lines:

 set sgn [expr {
        $x>0?  1 :
        $x<0? -1 :
           0
 }]

Just (as often in expr), the syntax is so un-Tclish... We can also emulate COND like this:

 proc cond {args} {
        if {[llength $args]==1} {set args [lindex $args 0]}
        foreach {condition body} $args {
                if {[uplevel 1 expr $condition]} {
                        return [uplevel 1 [list expr $body]]
                }
        }
 }

with some modifications: arguments come alternating as condition and value, but may as a whole be braced to avoid backslashing; for truth constant, Tcl's 1 (or in fact any non-zero number) is taken in place of T. Examples:

 set sgn [cond {$x>0} 1 {$x<0} -1 {1} 0]

or

 set sgn [cond {
            {$x>0}  1
            {$x<0} -1
            {1}     0
 }]

which looks almost as tidy as the LISP original. For performance reasons, better stay with expr however - the above cond vs. expr implementations, each wrapped in a proc, took 1070 vs. 60 microseconds on my old home P200, so good old expr appears to be 17 times faster... More examples in Binary trees.


See also In case of: select, another switch