3 ms·
I think these arguments about whether language X “needs” a switch statement are asking the wrong question. The right question is, “Is language X better for hav
by klodolph 6y ago
I think these arguments about whether language X “needs” a switch statement are asking the wrong question.
The right question is, “Is language X better for having a switch statement?”
Very few control structures are strictly necessary because it is almost always possible to achieve the same thing in a different way. For example, in C, you could rewrite this:
while (x < 10) {
// ... some code ...
}
To this:
goto test;
begin:
// ... some code ...
test:
if (x < 10) {
goto begin;
}
The fact is, there are important differences between a map full of objects and a switch statement. One IMPORTANT difference is that if the switch branches contain references to variables the outer scope, then to convert the switch into a map, you have to construct the object at least once each time you enter that scope, and the cost of constructing it is proportional to the number of branches. Similarly, if you use a chain of if/else statements, then the cost of finding a branch selected uniformly is proportional to the number of branches.
With a switch statement, there is normally some kind of assumption that the compiler is capable of optimizing a subset of these use cases—often a well-known or well-understood subset, and that the switch is fast—perhaps similar to a dictionary lookup or table lookup—yet without the cost of constructing a closure for each branch. Even a novice compiler developer can figure out how to make their compiler generate a table for integers or strings.
“I’ll just make this control structure out of lambdas” is something that’s theoretically sound, but which may require a much more sophisticated set of optimizations to get similar performance out of.
This is not a hypothetical concern—if you want to implement a lexical analyzer or parser, you often want to build it out of big switch statements, especially if the switch statements can be transformed at compile-time into table lookups or something similarly fast. The cost of parsing data, in my experience, can often be a significant fraction of the runtime of the entire program.
(If your response is, “Well, why write a parser in Python if you want it to be fast?”, then, well, keep in mind that CPython is not the only Python, and there are many reasons why you would want to write a parser in Python otherwise.)
- compressedgas 6y agoYou can just lift the objects so that they are a function of the scope so that the table of them only need to be constructed once. Python (C) does not have the tabled indirect jump that would be required to convert the table of objects into a table of offsets thereby turning the function call into a jump. The compiler could use such a tabled indirect jump for every switch statement. Some of which could be optimized by using a different type of table rather than always using a frozendict.
- klodolph 6y ago> You can just lift the objects so that they are a function of the scope so that the table of them only need to be constructed once. So, I had very carefully worded that statement. I said you have to construct them "each time you enter that scope." Although CPython does not have a jump table opcode, it easily could, if it were a priority.
- compressedgas 6y agoI might not have understood it correctly. I thought you were thinking of: def bar(x): def X(): return 1 def Y(): return 1 def Z(): return bar(x-1) + bar(x-2) table = {0:X,0:Y} default = Z return table.get(x, default)() Whereas I was thinking of: def X(x): def X(): return 1 return X def Y(x): def Y(): return 1 return Y def Z(x): def Z(): return bar(x-1) + bar(x-2) return Z table = {0:X,1:Y} default = Z def bar(x): return table.get(x, default)(x)() Which can be reduced to a tabled indirect jump: def bar(x): jump by x via table T and default D where T = {0:X,1:Y} and D = Z X: return 1 Y: return 1 Z: return bar(x-1) + bar(x-2)