A SwitchStatement is a more structured way (than multiple if...else statements) to express conditional logic in C-like languages. Usually looks something like this:
switch(expression) { case a: DoSomething(); break; case b: DoSomethingElse(); break; default: HandleGeneralCase(); break; }Other non-C-like languages have similar constructs, often with the keywords "case" or "select", often without the need for an explicit "break".
A switch can be invaluable in parsing, or as part of an interface between ObjectOriented code and ProceduralCode. A switch is easier to read than a long if-then-else stack, and almost always faster as well.
Discussion on speed and Optimization
"...almost always..."
Can you substantiate this? Does it apply to all languages with switch statements?
[It is optimized by the compiler as a whole into typically one of three different machine code approaches, which is made possible by the fact that the case expressions are constants. If the case expressions form a dense range, then the whole thing is turned into an indirect jump through a table, for instance. Basically it is optimized to be as fast as it possibly could be. A series of if-then-else statements almost always has to be simply implemented as it was coded, therefore e.g. a sequence of 50 if-then-elses will be as much as 50 times slower than a switch. This is all well-known in the compiler world; it is not something controversial.]
[Some languages do not require the case expressions to be constants, e.g. Lisp (cond), in which case it is just syntactic sugar on top of if-then-elses and not a true SwitchStatement as invented in Pascal and C. COND is not a switch! A switch is spelled CASE in Lisp (see below).]
With optimizing compilers and so many different languages to consider, it is dangerous to generalize, but the basic speedup that a switch exploits is that expression is evaluated only once, then compared against each case until one matches. Also, since switch statements are so common, they tend not to be just syntactic sugar, but directly supported right down to the hardware.
The advantage of a SwitchStatement is that you can have the compiler decide the trade-off point; i.e. if the SwitchStatement has only a few clauses, the compiler will probably produce a conditional jump cascade, but for many clauses, it will switch (sic) to a jump table approach. (Or one of several other approaches, depending on the data.)
And SwitchStatements almost always have many clauses, in the wild.
I did this for the ARM compiler 14 years ago. I'm sure it's commonplace now, but I was proud of it at the time :-) See this 6 year old posting http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&safe=off&selm=96-07-159%40comp.compilers
Fall Through
A feature (or some would say misfeature) of C's switch construct is that control flow can "fall through" from one case to another. For example:
switch (command) { case CMD_SHOW_HELP_AND_EXIT: do_show_help(); /* Fall thru */ case CMD_EXIT: do_exit(); break; case CMD_OTHER: do_other(); break; /* ... etc. ... */ }Because the do_show_help(); line is not followed by a break, the do_exit() call will be executed after do_show_help() when CMD_SHOW_HELP_AND_EXIT is the command. It is traditional to use a comment such as /* Fall thru */ in the example to indicate to readers that a break was not left out unintentionally (a common mistake).
Proponents say that this technique leads to more efficient control dispatching when multiple cases share code. Critics say that using this technique leads to unstructured hard-to-maintain code, and is little better than using gotos.
Many optimizing C compilers will emit the same object code for the above code as for the below -- they merge the common trailing portions of case blocks.
switch (command) { case CMD_SHOW_HELP_AND_EXIT: do_show_help(); do_exit(); break; case CMD_EXIT: do_exit(); break; case CMD_OTHER: do_other(); break; /* ... etc. ... */ }
CeeLanguage switch (DuffsDevice)
int q = (n+7)/8; switch (n%8) { case 0:do {foo();// Great C hack, Tom, case 7:foo();// but it's not valid here. case 6:foo(); case 5:foo(); case 4:foo(); case 3:foo(); case 2:foo(); case 1:foo(); } while (--q > 0); }
ForthLanguage (jump table from a chess program)
1 CONSTANT Pawn ( ... ) 6 CONSTANT King CREATE genVector \ each word is ( sq -- sq ) ' NOOP , ' genPawn , ' genKnight , ' genBishop , ' genRook , ' genQueen , ' genKing , ' genError , : genSquare ( sq -- sq ) DUP piece@ CELLS genVector + @ EXECUTE ; : genMoves ['] genSquare forEachSq ;ForthLanguage (ANS Forth Core Extension wordset)
: X ( n -- ) CASE test1 OF ( -- ) ... ENDOF testn OF ( -- ) ... ENDOF ( n -- ) ... ( default ) ENDCASE ... ;which is semantically equivalent to cascading IF statements:
: Y ( n -- ) test1 OVER = IF DROP ... ELSE testn OVER = IF DROP ... ELSE ( n -- ) ... THEN THEN ( big list of THENs to match the IFs) ;
CommonLisp switch
(case k ((1 2) 'clause1) (3 'clause2) (nil 'no-keys-so-never-seen) ((nil) 'nilslot) ((:four #\v) 'clause4) ((t) 'tslot) (otherwise 'others)))HaskellLanguage switch
Using explicit matching:
take m ys = case (m,ys) of (0,_) -> [] (_,[]) -> [] (n,x:xs) -> x : take (n-1) xsUsing implicit matching:
take 0 _ = [] take _ [] = [] take n (x:xs) = x : take (n - 1) xsJavaLanguage switch
switch (k) { case 1: case 2: return "clause1"; case 3: return "clause2"; case 0: return "nilslot"; case '4': case 'v': return "clause4"; case 't': return "tslot"; default: return "others"; }PerlLanguage switch (this is Perl 6: Perl 5 doesn't have explicit switches, although the language implementation documentation claims that the compiler will always optimize if-else into switch whenever possible; see e.g. "Programming Perl" by Larry Wall et al)
given $number { when &is_prime { warn "$_ is prime\n"; continue; } when /[13579]$/ { warn "$_ is odd"; } when /[02468]$/ { warn "$_ is even"; } }In Perl 5, you can use something like this (adapted slightly from a common idiom of mine):
&{{ "foo" => sub { bar(); baz(); }, "quux" => sub { wibble(); spam(); } }->{ $selector }};If you have a dense set of integer cases, you could use an array instead of a hash. No good idea about implementing fall-through, though :)
&{[ handle_0, handle_1, complain_about_other_digits ]->[$digit]};(Usually I use this setup to just pick a scalar value based on the input. Here I adjust that slightly by using code references for the values, and then invoking the relevant code.)
[Please note that this idiom (in Perl 5) -- using a scalar as an offset into an array or hash of function addresses -- is the basic paradigm of CeeLanguage "object-oriented" method dispatch, as in CeePlusPlus, ObjectiveCee, and various homebrews. Switch statements are generally viewed as a CodeSmell in object-oriented environments because they pessimize message dispatch, which a good OO environment makes faster than the corresponding switch. Also see the discussion under "SmalltalkLanguage switch", below.]
There is a working example of this mechanism in the interpreters for SnuspLanguage.
PlSql switch
CASE grade WHEN 'A' THEN dbms_output.put_line('Excellent'); WHEN 'B' THEN dbms_output.put_line('Very Good'); WHEN 'C' THEN dbms_output.put_line('Good'); WHEN 'D' THEN dbms_output.put_line('Fair'); WHEN 'F' THEN dbms_output.put_line('Poor'); ELSE dbms_output.put_line('No such grade'); END CASE;RubyLanguage switch
case $age when 0 .. 2 "baby" when 3 .. 6 "little child" when 7 .. 12 "child" when 12 .. 18 # Note: 12 already matched by "child" "youth" else "adult" endSchemeLanguage switch
(case (car '(c d)) ((a e i o u) 'vowel) ((w y) 'semivowel) (else 'consonant)) ===> consonantVHDL switch (combinational)
library ieee; use ieee.std_logic_1164.all; entity CombinationalSwitch? is port ( A: in std_logic_vector (2 downto 0); B: in std_logic_vector (7 downto 0); F: out std_logic ); end CombinationalSwitch?; architecture RTL of CombinationalSwitch? is begin with A select F <= B(0) when "000", B(1) when "001", B(2) when "010", B(3) when "011", B(4) when "100", B(5) when "101", B(6) when "110", B(7) when "111", 0 when others; end RTL;VHDL switch (sequential)
library ieee; use ieee.std_logic_1164.all; entity SequentialSwitch? is port ( A: in std_logic_vector (2 downto 0); B: in std_logic_vector (7 downto 0); F: out std_logic ); end CombinationalSwitch?; architecture RTL of SequentialSwitch? is begin process (A, B) begin case A is when "000" => F <= B(0); when "001" => F <= B(1); when "010" => F <= B(2); when "011" => F <= B(3); when "100" => F <= B(4); when "101" => F <= B(5); when "110" => F <= B(6); when "111" => F <= B(7); when others => F <= 'Z'; end case; end process; end RTL;Notice that, since VHDL describes hardware, the Switch statement here is basically a multiplexer. In this case, this is an 8-to-1 multiplexer.
VisualBasic switch
Dim Number Number = 8 Select Case Number Case 1 To 5 Debug.Print "Between 1 and 5" Case 6, 7, 8 Debug.Print "Between 6 and 8" Case 9 To 10 Debug.Print "Greater than 8" Case Else Debug.Print "Not between 1 and 10" End SelectHebrew (Ivrit)
בדוק משתנה אם משתנה = 1 אזי שלום אם משתנה = 2 אזי להתראות אחרת לך-יא-מניאאק סייםDelphiLanguage switch note uses sets, can work with characters and enumerated types instead of integers
case I of 1..5 : Caption := 'Low'; 6..9 : Caption := 'High'; 0, 10..99 : Caption := 'Out of range'; else Caption := ''; end; case Letter of Vowels : Caption := 'Vowel'; Consonants : Caption := 'Consonant'; end;where Letter is a char and Vowels and Consonants are Sets.
SmalltalkLanguage switch
anObject aMessagewhere aMessage is a method defined on the class of anObject.
(One can also define syntax for a SmalltalkCaseStatement within the language.)
How does that work when you're switching on 1..5, 6..10, and "other"?
A Smalltalk program implementing such a dispatch (and they are exceedingly rare, because the object/message paradigm so richly moots most situations that provoke a Switch) typically looks up a method name in an Array or Dictionary which they then perform: -- the equivalent of the C expression (*action[selector])().
In the interest of advancing the discussion, I built a test harness and measured the results. I simplified your example to dispatch on a number between 1 and 16 -- this gets at the most important aspects of your question. I defined a new class, SwitchTest, with sixteen methods with selectors #m01, #m02, ... , #m16. For the sake of testing, these methods were no-ops. I then built and measured the performance of three dispatch approaches:
(aMethodsArray at: (dispatchArray at: anIndex)) executeWithReceiver: self andArguments: #()
self perform: (dispatchArray at: anIndex)
self m01; m02; "..." m16.The above is a load of bull. This is hardcoding the evaluation of m01, etc. when in reality the values for switch statements are variable. Add the fact that objects must be coaxed into selectors before they can be sent to objects.
Smalltalk needs a switch statement, because nested ifTrue/ifFalse's are ripe with design smell.
I then used Time>>millisecondsToRun: to measure the time required to execute 1000000 repetitions of each of the above three cases (on a 3Ghz Pentium with 1G RAM running IBM Smalltalk). Here are the times I measured:
Even CeeLanguage imposes some overhead to setup the switch statement, and it might be interesting measure that overhead in comparison to a straight procedure call on each of the resulting branches. (But of course a C switch/case statement is faster than a series of if-elses.)
My point in putting up the example is to put some quantitative bounds (in this case a factor of two) on the switch statement dispatch overhead imposed by the language. If I was parsing something and cared about the performance, I would probably write and debug the parser in Smalltalk and then recode the lexer into C, calling the lexer through the external function API (AlternateHardAndSoftLayers).
The interesting thing is the relative overhead of a switch-like mechanism in Smalltalk, as compared to its natural behavior (not to C). My point is that 99% of what one does with switch/case in a procedural language is mooted by a good polymorphic language (like Smalltalk). Switch/case is needed in C because you don't have polymorphic message sends (unless you want to roll your own tables of function pointers).
The C-style switch isn't needed in Smalltalk (as a separate language construct) because its function is implicit in the method dispatching behavior. If, in the pathological Smalltalk case where you DO need switch, I showed that it's about twice as slow as a "straight" message send (in Smalltalk). The performance arguments don't make sense until you look at much larger grain sizes, where the industry has learned (particularly help from AlternateHardAndSoftLayers) that C's fine-grained performance advantages do not, in general, translate to corresponding application performance differences.
CobolLanguage evaluate-when (COBOL 85)
evaluate policy-holder-age also policy-holder-sex also smoking-flag when 20 thru 40 also "F" also ANY perform young-female-policy when 65 thru 999 also "M" also "Y" perform old-smokey when other perform all-other-cases end-evaluateIt's amazing; you can do anything. (I'm not claiming it can do anything well, just that it has an amazing array of expressive options. ;-)
Let's standardize on one particular form. A simple substitution process can then generate the correct syntax for your chosen language.
Um... not true, since different languages switching capabilities are different. No one generic form can be translated to each language.
Long ago, when memory was very limited, there wasn't room for either a jump list or a series of tests in a particular program, so I calculated the required address as a linear function of the switch expression (using a shift instruction for the multiplication), and then made a jump to the calculated address. Those were the days. ;-)
See SwitchStatementsSmell, CaseStatementsConsideredHarmful (summary: use OO/generic dispatch on the type (InternalPolymorphism) in preference to switch on the type ("TypeCase", "ExternalPolymorphism"); when looking at things other than the type, if Polymorphism is impossible or introduces too much extra machinery, then use switch in preference to if-else whenever possible. Contrary to some Purists, in some languages it is not possible to replace every switch statement with polymorphism, and in other languages it is not always desirable. Classic example: switch on char value in a lexical analyzer. But switch-on-type should indeed always be replaced.)
See also: IsBreakStatementArchaic