A Smalltalk programozási nyelv

Vezérlési szerkezetek

Eddig láttuk, hogy hogyan lehet kifejezések sorozatát elkészíteni. De miként lehet bonyolultabb vezérlési szerkezeteket előállítani? Természetesen itt is objektumokat használ a Smalltalk.

Blokkok

A blokkok szerepe megegyezik a más nyelvekben használt programblokkok szerepével, azonban nem képeznek külön nyelvi elemet, hanem ezek is egyszerű objektumok. Ezen objektumok osztálya a HomeContext, vagy más implementációban a BlockClosure. Amikor a Smalltalk egy blokkal találkozik, akkor nem kezdi el feldolgozni, csupán eltárolja azt - később a neki küldött üzenettel lehet végrehajtani. Blokk definiálásához a szögletes zárójeleket használjuk. A blokk alapértelmezett visszatérési értéke a benne utoljára végrehajtott utasítás eredménye, de ezt felüldefiniálhatjuk a ^ segítségével, ami más nyelvek return utasításának megfelelője. Ezzel azonban vigyázni kell, hiszen egy metódusban elhelyezett ^ jel elérésekor az egész metódus futása befejeződik. Az utolsó kifejezés utáni pontot - ha van, - figyelmen kívül hagyja, és az előtte álló kifejezés értékét kapjuk vissza. Ha nincs egyetlen kifejezés sem a blokkunkban - üres blokk -, akkor a végrehajtása nil-t ad vissza. Ha egy ilyen blokkot egyszerűen végre akarunk hajtani, akkor küldjük el neki (mármint a blokk-objektumnak) a #value üzenetet:
[ index:=index+1 . list at: index put: index*index ] value

Egy blokknak lehetnek argumentumai is, amelyeket kicsit más formában kell megadni, mint máshol: minden változónév előtt kettőspontnak kell szerepelnie, és a | jelnek is csak a jobb oldalon kell szerepelnie. Argumentummal rendelkező blokk futtatásához már nem az unáris value üzenetet kell elküldeni, hanem a kulcsszavas változatai közül azt, amelyik megfelel a tömb argumentumai számának: #value: vagy #value:value: (és így tovább), vagy ha az argumentumaink egy tömbben vannak, akkor #valueWithArguments: üzenetet.

Szintaxis: [ :<lokális vált> :<lokális vált> ... | <kifejezések sorozata> ]

Például: [ :i :j | i negated = j ] value: 10 value: -10

A blokkot értékül adhatjuk egy változónak is. Az incrementBlock := [ index := index + 1] értékadás után az incrementBlock value növeli az index-et.

Blokkok másik felhasználási módja olyan metódusok hívása, amelyek paraméterként blokkot várnak.

Elágazások

Az elágazás nem más, mint egy Boolean típusú objektumnak küldött üzenet. A lehetséges utasítások a következők:

<Boolean_kifejezés> ifTrue: <blokk>

<Boolean kifejezés> ifFalse: <blokk>

<Boolean kifejezés> ifTrue: <true blokk> ifFalse: <false blokk>

<Boolean kifejezés> ifFalse: <false blokk> ifTrue: <true blokk>

Például:

Feltételes ciklusok

A Smalltalk szemlélete lehetővé teszi, hogy nagyon sokféle ciklusutasítást használjunk, sőt akár saját magunk is definiálhatunk nekünk megfelelő ciklusokat, hiszen mi is készíthetünk olyan metódust, amelynek paraméterei blokkok. A legegyszerűbb feltételes ciklusutasítások a következők:

<blokk1> whileTrue: <blokk2>

<blokk1> whileFalse: <blokk2>

Érdemes megfigyelni, hogy mind a fogadó, mind a paraméter egy blokk így ez a két metódus az elöl- és hátultesztelő ciklusok megvalósítására használható, sőt "középen-tesztelő" ciklusként is működik.

Például:

