Blog

Mechanizm switch-case w Smalltalku

ENGLISH VERSION BELOW

POLISH VERSION

Język Smalltalk nie oferuje wraz ze swoimi standardowymi klasami odpowiednika mechanizmu switch-case, popularnego na przykład w językach wywodzących sie z języka C. Dlatego programiście, który chciałby takiego mechanizmu użyć pozostaje stworzenie go sobie samodzielnie. Poniżej został przedstawiony prosty sposób implementacji takiego mechanizmu.

Pierwszym krokiem będzie stworzenie klasy Switch jako podklasy klasy Object i zdefiniowanie trzech zmiennych: value, satisfied i response.

Object subclass: #Switch
    instanceVariableNames: 'value satisfied response'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Objectspace.net-Controlling Structures'

Zmienna value będzie przechowywała obiekt, dla którego wykonywany jest switch, zmienna satisfied informuje nas, czy któryś z warunków case został już spełniony, a zmienna response będzie przechowywała wynik operacji, która zostanie wykonana w wypadku spełnienia któregoś z warunków case.

Następnie zdefiniujemy metodę klasową for: tworzącą instancję klasy Switch.

Switch>>for: anObject
   ^self new value: anObject 

Aby uniknąć konieczności inicjalizacji zmiennej satisfied wartością false zdefiniujemy metodę isSatisfied, która zwróci true tylko w wypadku, gdy zmienna satisfied ma wartość true.

Switch>>isSatisfied
   ^satisfied == true 

Teraz pozostaje nam stworzyć metody case:then: i default:.

Switch>>case: oneArgTestBlock then: execBlock
   "The oneArgTestBlock must return a Boolean value
   when ==passed the value of the receiver."

   self isSatisfied ifFalse:[
      (oneArgTestBlock value: self value) ifTrue:[
         self response: execBlock value.
         self satisfied: true]].
   ^self response

Switch>>default: execBlock
   self isSatisfied ifFalse: [
	self response: execBlock value].
   ^self response 

I już możemy pisać:

(Switch for: x)
   case: [:value | value = 1] then: ["..."];
   case: [:value | value = 2] then: ["..."];
   default: ["..."]  

Dla tak prostego bloku testującego jak powyżej możemy stworzyć bardzo przydatną metodę w klasie Switch.

Switch>>caseIs: testObject then: execBlock
   ^self
	case: [:value | testObject = value]
	then: execBlock.  

Wtedy nasz poprzedni przykład będzie wyglądał następująco:

(Switch for: x)
   caseIs: 1 then: ["..."];
   caseIs: 2 then: ["..."];
   default: ["..."]  

Powyższą metodę można także zdefiniować w wersji używającej do porównania metody ==, do szybszego porównywania symboli czy klas:

Switch>>caseIdentityIs: testObject then: execBlock
   ^self
	case: [:value | testObject == value]
	then: execBlock.  

Możemy też uprościć sobie przeszukiwanie kolekcji.

Switch>>caseIsAny: testCollection then: execBlock
   ^self
	case: [:value | testCollection includes: value] 
	then: execBlock. 

Wszystko to ładnie wygląda, ale może ktoś chciałby to jeszcze bardziej uprościć i zamiast pisać Switch for: aValue móc po prostu napisać aValue switch. Nic prostszego. W tym celu wystarczy dodać do klasy Object metodę switch.

Object>>switch
   ^Switch for: self 

I już można nasz poprzedni przykład zapisać następująco:

x switch
   caseIs: 1 then: ["..."];
   caseIs: 2 then: ["..."];
   default: ["..."]  

I to już nawet przypomina składnię switch-case z innych języków programowania. :-)

(pn)

Przedstawioną wyżej metodę switch można też zdefiniować w następujący sposób:

Object>>switch
   ^Switch for: self value 

Możliwa jest wtedy również następująca składnia:

[x\*10] switch
   caseIs: 1 then: ["..."];
   caseIs: 2 then: ["..."];
   default: ["..."]  

