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.
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.
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:
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:
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:
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
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.
<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:
<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
<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) ]
<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 ]
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.