Version 43 of switch

Updated 2007-02-13 20:33:53 by dkf

Documentation for the Tcl switch command can be found at http://www.purl.org/tcl/home/man/tcl8.4/TclCmd/switch.htm

History corner: First announced by JO for Tcl 7.0 in [L1 ]


RS Make sure you always write "the switch to end all switches", "--", if there is the slightest possibility that faulty or unexpected data (i.e., beginning with a dash) may occur - they raise a data-driven syntax error which does not make a good impression on end-users.

 switch -- $foo {...}  ;# robust for all values of foo

In the form of the switch command where all patterns and bodies are in the last argument, this argument is technically a string which will be parsed by Tcl as a list. The rules for how a string is parsed as a list can be found on the lindex manual page [L2 ]. The difference here between a whitespace-delimited substring and a list element parsed from a string is usually negligible, but one must sometimes take them into account to get the right amount of quoting in place. Regexp patterns are particularly easy to overquote.


Coding style for switch is tricky. Recently on comp.lang.tcl there was an exchange about getting switch to work right. The user had originally typed:

 switch $::errorCode {
  $NOTFOUND {
        puts "page not found "
  }
  default {
        puts "Program encountered code $::errorCode"
        break
  }
 }

and was frustrated because the application kept taking the default branch.

A well known Tcl pundit suggested:

 switch $::errorCode "
    $NOTFOUND {
        puts "page not found "
    }
    default {
        puts "Program encountered code $::errorCode"
        break
    }
  "

so that Tcl had a chance to substitue the value of $NOTFOUND. However, the follow up, by Ken Jones said it best:

you don't want that (unless you're doing some very, very, very tricky stuff). Quoting switch's pattern/action block with "" allows substitutions within the entire argument. So, yes you get the $URL_NOT_FOUND variable substituted. But you also get the contents of the actions substituted before switch even gets called.

lexfiend - I think the following example illustrates Ken's point better:

 # GOTCHA - You're dead no matter what you do
 set TriggerBomb 0
 set DefuseBomb 1
 set WaitAMinuteWhileIThinkThisThrough 2
 set Action $WaitAMinuteWhileIThinkThisThrough
 interp alias {} SendToBomb {} puts
 switch $Action "
   $TriggerBomb {
     set result [SendToBomb boom]
   }
   $DefuseBomb {
     set result [SendToBomb shutdown]
   }
 "

Instead, we want the "alternate" syntax of switch, where you provide each pattern and its corresponding action as separate arguments to switch, rather than as a monolithic pattern/action quoted argument. Here's a correctly working example of the above code:

 set NOTFOUND "value"
 set errorCode "nonsense"

 switch -- $::errorCode \
    $NOTFOUND  {
        puts "page not found"
        } \
    default {
        puts "Program encountered code $::errorCode"
        break
    }

KBK - I dislike both of these. I tend to think of a switch as a selection among fixed alternatives, and substitution in the cases confuses me. I'd have just written:

  if { [string equal $::errorcode $NOTFOUND] } {
      puts "page not found"
  } else {
      puts "Program encountered code $::errorCode"
      break
  }

RHS - I find that, when I need substitution for switch values, formatting it like the following looks cleaner:

 switch -- $::errorCode $NOTFOUND {
    puts "page not found"
 } $value1 {
    puts "blah"
 } default {
    puts "Program encountered code $::errorCode"
    break
 }

The above sample was, of course, intended to be brief, and didn't show the other several dozen switch cases . For a single comparison, I agree with KBK. But if I have more than one or two of these types of comparisons, then I know I LV prefer a switch.


With switch -regexp, you can specify abbreviations of a flag to be switched on like this:

 switch -regexp -- $variable {
  -h(e(lp?)?)?             {...}
  -h|-he|-hel|-help|-ayuda {...}
  -\\? { ... }                     ;$ Match -? flag
  "(?x) - | -h | -help" {do stuff} ;# extended RE syntax
 }

without the -regexp, you would write:

 switch -- $variable {
  -h - -he - -hel - -help { ... }
 }

LV I really find using - as a seperator between switch case alternatives to be counter-intuitive; normally one uses | for that sort of thing .

glennj: It might be clearer if more whitespace is used:

 switch -- $variable {
    -h    - 
    -he   - 
    -hel  - 
    -help { ... }
 }

SB, 2003-05-21: When I use switch -regexp, Tcl already use regexp once to enter the proper clause. How can the match pattern be accessed from within the clause, if possible, and is switch -regexp able to use () match variables? I think perl store the regexp matches in global variables $1 .. $9 (The following example is not valid, only for illustrating question)

 switch -regexp -- $somevar {
    {^(\S+)\s+(\S+)} {puts "You hit matches $1 and $2"}
    {^(\d+)\s+(\d+)} {puts "You hit number matches $1 and $2"}
 }

KBK - You can't. However, that form of [switch] is no slower than the corresponding [if]...[elseif], so you can do:

   if { [regexp {^(\S+)\s+(\S+)$} $somevar -> part1 part2] } {
        puts "You hit matches $part1 and $part2"
    } elseif { [regexp {^(\d+)\s+(\d+)} -> part1 part2] } {
        puts "You hit number matches $part1 and $part2"
    }

