Comparing relations

Richard Suchenwirth 2001-10-29 - In TIP #70, the introduction of a new control structure, a relational switch was proposed, which takes two arguments and a body like switch with (exactly, or maximum) three branches of fixed relational cases, like this:

 rswitch $x $y {
   < {#action when $a<$b}
   = {#action when equal}
   > {#action when $a>$b}
 }

In the discussion it was pointed out that this is similar in functionality to FORTRAN's old three-way IF, which would GOTO one of three labels assigned to the cases:

 IF (X-Y) 10, 20, 30

and hence less general than a normal switch, which given a suitable comparison function could handle the above example without changes to the core:

 proc compare {x y} {expr {$x>$y? ">" : $x<$y? "<" : "="}}
 switch [compare $x $y] {
   < {...}
   = {...}
   > {...}
 }

and the result of such a function, if = was changed to ==, could also be used as an operator:

 if [expr {$p} [compare $x $y] {$q}] {...}

meaning "if the comparison relation between $x and $y also holds for $p and $q, ..." This code is slightly mystical: The first argument to if is evaluated like in a braced expr, where operators are not substituted, so an explicit call to expr had to be inserted, whose arguments $p and $q are however braced to prevent double evaluation... I didn't know (Andreas Leitgeb pointed it out), that besides bracing all or nothing of expr's arguments, you can also brace part of them selectively.

This change of roles, operators becoming operands, may also be counted among the Steps towards functional programming. Alternatively, one could compare explicitly:

 if {[compare $p $q]==[compare $x $y]} {...}
 #or, more targeted but wordier,
 if {[string equal [compare $p $q] [compare $x $y]]} {...}

Having learned that class derivation in OO programming may be used to replace switch constructs, I came up with yet another application for the compare function. Tcl is not strictly OO, but so incredibly flexible that we can walk that way too, using "derived" functions with a common interface:

 proc whatif:>  {a b} {...}
 proc whatif:== {a b} {...}
 proc whatif:<  {a b} {...}

and in place of the switch, or "rswitch", just write

 whatif:[compare $x $y] $x $y

in other words, "compute" the name of the called command at runtime. This may not be as effective, as it cannot be fully byte-compiled, but once again demonstrates that Tcl, without any changes to the core, holds surprising possibilities. No magic, though - you could have done the same in C by appending the result of compare to a string, which in a hash table would retrieve the function pointer to be called... but then again, this is roughly how it works inside Tcl anyway - Tcl is a rich multi-purpose C library with a powerful configuration language ;-)

Some speedup can probably be achieved by an extra level of indirection:

    array set whatif { > more == same < less }
    $whatif([compary $x $y]) $x $y

It's faster because the name is not constructed from string at run time, and hence a command object will end up being cached in each array element -jcw