[ i < 10 ] whileTrue: [ sum := sum + (a at: i). i := i + 1 ] "Legnagyobb közös osztó keresése" | a b | a := 345. b := 230. [ a ~= b ] whileTrue: [ a < b ifTrue: [ b := b - a ] ifFalse: [ a := a - b ] ]. ^a

For ciklus

A for-ciklust a Number osztály metódusaként valósították meg (tehát például valós számokkal is működik), a következő kétféle szintaxissal:

<alsó határ> to: <felsô határ> do: [:<ciklusváltozó> | <ciklusutasítások> ]

<alsó határ> to: <felsô határ> by: <lépésköz> do: [:<ciklusváltozó> | <ciklusutasítások> ]

Például:

Triviális ciklusok

Gyakran van szükségünk arra, hogy egy blokkot adott számban végrehajtsunk, de a for-ciklus változója valójában nem érdekel minket. Erre ad lehetőséget az alábbi metódus:

<Hányszor> timesRepeat: [ <ciklusutasítások>]

Például:

4 timesRepeat: [ i := i * 2 ]

Végtelen ciklust még könnyebb készíteni: egy blokknak küldjük el a #repeat üzenetet.

[i:=i+1] repeat

Iterátorok

Az iterátorokkal egy gyűjtemény (tömb, halmaz, file...) elemein tudunk végighaladni, és az összesre végrehajtani egy műveletet.
Valójában ezek mind Collections osztályoknak küldött üzenetek, hátterüket lásd részletesebben a Beépített osztályok, Collections menüpont alatt.

A do: iterátor

<tömb vagy stream> do: [ :<ciklusváltozó> | <utasítások> ]

Végigmegy a tömb ill. a file elemein, egyenként behelyettesítve őket a ciklusváltozóba, és végrehajtva rá a ciklus magját. Példa:

"A string magánhangzóinak megszámolása" | vowels | vowels := 0. 'Now is the time' do: [ :char | char isVowel ifTrue: [ vowels := vowels + 1 ] ]. ^vowels "kihagyja a kocsivissza (ascii 13) karatereket egy file-ból, és megadja a számukat is" | output stripped | stripped := 0. output := File pathName: 'stripped.go'. (File pathName: 'go') do: [ :char | char = 13 asCharacter ifTrue: [ stripped := stripped + 1 ] ifFalse: [ output nextPut: char ] ]. output close. ^stripped

A select: iterátor

<tömb> select: [ :<select változó> | <select feltétel> ]

Visszaad egy tömböt, aminek elemei a fogadó tömb azon elemei amelyekre a select feltétel igaz. Példa: magánhangzók megszámlálása egy stringben:

('Now is the time' select: [ :c | c isVowel ]) size

A reject: iterátor

<tömb> reject: [ :<reject változó> | <reject feltétel> ]

A select: ellentéte: visszaad egy tömböt, aminek elemei a fogadó tömb azon elemei amelyekre a reject feltétel nem igaz. Példa: azoknak a számjegyeknek megkeresése, amikre n! < n^4:

#( 1 2 3 4 5 6 7 8 9 ) reject: [ :i | i factorial >= (i*i*i*i) ]

A collect: iterátor

<tömb> collect: [ :<collect változó> | <collect utasítások> ]

Visszaad egy tömböt, aminek elemei a fogadó tömb elemeire végrehajtva a collect utasítások eredménye. Példa: tömb elemek négyzetre emelése:

#( 1 13 7 10 ) collect: [ :i | i*i ]

Saját vezérlési szerkezetek

Mivel blokkokat is átadhatunk paraméterként, így tetszőleges vezérlési szerkezetet megvalósíthatunk. A case szerkezet megvalósításához használhatjuk például azt a megoldást, hogy a blokkokat tömbbe fűzzük össze, és egy ilyen tömböt adunk át a metódusunknak, azonban a # tömboperátorral ilyen, blokkokból álló tömböt nem tudunk létrehozni, ezért nem túl kényelmes ez a megoldás.