hkoba, 2005-03-18: Hmm, how about switch-regexp?


How's this for a silly compromise. Nicely aligns the dispatch conditions and removes the confusing barrage of backslashes.

 if {0} {
 } elseif {$state == $INIT_STATE} {

    puts init

 } elseif {$state == $CLEANING_STATE} {

    puts cleaning

 } elseif {$state == $DONE_STATE} {

    puts done

 }

The silliness continues... this actually works though I'm not sure of the side effects.

 proc dispatch {thevar args} {
     set pass1 [uplevel subst $args]
     set pass2 "switch \$$thevar \{ $pass1 \}"
     uplevel $pass2
 }

 set INIT_STATE "init"
 set CLEANING_STATE "cleaning"
 set DONE_STATE "done"

 set state $INIT_STATE

 dispatch state {
     $INIT_STATE {

         puts init
         set nextstate $DONE_STATE

     }
     $CLEANING_STATE {

         puts cleaning

     }
     $DONE_STATE {

         puts done

     }
     default {
     }
 }
 puts "next state $nextstate"

See also ranged switch


LES: So, if it is so good a practice ALWAYS to call switch as "switch --" (the switch to end all switches), why can't the core code already cope with that?

MG That's not really specific to switch - it's really good practice with all commands that take -something args, and show the end of those with '--', to include the --. And I think the reason the core can't handle is that you may actually want the behaviour that occurs when you don't give --, some times.

LV because technically the rule isn't "ALWAYS call switch as switch --". The rule is "ALWAYS call switch with -- after the last of the switch flags". If you are using switch without a flag, then it would be "switch --". Think of code like this:

 set arg "something entered by the user"
 switch $arg {
        --first_thing { do something }
        --second_thing { do something else }
        default {do the last thing }
 }

Now, if the user happens to type in "--first_thing", the program raises the error:

 bad option "--first_thing": must be -exact, -glob, -regexp, or --

because there was no "--" between the switch and the variable being examined. Not only that, but if the user happens to type in "-exact", the error raised is:

 wrong # args: should be "switch ?switches? string pattern body ... ?default body?"

because of the missing "--" . Two potential error situations - and the core switch command can't handle either, because by the time it is called, any information about where the first argument is already gone.

RS The need for -- seems to exist only for switch. Other commands don't get confused by parameters that look like switches but aren't:

 % lsearch -foo -bar
 -1

Then again, regexp has the same need for --:

 % regexp -foo -bar
 bad switch "-foo": must be -all, -about, -indices, -inline, -expanded, -line, -linestop, -lineanchor, -nocase, -start, or --

RS 2005-05-30: Unlike in C, Tcl's switch returns its last evaluated result, so it can be used as a function. Given this simple identity

 proc is x {set x}

we can code the following "digit classifier" quite elegantly:

 foreach num {0 1 2 3 4 5 6 7 8 9} {
    set type [switch -- $num {
      1 - 9         {is odd}
      2 - 3 - 5 - 7 {is prime}
      0 - 4 - 6 - 8 {is even}
    }]
    puts "$num is $type"
 }

which displays

 0 is even
 1 is odd
 2 is prime
 3 is prime
 4 is even
 5 is prime
 6 is even
 7 is prime
 8 is even
 9 is odd

LV Can someone help me with switch? I have what seems like a simple bit of code, but it doesn't work as I expected. So I need help adjusting my expectation (and my code!).

 $ cat ts.tcl
 #! /usr/tcl84/bin/tclsh

 set ::expert "no"
 set ::usage "USAGE: $argv0 arg1 arg2"
 set ::val "XYZ"

 foreach i $::argv {    
                switch -exact -- $i in {
                 !  {
                        if {$::expert == "no"} {    
                                set ::expert "yes"  
                        } else {
                                puts $::usage       
                                exit 1
                        }      
                    }

                 1 { set $::val 1 }

                 default {     
                        puts "** Too many values specified **"      
                        puts "You will be prompted for the information **"
                        set ::expert "no"
                         }
                } 
 }

 puts $::expert
 puts $::val
 $ ts.tcl 1 2 !
 no
 XYZ

I was expecting to see

 no 
 ** Too many values specified **
 You will be prompted for the information **
 1

as a result, since I supplied a 2 and a !.

What am I missing in this?

RHS You have an in there that you shouldn't:

 switch -exact -- $i in {
                     ^^

Compilation Notes

DKF: In Tcl 8.5, the switch command is bytecompiled if reasonably possible. The "reasonably possible" part means that you do have to be doing either -exact or -glob matching, and the bodies of the arms have to be compilable (as usual). With -regexp matching, that always goes to the "interpreted" version (though by compensation, there are some other new goodies available).

In most cases, the switch is compiled to (effectively) a sequence of ifs. However, in some special cases (i.e. -exact matching of constant terms) a jump table is built instead, which is considerably more efficient, especially for terms other than the first one.


[Category Command|Category Control Structure|Tcl syntax help|Arts and crafts of Tcl-Tk programming]