Powyższa implementacja metody umożliwia również proste wykorzystanie instancji klasy ValueHolder w strukturze switch-case. Metoda switch może teraz zostać wysłana bezpośrednio do takiego obiektu, odbiorcą metod typu case będzie obiekt przechowywany przez instancję klasy ValueHolder.

W przedstawionym powyżej mechaniźmie switch-case można również zrezygnować z wykorzystania zmiennej satisfied. Możliwe będzie wtedy wykonanie kilku bloków spełniających warunek case, tak jak ma to miejsce w języku C.

(wh)

Przemysław Nieściór & Wacław Hołub, 02.06.2002

ENGLISH VERSION

Smalltalk language does not offer with his standard classes a switch-case mechanism. Here is an easy way to implement such a mechanism.

The first step will be to create a class Switch as subclass of class Object and to define three variables: value, satisfied and response.

Object subclass: #Switch
    instanceVariableNames: 'value satisfied response'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Objectspace.net-Controlling Structures'

The variable value will hold the object for which the switch is made. The variable satisfied tells us, if any of the case conditions are met. The variable response will hold the result of the operation, which will be made in case of fulfillment of the conditions of any case.

Next we define a class method for: which creates an instance of the class Switch.

Switch>>for: anObject
   ^self new value: anObject 

To avoid having to initialize the variable satisfied to false, we define a method isSatisfied, which returns true only if the variable satisfied is true.

Switch>>isSatisfied
   ^satisfied == true 

Now we create methods case:then: and default:

Switch>>case: oneArgTestBlock then: execBlock
   "The oneArgTestBlock must return a Boolean value
   when ==passed the value of the receiver."

   self isSatisfied ifFalse:[
      (oneArgTestBlock value: self value) ifTrue:[
         self response: execBlock value.
         self satisfied: true]].
   ^self response

Switch>>default: execBlock
   self isSatisfied ifFalse: [
	self response: execBlock value].
   ^self response 

And now we can write:

(Switch for: x)
   case: [:value | value = 1] then: ["..."];
   case: [:value | value = 2] then: ["..."];
   default: ["..."]  

For such a simple test block as above, we can create a very useful method in class Switch.

Switch>>caseIs: testObject then: execBlock
   ^self
	case: [:value | testObject = value]
	then: execBlock. 

So our previous example will look like this:

(Switch for: x)
   caseIs: 1 then: ["..."];
   caseIs: 2 then: ["..."];
   default: ["..."]

We can also define an identity version of this method for fast comparing of symbols or classes:

Switch>>caseIdentityIs: testObject then: execBlock
   ^self
	case: [:value | testObject == value]
	then: execBlock.  

And we can simplify searching in collections:

Switch>>caseIsAny: testCollection then: execBlock
   ^self
	case: [:value | testCollection includes: value] 
	then: execBlock. 

All of this looks good, but someone would like it even easier - instead of writing Switch for: aValue simply write aValue switch. To do this we add the switch method to the class Object.

Object>>switch
   ^Switch for: self 

And our previous example can be written as follows:

x switch
   caseIs: 1 then: ["..."];
   caseIs: 2 then: ["..."];
   default: ["..."]  

(pn)

The method switch presented above can also be defined as follows:

Object>>switch
   ^Switch for: self value 

Now it is possible to use the following syntax:

[x\*10] switch
   caseIs: 1 then: ["..."];
   caseIs: 2 then: ["..."];
   default: ["..."]  

Besides, this implementation allows to use an instance of the class ValueHolder in the switch-case structure. The method switch can now be sent directly to such an object.

In the above switch-case mechanism we can also remove the variable satisfied. We will then perform several blocks satisfying the case conditions, just as it does in C.

(wh)

Przemysław Nieściór & Wacław Hołub, 02.06.2002


Sources:

or on GitHub:

Posted by Przemysław Nieściór at 11 June 2012, 12:00 pm with tags switch, case, smalltalk link