Richard Suchenwirth 2004-04-21: Branching is the elementary action of doing either one thing or another, depending on a condition. It can be done in several ways, the most obvious being with if. Take the following example that compares two strings and returns "different" or "same":
proc streql-if {a b} { if {$a ne $b} { return different } else { return same } }
Not exactly fascinating. An alternative is the conditional operator x?y:z of expr, which allows shorter code:
proc streql-expr {a b} { expr {$a ne $b? "different" : "same"} }
In If we had no if, it was shown that with two helper procs that select one of their arguments according to a truth value of 0 or 1, we can implement branching too, without if or x?y:z:
proc 0 {then else} {uplevel 1 $else} proc 1 {then else} {uplevel 1 $then} proc streql-01 {a b} { [expr {$a ne $b}] {return different} {return same} }
Until today I thought this was the coolest abstraction of branching. But then I read an example in the TRAC-64 tutorial [L1 ] which confirms Larry Smith's claim that TRAC, first implemented in 1964, was indeed the great-granddaddy of Tcl - a scripting language where everything is a string, names of variables can be any string, and all action happens by string substitution, and cross-platform operation of scripts was promised, starting from PDP-10s. TRAC is less readable than Tcl, and has many other mind-boggling features, but from the DECIDE example on page IX-19 in "The Beginner's Manual for TRAC Language" (Rockford Research, Cambridge MA 1972 - available as scanned PDF files on the web), I got the idea that branching can be implemented in Tcl just with the set command too:
proc streql-set {a b} { set ($a) different set ($b) same set ($a) }
What happens is that the string values of the two arguments are taken as names of local variables (hence the dollar signs). This dereferencing is perfectly legal in Tcl, and the variable names can be any string, as long as they're only referred to with set. If string a is different from b, two local variables will be created; if they are the same, the second set overwrites the first and only variable, whose value is implicitly returned as the result.
The TRAC source code for this example was 12 lines long and bothered with prompting for arguments, reading the input from "stdin", and printing the result to "stdout". But an interactive tclsh makes it easy to program in a functional way: arguments come of course from the argument list, and the result is the return value, with no side effects caused.
Finally, let's test the different implementations of "streql":
foreach cmd [info proc streql-*] { puts "$cmd hello world -> [$cmd hello world]" puts "$cmd hello hello -> [$cmd hello hello]" }
streql-set hello world -> different streql-set hello hello -> same streql-01 hello world -> different streql-01 hello hello -> same streql-expr hello world -> different streql-expr hello hello -> same streql-if hello world -> different streql-if hello hello -> same
Evidently, all of them do their job well, but [streql-set] is still the most fascinating... although I have to admit that it covers only the case of exact string equality, while the others can be used for any branching condition.
DKF: Added a little extra magic to the variable names to stop the unexpected from happening when the second argument is "a" or the first "b"...
PS: Um, what if you do:
proc streql-set {a b then else} { set ($a) $else set ($b) $then uplevel 1 [set ($a)] }
Or is that something completely different?
RS: Well, this is a control structure that executes one of the two bodies in caller's scope, like 0 and 1 above, while the original challenge was just to return a string.
DKF: One of the advantages of the ($var) trick (and which is why stooop uses it) is that you can do this instead:
proc ifstreql-set {a b then else} { set ($a) $else set ($b) $then uplevel 1 $($a) }