"Steve Wart" <[hidden email]> wrote in message
news:Qe2k6.132886$[hidden email]... > I hate to wade into this one, but what the hell. > > "switch" statements are kind of neat in a way, since they are not really > semantically the same as a nested collection of boolean tests. > > Since this discussion seems to have been about syntax so far, this seems > worth mentioning. > > What is really going on (okay, assuming one knows what the compiler is > *really* doing), is that a "jump table" is generated. You should not *assume* that the compiler will or should build a jump table. In fact, that is just an optimization for the special situation where the cases are all literal numeric constants (effectively enum values). In general, a switch is a sequence of nested (arbitrarily complex and dynamic) if expressions with the additional ability (via break/no-break) to share/combine condition actions. ====== Switches have distinct value in OO languages like Smalltalk because: 1. They enable control flow decision rules to be succinctly and clearly co-located for explicit (intention revealing) ease of reference, maintenance and understanding. Which also reduces method clutter and confusion. 2. They enable efficient and best use flow of control for non-trivial *value* decisions. Innapropriate OO language use would be using switch or if control flow constructs for decisions based on <type> behavior. Conversely, not using switch or if control flow constructs for decisions based on values of a given <type> is equally innappropriate (i.e., using messaging and discrete methods instead of conditional control flow constructs). Situations arise where both type and value conditions need to be intermingled. This is a gray area but it suggests design work is still in progress. Use of the following switch-like substitute via OO languages is *poor* use of OO facilities and is *not* intention revealing: selectorIndex := someLikelyComplexExpressionCallChain. selector := #(selectorA selectorB ...) at: selectorIndex. expr perform: selector Use of a selector as a state machine a gray area: stateActionSelector := nextStateSelector. nextStateSelector := self computeNextStateSelector. self perform: stateActionSelector. ====== Here are the forms used in SmallScript and QKS Smalltalk -------------------------------------------------------- "" SmallScript form result := switch(cond) [ case exprA: statements1. break. case exprB: statements2. "" B will fall through and also do statements3 case exprC: "" SmallScript allows case combining case exprD: statements3. break. default: statements. break. ]. "QKS Smalltalk form (which, as you'd expect works in SmallScript)" result := (Switch new) case: [exprA] do: [ statements1 ]; case: [exprB] do: [ statements2. statements3. "case actions must be duplicated in Smalltalk" ]; case: [exprC] do: [ statements3 "case actions must be duplicated in Smalltalk" ]; case: [exprD] do: [ statements3 "case actions must be duplicated in Smalltalk" ]; default: [ statements ]; on: cond. The above examples also illustrate that switch expressions can support combining of cases to share a single sequence of actions. Also noting that QKS Smalltalk does not require the cases to be declared as a block [expr], that is optional. The case expressions can be any form of expression. If they are a block expression, they may optionally take an argument which is the switch condition. In both SmallScript and Smalltalk forms the entire switch expression and all its blocks are inlined. However, the evaluation rule is that each case is tested via an equality message. At the first case where the equality message applied to the <case> and the <cond> returns true, the search for a matching case *stops* and the the cases statements are executed and returned as the result of the switch expression. A hash or direct lookup "table" for cases is a compiler optimization (used as much as possible in C/C++ compilers) where all the cases are numeric values, and the switch <cond> type is also a numeric value. Which in C/C++ is all your allowed to use within a switch expression. However, it does not need to be all your allowed in Smalltalk, etc. The switch equality messages are: "Smalltalk forms" #on: #= #equivalentlyOn: #=~ #exactlyOn: #== "" SmallScript forms switch(cond) #= switch:on(cond) #= switch:equal...(cond) #= switch:equiv...(cond) #=~ switch:is(cond) #== switch:ident...(cond) #== switch:exact...(cond) #== "" or any binary message selector (e.g.) switch:=(cond) #= switch:=~(cond) #=~ switch:==(cond) #== switch:==~(cond) #==~ -- Dave Simmons [www.qks.com / www.smallscript.com] "Effectively solving a problem begins with how you express it." > > Wouldn't the equivalent construct in Smalltalk would be a dictionary whose > values are blocks? > > Then you could initialize something like this: > > switch := Dictionary new > at: #doSomethingCondition put: [ self doSomething ]; > at: #continueCondition put: []; > at: #breakCondition put: [ ^self bailout ]; > yourself. > > then later refer to it like > > conditions do: [ :condition | > (switch at: condition ifAbsent: [ self defaultConditionBlock ]) > value ] > > So it's not all in one method. I suppose if that were an important > consideration (hard to picture why), you probably wouldn't be using > Smalltalk anyhow. > > But this does seem more compact and powerful than what you could do with a > "switch" statement. > > Steve > > "Jeffrey Odell" <[hidden email]> wrote in message > news:RmRj6.41$[hidden email]... > > > > "A SERFer" <[hidden email]> wrote in message > news:[hidden email]... > > > Ron Jeffries <[hidden email]> writes: > > > > > > >On Fri, 16 Feb 2001 14:30:22 GMT, Chris Lopeman <[hidden email]> > > > >wrote: > > > >Well, I guess you don't have an example. > > > > > > > >I have found it valuable to use languages as they were intended to be > > > >used by their designers. It seems to push new ideas into my head. So > > > >don't try to program FORTRAN in C or C in Pascal or Smalltalk. Works > > > >for me. > > > > [ more stuff ] > > |
[hidden email] (David Simmons) wrote (abridged):
> [Some stuff about Smalltalk in general] Does this need to be cross-posted to the Dolphin group? I should think that anyone interested in general Smalltalk issues would read comp.lang.smalltalk directly. This discussion is now in 4 groups, which is surely too many. Dave Harris, Nottingham, UK | "Weave a circle round him thrice, [hidden email] | And close your eyes with holy dread, | For he on honey dew hath fed http://www.bhresearch.co.uk/ | And drunk the milk of Paradise." |
In reply to this post by David Simmons
"David Simmons" <[hidden email]> wrote
> "Steve Wart" <[hidden email]> > > I hate to wade into this one, but what the hell. > > > > "switch" statements are kind of neat in a way, since they are not really > > semantically the same as a nested collection of boolean tests. > > > > Since this discussion seems to have been about syntax so far, this seems > > worth mentioning. > > > > What is really going on (okay, assuming one knows what the compiler is > > *really* doing), is that a "jump table" is generated. > > You should not *assume* that the compiler will or should build a jump > In fact, that is just an optimization for the special situation where the > cases are all literal numeric constants (effectively enum values). sure. > In general, a switch is a sequence of nested (arbitrarily complex and > dynamic) if expressions with the additional ability (via break/no-break) to > share/combine condition actions. > > ====== > Switches have distinct value in OO languages like Smalltalk because: > > 1. They enable control flow decision rules to be succinctly and clearly > co-located for explicit (intention revealing) ease of reference, maintenance > and understanding. Which also reduces method clutter and confusion. > > 2. They enable efficient and best use flow of control for non-trivial > *value* decisions. > > Innapropriate OO language use would be using switch or if control flow > constructs for decisions based on <type> behavior. > > Conversely, not using switch or if control flow constructs for decisions > based on values of a given <type> is equally innappropriate (i.e., using > messaging and discrete methods instead of conditional control flow > constructs). > > Situations arise where both type and value conditions need to be > intermingled. This is a gray area but it suggests design work is still in > progress. I'm not sure I've ever missed using a switch statement in ST, although I have used 'em in C code, but that was years ago before I understood the distinction between type and value conditions. Okay, I confess, I still don't understand the distinction. Maybe an example? > Use of the following switch-like substitute via OO languages is *poor* use > of OO facilities and is *not* intention revealing: > > selectorIndex := someLikelyComplexExpressionCallChain. > selector := #(selectorA selectorB ...) at: selectorIndex. > expr perform: selector > > Use of a selector as a state machine a gray area: > > stateActionSelector := nextStateSelector. > nextStateSelector := self computeNextStateSelector. > self perform: stateActionSelector. State machines are the only case I've ever really used a switch statement in C code. The problem with writing state machines this way, is that although they usually work (there is a simple mapping between a state diagram and the case statements), my experience has been that you need to completely rewrite them if your state machine changes, even a little bit. Not to mention that state transition diagrams do not map nicely into ASCII documentation, and paper-based diagrams usually vanish a few days after you finish coding up your parser :( For a while I thought it would be interesting in Smalltalk to use classes to represent the states, and then have polymorphic methods which do the state transitions, but never took it anywhere. I have heard that state-based network software is brittle; what is it about state machines that make them a gray area for you? > ====== > > Here are the forms used in SmallScript and QKS Smalltalk > -------------------------------------------------------- > > "" SmallScript form > result := switch(cond) [ > case exprA: > statements1. > break. > case exprB: > statements2. "" B will fall through and also do statements3 > case exprC: "" SmallScript allows case combining > case exprD: > statements3. > break. > default: > statements. > break. > ]. > > "QKS Smalltalk form (which, as you'd expect works in SmallScript)" > result := (Switch new) > case: [exprA] do: [ > statements1 > ]; > case: [exprB] do: [ > statements2. > statements3. "case actions must be duplicated in Smalltalk" > ]; > case: [exprC] do: [ > statements3 "case actions must be duplicated in Smalltalk" > ]; > case: [exprD] do: [ > statements3 "case actions must be duplicated in Smalltalk" > ]; > default: [ > statements > ]; > on: cond. > > The above examples also illustrate that switch expressions can support > combining of cases to share a single sequence of actions. > I'm still not convinced this type of statment is really necessary in Smalltalk. I think judicious use of polymorphism is easier to maintain/extend than a case construct. No reason not to add it if you need it, but I don't think there is a strong enough case to add anything to the compiler. As I indicated, you can emulate a jump table with a dictionary, which you correctly point out is not necessarily how a "real" switch statement would be compiled, it's still more efficient than cascading messages (although if the number of cascaded tests becomes an issue, there are probably bigger problems to deal with). Cheers, Steve |
"Steve Wart" <[hidden email]> wrote in message
news:aAmk6.140233$[hidden email]... > > "David Simmons" <[hidden email]> wrote > > "Steve Wart" <[hidden email]> > > > I hate to wade into this one, but what the hell. > > > > > > "switch" statements are kind of neat in a way, since they are not really > > > semantically the same as a nested collection of boolean tests. > > > > > > Since this discussion seems to have been about syntax so far, this seems > > > worth mentioning. > > > > > > What is really going on (okay, assuming one knows what the compiler is > > > *really* doing), is that a "jump table" is generated. > > > > You should not *assume* that the compiler will or should build a jump > table. > > In fact, that is just an optimization for the special situation where the > > cases are all literal numeric constants (effectively enum values). > > sure. > > > In general, a switch is a sequence of nested (arbitrarily complex and > > dynamic) if expressions with the additional ability (via break/no-break) > to > > share/combine condition actions. > > > > ====== > > Switches have distinct value in OO languages like Smalltalk because: > > > > 1. They enable control flow decision rules to be succinctly and clearly > > co-located for explicit (intention revealing) ease of reference, > maintenance > > and understanding. Which also reduces method clutter and confusion. > > > > 2. They enable efficient and best use flow of control for non-trivial > > *value* decisions. > > > > Innapropriate OO language use would be using switch or if control flow > > constructs for decisions based on <type> behavior. > > > > Conversely, not using switch or if control flow constructs for decisions > > based on values of a given <type> is equally innappropriate (i.e., using > > messaging and discrete methods instead of conditional control flow > > constructs). > > > > Situations arise where both type and value conditions need to be > > intermingled. This is a gray area but it suggests design work is still > > progress. > > I'm not sure I've ever missed using a switch statement in ST, although I > have used 'em in C code, but that was years ago before I understood the > distinction between type and value conditions. Okay, I confess, I still > don't understand the distinction. Maybe an example? A simple but general case is that of numbers. Values like: -1, 58, 98784374, 12 Are all instances of the same type: <SmallInteger> The same kind of simple but general case can frequently be seen for classes such as <String> and <Symbol>. Obviously you don't want to write methods on those classes or helper classes just to deal with specific values. By logical extension, you don't want to write methods or helper class methods to break out each of the decisions with regard to specific (instances) values of user defined (classes) types. > > > Use of the following switch-like substitute via OO languages is *poor* use > > of OO facilities and is *not* intention revealing: > > > > selectorIndex := someLikelyComplexExpressionCallChain. > > selector := #(selectorA selectorB ...) at: selectorIndex. > > expr perform: selector > > > > Use of a selector as a state machine a gray area: > > > > stateActionSelector := nextStateSelector. > > nextStateSelector := self computeNextStateSelector. > > self perform: stateActionSelector. > > State machines are the only case I've ever really used a switch statement > C code. The problem with writing state machines this way, is that although > they usually work (there is a simple mapping between a state diagram and the > case statements), my experience has been that you need to completely rewrite > them if your state machine changes, even a little bit. Not to mention that > state transition diagrams do not map nicely into ASCII documentation, and > paper-based diagrams usually vanish a few days after you finish coding up > your parser :( > > For a while I thought it would be interesting in Smalltalk to use classes to > represent the states, and then have polymorphic methods which do the state > transitions, but never took it anywhere. I have heard that state-based > network software is brittle; what is it about state machines that make them > a gray area for you? I think you misunderstood my "gray area" comment. I was not making any comment on "state machines". I was commenting on the "use of selectors with #perform:" as a mechanism for implementing state machines. In my opinion Smalltalk is an excellent language for building state machines. I've built uncountable state machines over the years in many different languages. They have their place and in that place they are not brittle -- quite the opposite. A brittle state machine is just a state machine whose design was not well planned or was just poor. The "brittle" factor may also come in where "performance" overrides design -- but that is a whole other topic. A state machine, by definition, is something where every state and transition is known and therefore it should be impossible to have unknown states or transition situations. That means that state machines are easier than many other problem types for both understanding and developing good designs which should make it easier to avoid brittle design issues. If you introduce new states or transitions, that is a different state machine. The idea that adding new states or transitions requires regenerating the state machine, or if hand built, updating the state decision code, does not make state machines inherently brittle. By analogy, that would be like saying that using OO is brittle because when your design changes you may need to refactor your classes and methods and that can be hard without good tools and can lead to fragile designs? > > > ====== > > > > Here are the forms used in SmallScript and QKS Smalltalk > > -------------------------------------------------------- > > > > "" SmallScript form > > result := switch(cond) [ > > case exprA: > > statements1. > > break. > > case exprB: > > statements2. "" B will fall through and also do > > case exprC: "" SmallScript allows case combining > > case exprD: > > statements3. > > break. > > default: > > statements. > > break. > > ]. > > > > "QKS Smalltalk form (which, as you'd expect works in SmallScript)" > > result := (Switch new) > > case: [exprA] do: [ > > statements1 > > ]; > > case: [exprB] do: [ > > statements2. > > statements3. "case actions must be duplicated in Smalltalk" > > ]; > > case: [exprC] do: [ > > statements3 "case actions must be duplicated in Smalltalk" > > ]; > > case: [exprD] do: [ > > statements3 "case actions must be duplicated in Smalltalk" > > ]; > > default: [ > > statements > > ]; > > on: cond. > > > > The above examples also illustrate that switch expressions can support > > combining of cases to share a single sequence of actions. > > > [ some implementation notes omitted ] > > I'm still not convinced this type of statment is really necessary in > Smalltalk. I think judicious use of polymorphism is easier to > maintain/extend than a case construct. No reason not to add it if you need > it, but I don't think there is a strong enough case to add anything to the > compiler. Hmm. "Polymorphism" is about types and their behavior, not about creating individual behaviors for discrete values of a type. If one finds oneself trying to do that, then I suggest that there is some confused understanding of types, polymorphism, and OO in general. Where polymorphism would be appropriate to your ultimate problem but you just have a set of values for a given type, like integers, you may need to define classes to represent the different values so they can have individual behavior. I.e. You need to map a set of values (think of it as data not objects) of a given type into objects (instances of different types of objects). This is a fairly common situation where switch statements and lookup tables are both appropriate solutions. One of the criteria affecting the choice of which one to use is whether or not the mapping can be reduced to a simple key<->value relationship, or whether it has more complex dependencies and relational combinatorics. However, the idea that polymorphism should be used to manage decisions about values of a given type is just innapropriate. If you have a set of numbers or a set of strings that you need to map to some decision set, writing a lot methods for each of the value test cases is just obfuscating the intent and muddling the design (it is also very inefficient). Just like one can twist functional and procedural language constructs to avoid cases where OO functionality would serve best, one can try to twist OO to avoid functional and procedural control flow capabilities -- that doesn't mean (just because one is using OO) that it is a best practice solution. > > As I indicated, you can emulate a jump table with a dictionary, which you > correctly point out is not necessarily how a "real" switch statement would > be compiled, it's still more efficient than cascading messages (although if > the number of cascaded tests becomes an issue, there are probably bigger > problems to deal with). I don't understand the "cascaded" messages comment -- if the compiler optimizes the code then the actual declaration form is irrelevant with regard to efficiency. Therefore other criteria apply to the choice of cascading. Such as, that it is just more natural for Smalltalk style than ultra long message names as others have suggested. And, the cascade form supports design of clean and simple <Switch> class built with a handful of methods which enable support for all the switch semantics in portable (non-optimized/slow) Smalltalk code. Both the switch forms I cited in my original post are fully implemented and optimized in the SmallScript language and QKS Smalltalk language implementations I designed and built (we've had switches in our Smalltalk for nearly 10 years). For certain classes of problems, their use makes it easier to understand a decision matrix (flow of control). Optimization by the compiler results in performance that is significantly faster than trying to contort Smalltalk OO facilities innapropriately. ---- Our compilers have always optimized our code and I have always designed both our object model and instruction sets for optimization. When I first came to Smalltalk many years ago, and even up until about five years ago, most of the Smalltalk compilers were surprisingly poor at generating code. It was surprising how even simple things like peephole optimization and constant folding were not done. (let alone important things like block and context management optimizations). I don't know where they stand now, but from discussion I've had with other implementors I believe they have improved quite a bit. -- Dave Simmons [www.qks.com / www.smallscript.com] "Effectively solving a problem begins with how you express it." > > Cheers, > Steve |
"David Simmons" <[hidden email]>
> "Steve Wart" <[hidden email]> wrote > > [ snip ] > > I'm not sure I've ever missed using a switch statement in ST, although I > > have used 'em in C code, but that was years ago before I understood the > > distinction between type and value conditions. Okay, I confess, I still > > don't understand the distinction. Maybe an example? > > A simple but general case is that of numbers. > > Values like: > > -1, 58, 98784374, 12 > > Are all instances of the same type: <SmallInteger> > > The same kind of simple but general case can frequently be seen for > such as <String> and <Symbol>. Obviously you don't want to write methods on > those classes or helper classes just to deal with specific values. > > By logical extension, you don't want to write methods or helper class > methods to break out each of the decisions with regard to specific > (instances) values of user defined (classes) types. So when you talk about distinguishing between values and types, does it refer only to these "basic" classes or does this argument extend to other classes as well? I think I understand your argument for avoiding polymorphism for specific instances as a general principle. I've seen a lot of code that looks something like (aCompany label = 'XYZ Customer') ifTrue: [ aCompany doSomething ]. I consider this poor practice, but programmers (especially beginning programmers) often find it difficult to deal with special cases. I suppose you could subclass roles of an object and use double-dispatching and polymorphism to avoid these situations, but there is a risk of introducing multiple points of failure which can be hard to track down (again, especially for beginning programmers). One alternative would be to have multiple variants of a method signature on a given class. e.g. Number>>#add: <anInteger> Number>>#add: <aFloat> Number>>#add: <aPoint> And double-dispatching becomes unnecessary. It might be simple to extend this (although perhaps terribly wrong) to allow forms like: MyStateMachine>>#input: 3 MyStateMachine>>#input: -12322 MyStateMachine>>#input: #endToken MyStateMachine>>#input: <anEndOfStreamSignal> Then the compiler can bury the logic (even generate a jump table if appropriate), but the programmer is dealing only with one case in each variant of the method. > > For a while I thought it would be interesting in Smalltalk to use classes to > > represent the states, and then have polymorphic methods which do the state > > transitions, but never took it anywhere. I have heard that state-based > > network software is brittle; what is it about state machines that make them > > a gray area for you? > > I think you misunderstood my "gray area" comment. I was not making any > comment on "state machines". I was commenting on the "use of selectors with > #perform:" as a mechanism for implementing state machines. > > In my opinion Smalltalk is an excellent language for building state > machines. > > I've built uncountable state machines over the years in many different > languages. They have their place and in that place they are not brittle -- > quite the opposite. A brittle state machine is just a state machine whose > design was not well planned or was just poor. The "brittle" factor may also > come in where "performance" overrides design -- but that is a whole other > topic. I guess different application domains impose different design patterns. I wonder if there is a state-machine design pattern that could provide a nice high-level abstraction for dealing with a large number of cases where the "switch" statement seems to be required? > A state machine, by definition, is something where every state and > transition is known and therefore it should be impossible to have unknown > states or transition situations. That means that state machines are easier > than many other problem types for both understanding and developing good > designs which should make it easier to avoid brittle design issues. > > If you introduce new states or transitions, that is a different state > machine. The idea that adding new states or transitions requires > regenerating the state machine, or if hand built, updating the state > decision code, does not make state machines inherently brittle. > > By analogy, that would be like saying that using OO is brittle because > your design changes you may need to refactor your classes and methods and > that can be hard without good tools and can lead to fragile designs? Well, state machines are not encapsulated. By definition the state is of the entire machine, so changing one part of it can affect the whole thing. OO is a bit different in theory, if only because it provides better support for hiding the brittle stuff. OTOH, if all my behavior is in one method, then OO doesn't buy much. > > [ some implementation notes omitted ] > > > > I'm still not convinced this type of statment is really necessary in > > Smalltalk. I think judicious use of polymorphism is easier to > > maintain/extend than a case construct. No reason not to add it if you need > > it, but I don't think there is a strong enough case to add anything to the > > compiler. > > Hmm. "Polymorphism" is about types and their behavior, not about creating > individual behaviors for discrete values of a type. If one finds oneself > trying to do that, then I suggest that there is some confused understanding > of types, polymorphism, and OO in general. > [ more about polymorphism and values ] This makes sense. My concerns are mostly that I find the "switch" statement inelegant and subject to abuse. It may be that it is useful for certain types of applications (e.g. tokenizers), but languages with an equivalent statement were around before the first Smalltalk environments were built. If it is so useful (especially for parsers and compilers), why was it omitted from the language in the first place? My feeling is that rather before lifting a construct that exists with various degrees of sophistication in a number of other languages that we should understand what it means in an OO context. If you're the one writing the compiler, and you already have this understanding, then you really are the only one you have to answer to. For me, it's a curiosity. I use the tools I'm given and try to impose my own sense of aestetics (which is always changing). [ notes on optimization omitted ] > -- Dave Simmons [www.qks.com / www.smallscript.com] > "Effectively solving a problem begins with how you express it." Steve |
Free forum by Nabble | Edit this page |