Version 86 of switch

Updated 2011-06-09 10:23:38 by dkf

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

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

There is a typo in the current switch.html documenation. It should read:

    switch abc "a - b" {expr {1}} $foo {expr {2}} default {expr {3}}

switch supersedes [case].

Synopsis

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

[AMG]: Actually, "`--`" is not required when using this form of [[switch]] [http://www.tcl.tk/man/tcl8.6/TclCmd/switch.htm#M11].  It's only needed when the patterns and bodies are separate arguments, which is a relatively rare usage of [[switch]].
In Tcl versions prior to [Changes in Tcl/Tk 8.5%|%8.5],  `--` should be used
[AMG], update: On second thought, "`--`" is required, if you're using Tcl 8.4 or older.  Tcl 8.5+ doesn't require it.
With exactly one argument, that argument is a [dict%|%dictionary] of patterns
----
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 [http://www.purl.org/tcl/home/man/tcl8.5/TclCmd/lindex.htm].
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.
`switch` only performs string comparison, so it isn't ideal for numeric
----
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

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

PYK 2014-05-29: scripted list can make this syntax work, and even allows comments:


PZ 'default' alternative for regexp:

set a x
switch -regexp -- $a {
         y {
                 puts "y"
         }
 
         {} {
                 puts "z"
         }
         
         default {
                 puts "$a"
         }
}

returns:

 z

----
[LV] When I try to use a break inside of a switch command, I get the error

switch $::errorCode [sl {
invoked "break" outside of a loop
    while executing
"switch 

A well-known Tcl pundit suggested:
Is there an equivalent to [break] for switch?

YSK How about trapping "switch" in "while 1":

switch $::errorCode " while {1} { switch $cond { case1 {

   if {$fault} {puts NG; break}
   puts OK    

} default {

   puts OK

} break }

[MG] Hrm, I thought [break] worked in switches, too. But it seems that Tcl's [switch] doesn't fall through by default:

switch -glob -- foo \

  f* {puts match1} \
  *o* {puts match2} \
  default {puts nomatch}
Only the "match1" is shown, and then it stops, there's no "match2". So no [break] is needed, it'll stop when a match is found anyway. That means, in something like

switch -glob -- $string \

  f* {puts foo}
  b* {puts bar}
  *z* {# do nothing}
  default {puts "something else"}
the "# do nothing" case has the same effect as a [break] in a switch in [C] would.

[LV] without break, it means that one has to code carefully to avoid the cases where one normally would use it. For instance, in other languages, one might code

switch $variable { case1 {do this; break} case2 {do that; break} case3 {if today is Monday, do the other; break; if tomorrow is the 14th, do something else; break;} default {if an error, say so and break; do the last thing, break;}

In tcl, one would recode using else or elseif or fiddling with state variables, etc. to accomplish the same thing.

----
A well known Tcl pundit suggested:
    $NOTFOUND {
        puts "page not found "
    }
    default {
        puts "Program encountered code $::errorCode"
    }

"

so that Tcl had a chance to substitue `$NOTFOUND`.
so that Tcl had a chance to substitue the value of $NOTFOUND.
''[AMG]: The quotes don't match up the way you expect.  This is because braces inside "quoted" words don't have any special power.  the second argument to switch starts with a line break, ends with the space following puts, and has the word "page" illegally appended to it, resulting in an "extra characters after close-quote" error.''

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

[lexfiend]: I think the following example illustrates Ken's point better:
[lexfiend] - I think the following example illustrates Ken's point better:
# GOTCHA - You're dead no matter what you do
 # 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

set NOTFOUND value
 set NOTFOUND "value"
 set errorCode "nonsense"
switch -- $::errorCode \
 switch -- $::errorCode \
    $NOTFOUND  {
        puts "page not found"
        } \
        puts "Program encountered code $::errorCode"
    }

KBK - I dislike both of these. I tend to think of a switch as a confuses me. I'd have just written:

if {$::errorcode eq $NOTFOUND} {

  if { [string equal $::errorcode $NOTFOUND] } {
      puts "page not found"
  } else {
      puts "Program encountered code $::errorCode"
  }
[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 {

}

 }

DKF: With 8.6, I'd be using try for this particular problem instead of using switch to pick through the errorCode after catch returned, but YMMV.

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. LV: 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 prefer a switch.

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

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

switch -- $variable {

 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 . LV: I really find using - as a separator between switch case alternatives to be counter-intuitive .

switch -- $variable {

 switch -- $variable {
    -he   - 
    -hel  - 
    -help {...}
    -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

switch -regexp -- $somevar {

 switch -regexp -- $somevar {
    {^(\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],

if {regexp {^(\S+)\s+(\S+)$} $somevar -> part1 part2} {

   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? 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} {
** Delimiting the Options **
    puts cleaning
[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?
 } 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


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 sometimes you may actually want the behaviour that occurs when you don't give --. 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 --". 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 Think of code like this:

set arg {something entered by the user}

 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

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

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

because of the missing "--" . Two potential error situations - and 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: 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 --
Yax: lsearch assumes the form of arguments by the argument count, and its quite probable that all commands that take fixed args after options do. Any commands (such as regexp) that can take an arbitrarily varying number of args and have switches probably also suffer this issue.
Then again, `[regexp]` has the same need for `--`:
----
[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}


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


 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

ZB 20100318 - the above example doesn't work (invalid command name "is"). I've got a feeling, it was probably valid for TCL8.4 (didn't check it), but it won't work with 8.6 anymore.

Lars H: More likely, you forgot to include the definition of is (the proc command in the preceeding one-line code block).

ZB Oh, yeah... O_O indeed.

AMG: See return for more discussion of this pattern. Search for "return -level 0". I have found that the simplest way to do it is with single-argument lindex. Elsewhere I've seen it done with single-argument list, but this is unsafe.


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

AMG: See identity function for more discussion of this pattern. The simplest way to do it is with

 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
                        }      
                    }

History

                 1 { set $::val 1 }

First announced by JO for Tcl 7.0 in Advice wanted: "case" command and regexps , comp.lang.tcl,1993-06-05

                 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
** Compilation Notes **
as a result, since I supplied a 2 and a !.
[DKF]: In Tcl [Changes in Tcl/Tk 8.5%|%8.5], `switch` is [bytecode%|%byte-compiled] 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 (see `-matchvar` and `-indexvar`, for instance) available).
What am I missing in this?
In most cases, the switch is compiled to (effectively) a sequence of `[if]` commands. 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.
[RHS] You have an '''in''' there that you shouldn't:
 switch -exact -- $i in {
                     ^^

----
'''Compilation Notes'''
** Bug: `-indexvar` Indexes Off By One **
[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 (see -matchvar and -indexvar, for instance) available).
[TJE]: Why are the ranges placed in a `switch` statement's `-indexvar` target inclusive of the character AFTER the match?  This differs from the behavior of regexp's `-indices` option, which seems quite odd to me.  For example:
In most cases, the switch is compiled to (effectively) a sequence of [if]s. 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.

TJE Why are the ranges placed in a switch statement's "-indexvar" target inclusive of the character AFTER the match? This differs from the behavior of regexp's "-indices" option, which seems quite odd to me. For example: % set line {foo bar}

  % set line {foo bar}
  foo bar
  % regexp -inline -indices {foo} $line
  {0 2}
  % switch -regexp -indexvar index -- $line {foo} {set index}
  {0 3}

PYK 2014-05-30: If this really happened, it must have been a bug in some

[Anyone have an example of a switch statement with two or more cases, with comments for each case?] Wookie: I have a large switch and wanted comments for blocks of statements e.g. The man page [L2 ] includes such an example.

switch -exact -- $test {

  switch -exact -- $test {
    Test1 {do stuff...}
    Test1 { SomeProcs...}
    Test2 { SomeProcs...}

    Test3 {do stuff...}
    Test3 { SomeProcs...}
    Test4 { SomeProcs...}

    NONE -
    default {SomeCleanUpProc...}
    default { SomeCleanUpProc... }
  }

   "extra switch pattern with no body, this may be due to a comment incorrectly placed outside of a switch body - see the "switch" documentation"

As Tcl switch statements don't seem to care that there are multiple definitions of a statement, I rewrote the code as: As TCL switch statements don't seem to care that there are multiple definitions of a statement, I rewrote the code as: switch -exact -- $test {

  switch -exact -- $test {
    Test1 {do stuff...}
    Test1 { SomeProc...}
    Test2 { SomeProc...}

    Test3 {do stuff...}
    Test3 { SomeProc...}
    Test4 { SomeProc...}

    NONE -
    default {clean up stuff...}
    default { SomeCleanUpProc... }
  } 

DrASK - Is there any speed improvement when using -exact over -regexp? As in: set number 0x5

   switch -exact -- $value {
      {a} - {b} - {c} {match}
   }

You may instead consider a long if/elseif chain, but maybe this won't bytecode as well. Alternately, force the formatting to be consistent:

   switch -regexp -- $value {
      {[abc]} {match}
   }

set number 0x5 Lars H: Comments by DKF above suggest that the answer should be yes, but why not check for yourself? time it!

switch -regexp -- $value {

   switch -regexp -- $value {
      {^[abc]$} {match}
   }

DrASK Yeah, I failed to mention that $value is known to be a single character. Thanks. Here is the test and the results: proc exact value {

 proc exact {value} {
        a - b - c {return true}
        default {return false}
    }

}

 }

proc re value {

 proc regexp {value} {
        [abc] {return true}
        {[abc]} {return true}
    }

}

 }

proc first value {

 proc first {value} {
    return [expr {[string match $value {abc}] != -1}]
 }

proc multi {type value} {

 proc multi {type value} {
        $type $value
    }

}

 }

foreach type {exact re first} {

 foreach type {exact regexp first} {
        puts "$type $value [errortime {multi $type $value} 10000 100]"
    }

}

 }

 exact a 66 +/- 0 microseconds per iteration
 exact b 67 +/- 0 microseconds per iteration
 exact c 67 +/- 0 microseconds per iteration
 exact d 66 +/- 0 microseconds per iteration
 regexp a 151 +/- 0 microseconds per iteration
 regexp b 140 +/- 0 microseconds per iteration
 regexp c 142 +/- 0 microseconds per iteration
 regexp d 121 +/- 0 microseconds per iteration
 first a 71 +/- 0 microseconds per iteration
 first b 71 +/- 0 microseconds per iteration
 first c 71 +/- 0 microseconds per iteration
 first d 70 +/- 0 microseconds per iteration
It's interesting to note the dropoff when regexp hit the default case. But -regexp is clearly is 2-2.5x slower. I used [MAK]'s errortime proc [http://wiki.tcl.tk/924] for timing.
It's interesting to note the dropoff when `[regexp]` hit the default case. But `-regexp` is clearly is 2-2.5x slower. I used [MAK]'s [How to Measure Performance%|%errortime proc] for timing.
[ferrieux]: Indeed, timing shows that the jump table is roughly 3x faster than if..elseif..elseif, which is itself 1.25x faster than regexp. Notice that the regexp compilation overhead is not in the picture, since it is cached. Not sure whether we should worry about the 1.25...
[ferrieux]: Indeed, timing shows that the jump table is roughly 3x faster than `if..elseif..elseif`, which is itself 1.25x faster than `[regexp]`. Notice that the `[regexp]` compilation overhead is not in the picture, since it is cached. Not sure whether we should worry about the 1.25...
----
[AMG]: Unlike [C] switch which does numeric comparisons, [Tcl] switch performs string comparisons.  This can make a big difference in your program if you are dealing with numbers that are formatted in different ways.

set number 0x5 switch -- $number { 5 {puts five!} default {puts unknown} }

** An Implementation in Tcl **
You may instead consider a long [if]/[elseif] chain, but maybe this won't [bytecode] as well.  Alternately, force the [format]ting to be consistent:
[rwm]: I recently needed to run some [Changes in Tcl/Tk 8.5%|%8.5] code in [Changes in Tcl/Tk 8.4%|%8.4].  I wrote the following code to mimic the 8.5 switch in 8.4. (I looked for but could not find a compatibility library...) Note the many errors for cases that I did not need.  Maybe someone else will cover fix/post those ;)  Feel free to edit/move this as appropriate.

set number 0x5 switch -- format %d $number { 5 {puts five!} default {puts unknown} }


rwm: I recently needed to run some 8.5 code in 8.4. I wrote the following code to mimic the 8.5 switch in 8.4. (I looked for but could not find a compatibility library...) Note the many errors for cases that I did not need. Maybe someone else will cover fix/post those ;) Feel free to edit/move this as appropriate.

proc switch_8.5 args} { proc switch_8.5 {args} {

   ## this is an 8.4 version of the 8.5 switch (incomplete)
   if {[llength $args] < 2} {
      error "usage: switch ?options? string {pattern body ?pattern body ...?}"
   }
   set type exact
   while {[string match "-*" [lindex $args 0]]} {
      set option [lindex $args 0]
      set args [lrange $args 1 end]
      switch -- $option {
         -exact    {set type exact}
         -nocase   {set type nocase}
         -glob     {set type glob}
         -regexp   {set type regexp}
         -matchvar {set matchvar [lindex $args 0]; set args [lrange $args 1 end]}
         -indexvar {error "unimplemented"}
         -- {break}
         default {error "bad switch option=$option"}
      }
   }
   set string [lindex $args 0]
   if {[llength $args] == 2} {
      set body [lindex $args 1]
   } else { 
      set body [lrange $args 1 end]
   }
   switch -- $type {
      exact  {error "unimplemented"}
      nocase {error "unimplemented"}
      glob   {error "unimplemented"}
      regexp {
         foreach {rexp script} $body {
            if { $rexp == "default" } {
               uplevel 1 $script
               break
        }
            if {[regexp -- $rexp $string match 1 2 3 4 5 6 7 8 9]} {
               if {[info exists matchvar]} {
                  upvar $matchvar m
                  set m [list $match $1 $2 $3 $4 $5 $6 $7 $8 $9]
                  uplevel 1 $script
                  unset m
               } else {
                  uplevel 1 $script
               }
               break
            }
         }
      }
      default {error "internal type=$type error"}
   }
----
[neb] ddd asked about http://groups.google.com/group/comp.lang.tcl/browse_thread/thread/b6c3591dbbbf2b40?hl=en%|%extending switch statements%|%. I'm putting some of the answers here, because I intend to possibly use them =]

Schelte Bron said: The switch command (at least in the format you used) takes a list as its last argument. So you can simply put that into a variable and use the normal list operations to manipulate it:
set switch {
   a*b     -
   b       {expr 1}
   b       {expr {1}}
   a*      {expr {2}}
   default {expr {3}}

switch -glob aaab $switch

set switch [linsert $switch end-2 c {expr 4}]
set switch [linsert $switch end-2 c {expr {4}}]
switch -glob c $switch

dkf said: I'd do it like this, using the alternate form of switch: set defs {

  set defs {
      a*b     -
      b       {expr {1}}
      a*      {expr {2}}
  }

switch -glob -- $value {*}$defs default {

  switch -glob -- $value {*}$defs default {
      expr {3}
  }

lappend defs c {expr 4}

  lappend defs c {expr {4}}

Yet you won't disrupt the special handling of 'default', which has to be the last clause. (You could also put the default handling script in its own variable, or other arbitrarily complex solution. Your call.) Yet you won't disrupt the special handling of default, which has to be the last clause. (You could also put the default handling script in its own variable, or other arbitrarily complex solution. Your call.)