Smalltalk je čistě objektově orientovaný inkrementální jazyk. Je vystavěn na velmi obecných základech, což mu dodává nebývalou mocnost a pružnost. Od začátku byl koncipován s ohledem na co nejlepší čitelnost a srozumitelnost zdrojových kódů v něm napsaných. Smalltalk je case-sensitive, bere tedy ohled na velikost jednotlivých písmen. Ač vychází částečně z Lispu, rozhodně nečekejte žádné závorkové peklo. Syntaxe Smalltalku sice vypadá na první pohled poněkud nezvykle, ovšem má svoji jasnou a striktně dodržovanou logiku.
Smalltalk zná vlastně jen tři operace. Je to zaslání zprávy objektu, specifikace objektu a vrácení objektu jako výsledku volání zprávy. Skoro všechno, co ve smalltalkovském kódu můžete vidět, je voláním zprávy nějakého objektu. Nejdříve určíme objekt, kterému máme zprávu zaslat, a poté identifikátor zprávy. Zápis pak vypadá takto: object message
Například pokud napíšeme 1234 factorial
, zašleme objektu reprezentujícímu číslo 1234 zprávu jménem factorial. Výsledkem pak bude objekt reprezentující faktoriál čísla 1234. Takovýmto zprávám říkáme unární zprávy, mají nejvyšší prioritu a vyhodnocují se zleva doprava. Pokud napíšeme 1234 factorial asString size
, dopracujeme se stejného výsledku, jako kdybychom napsali ((1234 factorial) asString) size
. Tento výraz nejdříve vypočítá faktoriál čísla 1234, potom výsledek převede na řetězec voláním zprávy (metody) asString a výslednému řetězci nakonec pošle zprávu size, která vrátí jeho délku. Celkovým výsledkem pak je objekt reprezentující číslo 3281, což je počet číslic faktoriálu čísla 1234.
Můžete se zeptat, kde se objekt čísla 1234 vlastně vzal. Odpověď nám dá letmý pohled do přeloženého bytekódu (jeho slovní reprezentace).
pushConstant: 1234 send: factorial pop
Interpret vezme konstantní výraz, který jste napsali do zdrojového kódu, a vytvoří podle něj objekt tento výraz reprezentující – v našem případě objekt třídy SmallInteger. Stejně postupuje i u ostatních konstantních výrazů, takže například ve výrazu 'Hello world' size
interpret vytvoří objekt třídy String reprezentující daný řetězec a pošle mu zprávu size na zjištění jeho délky. V bytekódu se pak objeví pushConstant: 'Hello world'
.
Čísla
Konstantní výrazy pro celá čísla zahrnují desítkové číslice ( 1234
), osmičkové číslice ( 8r177
), šestnáctkové číslice ( 16rFF
) a samozřejmě dvojkové číslice ( 2r01011
). Písmeno r znamená radix (základ použité číselné soustavy) a budiž poznamenáno, že nejste omezeni jen na tyto základní číselné soustavy. Můžete tak například uvádět čísla v trojkové soustavě ( 3r21
=7) či libovolné jiné ( 25472r21
=50945). Základ je vždy vyjádřen desítkově. Zapomenout nesmíme na celá čísla s exponentem ( 123e2
). Celá čísla jsou objekty třídy SmallInteger s rozsahem –230 až 230-1. Pokud již tento rozsah nestačí, dochází k automatické konverzi na instance tříd LargePositiveInteger a LargeNegativeInteger. Ty nemají žádné explicitní velikostní omezení, ale počítání s nimi je podstatně pomalejší.
Reálná čísla jsou instancemi třídy Float. Pro jejich konstantní výrazy platí podobná pravidla jako pro celá čísla ( 3.14
, 3.14e-10
, 2r1.1
). Za pozornost stojí, že operace dělení (např. 10 / 3
) nevrací reálné číslo, ale zlomek (třída Fraction). Odpadají tedy všemožné zaokrouhlovací chyby a počítání je naprosto přesné. Mimoto Squeak podporuje ještě počítání s nekonečnem.
Znaky, Řetězce a Symboly
S řetězci jsme se již setkali. Jsou ohraničeny apostrofy a a mohou být i víceřádkové (v takovém případě je znak konce řádku součástí řetězce, nepoužívají se speciální znaky typu \n). Pokud chceme, aby řetězec obsahoval apostrof, vložíme do něj dva apostrofy za sebou (''). Řetězce jsou instancemi třídy String, jsou velmi podobné polím a indexují se od jedničky.
Konstantní výrazy pro znaky začínají symbolem dolaru $, za nímž bez výjimky následuje požadovaný znak. $3
je tedy znak pro číslici 3, $a
je znak pro písmeno a, $$
je znak pro znak dolaru, $
je znak pro mezeru. Jsou to instance třídy Character. V případě mezery, konce řádku a podobných znaků se přirozeně doporučuje jiná méně matoucí reprezentace (např. Character space
, Character cr
apod.).
Symboly (třída Symbol) jsou speciálními druhy řetězců. Liší se v tom, že všechny symboly obsahující stejnou sekvenci znaků mají jednu společnou instanci (rovnost je u nich totožná s identitou). Symboly jsou řetězce uvozené znakem mřížky, např. #'toto je symbol'
. Mají široké spektrum použití. Využívají se mimo jiné pro identifikátory zpráv. Při dodržení omezené znakové sady je lze psát bez apostrof (např. #factorial
)
Sekvence výrazů a kaskáda
Jednotlivé výrazy v sekvenci výrazů jsou odděleny tečkou. Ta hraje stejnou úlohu jako středník v C/C++. Výsledek sekvence výrazů je roven výsledku posledního výrazu. Poslední tečka je volitelná. Pokud chceme poslat jednomu objektu více zpráv za sebou, můžeme tak učinit buď sekvencí výrazů object message1. object message2. object message3.
, nebo pomocí kaskády, kde jsou jednotlivé zprávy odděleny středníkem: object message1; message2; message3.
Často se na konci kaskády používá zpráva yourself, která vrátí volaný objekt object
.
Pole
Konstantní pole vytváří instance třídy Array. Jsou tvořena konstantami v závorkách za znakem mřížky. Například
#(1 2 3 4 5)
je pole velikosti 5 obsahující čísla 1 až 5.
#('this' #is $a #'constant' #array)
je pole velikosti pět obsahující řetězec, symbol, znak, symbol a symbol.
V konstantním poli může být obsaženo i další konstantní pole, např. #(1 2 #(3 4))
je pole velikosti tři. Protože konstantní pole obsahuje jen konstanty, je například #(1 + 2)
polem o velikosti tři obsahujícím dvě čísla a jeden symbol.
Výrazová pole se od konstantních polí liší v tom, že místo sekvence konstant obsahují sekvenci výrazů. Pole je pak tvořeno výsledky jednotlivých výrazů. Jsou ohraničena složenými závorkami, např. { 1. 2+3. 1234 factorial }
Proměnné
Lokální proměnné jsou pojmenovávány pomocí identifikátorů, což, stejně jako v jiných jazycích, jsou sekvence znaků a číslic začínajících písmenem. Identifikátory lokálních proměnných z konvence začínají malým písmenem, a pokud vyjadřují víceslovné spojení, každé další významové slovo začíná velkým písmenem (např. totoJeIdentifikator). Identifikátory nesmí obsahovat podtržítka. Seznam lokálních proměnných musí být vždy na začátku metody nebo bloku a je ohraničen znaky pro rouru
| var1 var2 var3 |
Pokud vám zde něco chybí, třeba určení typů, nehledejte to, nepovedlo by se vám to. Jak jsme si řekli, proměnné ve Smalltalku nelze deklarovat kdekoliv. Nicméně vzhledem k tomu, že se u nich nemusí uvádět typy, dokáže se o ně postarat editor kódu. Pokud narazí na neznámý identifikátor, nabídne vám nejpodobnější známé identifikátory (to pro případ, že jste se přepsali) včetně možnosti deklarace nové lokální proměnné. V takovém případě ji sám rád automaticky doplní do jejich seznamu. Pokud je naopak nějaká lokální proměnná nevyužívána, nabídne vám při rekompilaci metody její odstranění.
Globální proměnné začínají z konvence velkými písmeny. Jsou obsaženy v systémovém slovníku jménem Smalltalk a k jejich popisu se dostaneme později. Poznamenejme, že třídy jsou rovněž globální proměnné.
Mimoto existují ještě instanční proměnné, třídní proměnné a sdílené slovníky, ale ty si rovněž necháme až na některý z následujících dílů.
Pseudo-proměnné
Pseudo-proměnné jsou rezervované identifikátory podobné klíčovým slovům z jiných jazyků. Nelze je použít na levé straně přiřazení. Jsou to:
nil
- Obdoba NULL apod. Jedinečná instance třídy UndefinedObject. Neinicializované objekty mají implicitně tuto hodnotu.
true, false
- Jedinečné instance tříd True a False.
self
- Obdoba „this“ z C++. Jedná se o referenci na objekt, který je příjemcem vykonávané zprávy.
super
- Volání metody předka. Referencuje sice stejný objekt jako self, ovšem v kontextu předka jeho třídy.
thisContext
- Objekt popisující aktivní kontext vykonávané zprávy nebo bloku. Obsahuje např. zásobník volání a jiné informace používané především při ladění.
Komentáře
Komentáře jsou vkládány do uvozovek a mohou být víceřádkové. Např. "toto je komentář"
. Uvozovky lze do komentáře vložit jejich zdvojením. Smalltalk bohužel nemá jednořádkové komentáře, tedy nic podobného dvěma lomítkům z C++ a spol.
Zprávy
O unárních zprávách jsme se již zmínili. Nepřijímají žádné argumenty a jsou vyhodnocovány jako první.
Binární zprávy jsou identifikovány binárními identifikátory (+, -, , <= atd.). Jsou vyhodnocovány jako druhé a vždy zleva doprava bez ohledu na prioritu numerických operací. 2 + 3 * 4
je tedy 20. Objektu 2 se pošle binární zpráva #+ s jedním parametrem 3 a výsledkem je objekt čísla 5. Tomu je poslána binární zpráva # s parametrem 4 a výsledkem je číslo 20. Samozřejmě lze použít závorky: 2 + (3 * 4)
.
Nakonec jsou ve výrazech vyhodnoceny slovní zprávy. Přijímají jeden nebo více argumentů a tvoří je identifikátory ukončené dvojtečkami. Např. array at: 1 put: 'první prvek'
pošle zprávu #at:put: s dvěma parametry objektu array (lokální proměnná). 10 perform: #factorial
řekne objektu 10, že má vykonat unární zprávu #factorial (stejné jako 10 factorial
).
Vyhodnocování zpráv tedy probíhá v pořadí unární zprávy, binární zprávy, slovní zprávy. Výraz array at: 1 + 2 factorial put: 'první prvek'
je vyhodnocen jako array at: (1 + (2 factorial)) put: 'první prvek'
.
Přiřazení
Pro přiřazení se používají znaky := jako v Pascalu (= je použito pro rovnost, == pro identitu). Původně se používal znak šipky doleva (), který supluje podtržítko (_). S ním se lze stále setkat velmi často. Přiřazení je výraz, a proto mohou být jednotlivá přiřazení kaskádně uspořádána.
| var var2 |
var := 1234 factorial.
var2
var 50.
Návratové výrazy
Obdoba return z C/C++ a spol., ovšem místo slova return se použije znak ^. Pokud je návratový výraz uvnitř bloku, je ukončena celá obalující metoda, nikoliv pouze vyhodnocovaný blok. Pokud návratový výraz neuvedeme, vrací se implicitně self.
Bloky
Bloky jsou velmi významnou součástí jazyka Smalltalk. Jedná se o instance třídy BlockContext a vytvářejí se pomocí hranatých závorek [ ] ohraničujících sekvenci výrazů, přičemž výsledkem bloku je výsledek posledního výrazu. Například [ 1. 2. 3 ]
je blok, jehož vyhodnocením získáme hodnotu 3.
Bloky mohou přijímat parametry. Vyhodnocení bloku je provedeno voláním metody #value případně #value: pro vyhodnocení bloku s jedním parametrem (#value:value: pro dva parametry atd.). Pro počet parametrů větší než čtyři se používá metoda #valueWithArguments: zpracovávající pole parametrů. V bloku mohou být deklarovány lokální proměnné (za deklarací parametrů).
Parametry bloku jsou uváděny vždy na jeho začátku. Blok s parametry vypadá takhle: [ :param1 :param2 | param1 + param2 ]
. Použití tohoto bloku je pak následující (vrátí výsledek 7): [ :param1 :param2 | param1 + param2 ] value: 3 value: 4
.
Bloky jsou samozřejmě objekty, lze je proto použít na pravé straně přiřazení. Jsou to vlastně anonymní funkce.
| aBlock | aBlock := [ :param1 :param2 | param1 + param2 ]. ^ aBlock value: 3 value: 4.
Volání primitivních metod
K explicitnímu volání primitivních metod se běžný programátor dostane jen výjimečně. Zapisuje se následovně: <primitive: 41>
. Číslo za dvojtečkou je unikátní číslo primitivy. Pokud volání primitivy selže, může za ní být uveden náhradní kód. Ve speciálních případech se ještě používají <cdecl: >
, <apicall: >
či jiná podobná volání služeb VM, ale ta souvisí se speciálními pluginy virtuálního stroje pro platformně závislé operace.
A co dál?
Dál už nic. Ze syntaktického hlediska jsme možnosti Smalltalku vyčerpali. Na první pohled to tak určitě nevypadá, ale již máme k dispozici všechno, co potřebujeme k vytváření tříd, metod, řídících konstrukcí, vícevláknových aplikací, strukturovanému zpracování výjimek, typové kontrole, provázané dokumentaci, zkrátka ke všemu, co se od moderního programovacího jazyka vyžaduje, ale o tom až v dalších dílech.
Abych nevypadal, že si moc vymýšlím, dopředu vám ukáži, jak vypadá například obdoba C++ cyklu for (int i = 1; i <= 10; i++) { cout << i << endl; };
1 to: 10 do: [ :i | Transcript show: i; cr ].
objektu 1 je poslána zpráva #to:do: se dvěma argumenty, kde prvním argumentem je ukončovací hodnota cyklu a druhým argumentem je jednoparametrový blok. Objekt 1 tento blok desetkrát vyhodnotí s indexem jako parametrem bloku. Blok samotný si tento parametr přečte (na jeho pojmenování nezáleží) a použije ho jako argument zprávy #show: zaslané Transcriptu, což je jistá obdoba standardního výstupu. Ten dané číslo vypíše a na pokyn zprávy #cr odřádkuje.
Tento řádek můžete ve Squeaku takřka kamkoliv napsat, označit a dát pokyn k jeho provedení. To, že jsme nepotřebovali žádné #include, funkci main(), return, spuštění kompilátoru a linkeru, nemá cenu rozvádět a ani by to nebylo vůči kompilovaným jazykům korektní.