Hlavní navigace

Squeak: návrat do budoucnosti (4)

2. 3. 2004
Doba čtení: 7 minut

Sdílet

V dnešním dílu se podíváme na smalltalkovskou typovou kontrolu a ukážeme si, jak se tvoří základní řídící struktury.

Objekty ve Smalltalku jsou uzavřené entity, které samy rozhodují o tom, jak se budou na své okolí tvářit. Je jen na nich, jestli se svému okolí budou jevit jako číslo, řetězec, či cokoliv jiného. V tomto duchu je vytvořena i statická typová kontrola Smalltalku. Žádná není. Pro programátora zvyklého na konvenční programovací jazyky a především na Adu to může působit stejně drtivě jako vypití několika skleniček pangalaktického megacloumáku, ovšem i se stejnými povznášejícími účinky.

Objekty jsou pouze referencovány. V proměnné nikdy nemůže být obsažen objekt jako takový (jeho hodnota), přičemž platí, že reference s sebou nenese žádnou informaci o typu. Důsledkem toho jsou všechny parametry metod a bloků předávány odkazem a nespecifikuje se u nich typ. Totéž se týká i lokálních, instančních a třídních proměnných. Parametrem metody tak může být reference na libovolný objekt, tedy třeba i na třídu nebo blok.

Objekty mezi sebou komunikují pouze zasíláním zpráv a existují jen dvě možnosti, jak se objekt ke zprávě zachová. Buď jí rozumí, nebo ne. Pokud mu zašlete například zprávu asString, abyste získali jeho textovou reprezentaci, objekt si zjistí, zda má metodu asStringimple­mentovánu, a v případě, že ne, dá to náležitě na vědomí – nejčastěji vyvoláním výjimky MessageNotUnder­stood. Nicméně si tuto informaci může klidně nechat pro sebe a tvářit se, že všechno ví a všechno zná.

Objekty naprosto odlišných tříd, které mají různé předky a dělají zcela odlišné věci, se tak mohou tvářit jako objekty stejné třídy, protože příslušnost k třídě s typovou kontrolou primárně nesouvisí. Pokud reagují na stejné zprávy, je jejich skutečná příslušnost ke třídě nepodstatná.

Pracujeme-li s nějakým objektem, chceme mít přirozeně alespoň trochu povědomí, s kým máme tu čest. Možností máme hned několik. Pokud objektu pošleme zprávu class, vrátí referenci na svou třídu. Pomocí metody isKindOf: zjistíme příslušnost daného objektu k určité třídě či k některému z potomků. Každý objekt je instancí právě jedné třídy. Tato vazba platí pro celou dobu existence objektu a nelze ji změnit (přesněji řečeno lze, ale pouze použitím primitivních metod a při zachování celé řady omezení)

Rychlejší a obecnější variantou typové kontroly je definovat v hierarchii tříd zprávy, které typ objektů identifikují. Z konvence vypadají jako isString, isBoolean či isNumber a vracejí true nebo false. Například metoda isString implementovaná ve třídě Object vrací hodnotu false, ale její přetížená verze ve tříděString, která je potomkem třídy Object, vrací true. Tím je zajištěno, že všichni potomci třídy String na otázku isString odpoví kladně. Tento přístup umožňuje v rámci hierarchie tříd odpovědi na tyto zprávy měnit. Velkou nevýhodou však je, že metody typu isSomething musí být definovány na co nejobecnější úrovní, nejlépe přímo ve třídě Object, která je základní třídou hierarchie tříd. Squeak má v současnosti cca 1800 tříd a raději ani nedomýšlet, kam by to vedlo. Proto se tyto metody ve třídě Object definují pouze pro nejdůležitější a nejčastěji používané třídy.

Skutečná třída objektu nás však většinou příliš nezajímá. Důležité je pouze to, zda rozumí zasílané zprávě. To lze bezpečně bez rizika vyvolání výjimky zjistit pomocí zprávy respondsTo: vracející true nebo false.

Tabulka č. 543
Výraz: Výsledek:
42 asString '42'
42 class SmallInteger
42 class asString 'SmallInteger'
42 isKindOf: Number true
42 isString false
42 respondsTo: #factorial true

Pro zopakování připomeňme, že #factorial je symbol (speciální případ řetězce) a v tomto případě se jedná o selektor zprávy. Globální proměnné začínají z konvence velkým písmenem. SmallInteger je globální proměnná, jež slouží jako reference na třídu SmallInteger. Number je reference na třídu Number.

Dynamická typová kontrola v mnoha směrech zjednodušuje návrh programů, zlepšuje jejich rozšiřitelnost a znovupoužitelnost, zkracuje jejich zápis a zvyšuje čitelnost kódu. Co si pod tím konkrétně představit, si ukážeme později v souvislosti s metodami.

Na druhou stranu si vynucuje větší pozornost pro fázi testování, ovšem v menší míře, než by se na první pohled zdálo.

Základní řídící struktury

Řídící struktury ve Smalltalku nehrají nijak výjimečnou roli. Jsou implementovány pomocí standardních vyjadřovacích prostředků jazyka. Přesto se jejich zápis příliš neliší od toho, s čím se můžete setkat v konvenčních jazycích.

Nejdůležitější řídící strukturou je bezpochyby alternativa (if). Je vyřešena jako slovní zpráva ifTrue: ve třídě Boolean. Přijímá jako argument bezparametrový blok vykonaný v případě pravdivosti boolovského výrazu.

Tabulka č. 544
Smalltalk: 1 = 1 ifTrue: [ Transcript show: 'pravda' ]
C++: if (1 == 1) { cout << "pravda"; };

Pokud již najdete odvahu některé výrazy zkoušet, okno Transcriptu otevřete kliknutím na pracovní plochu – open – transcript. V podmenu open naleznete také tzv. workspace, kam je nejvhodnější zkoušené výrazy napsat, označit, vyvolat nad nimi kontextové menu a zvolit do it pro provedení výrazu nebo print it pro provedení a vypsání výsledku. K práci se Squeakem se ale dostaneme později.

Opakem ifTrue: je zpráva ifFalse:

Tabulka č. 545
Smalltalk: 1 = 2 ifFalse: [ Transcript show: 'nepravda' ]
C++: if (1 != 2) { cout << "nepravda"; };

Dále je k dispozici zpráva ifTrue:ifFalse: umožňující zadat i alternativní větev (else):

1 = 1 ifTrue: [ Transcript show: 'pravda' ]
      ifFalse: [ Transcript show: 'nepravda' ].

Jedná se o jednu zprávu, není to tedy kombinace zpráv ifTrue: a ifFalse:. Jejím opakem je metoda ifFalse:ifTrue:.

Pro zvídavější, v pozadí zpracování těchto zpráv nestojí žádné volání primitiv. Výsledkem vyhodnocení výrazu 1 = 1 je reference na objekt true, což je jedinečná instance třídy True (jejím předkem je třída Boolean). Třída True implementuje zprávu ifTrue: tak, že vyhodnotí blok, který obdrží jako parametr. Oproti tomu její implementace zprávy ifFalse: vrátí pouze nil. TřídaFalse pak pracuje přesně obráceně, jak se můžete sami časem přesvědčit nahlédnutím do zdrojových kódů.

V praxi jsou tyto konstrukce optimalizovány již na úrovni bytekódu pomocí skoků. To se týká i některých dalších často užívaných obratů.

Nezřídka je třeba použít porovnání s hodnotou nil, tedy zjistit, zda byl objekt již definován.

1 = nil ifFalse: [ Transcript show: 'not nil' ].

Tento zápis lze zčitelnit pomocí volání metod isNil nebo notNil.

1 isNil ifFalse: [ Transcript show: 'not nil' ].

Ještě výhodnější je použítí metod ifNil:, ifNotNil:,ifNil:if­NotNil: a ifNotNil:ifNil:.

1 ifNotNil: [ Transcript show: 'not nil' ].

Díky tomu, že bloky lze parametrizovat a libovolně vyhodnocovat, hrají především při implementaci cyklů důležitou úlohu. Například cyklus while pracuje jako zpráva zaslaná bloku.

| var |
var := 10.
[ var := var - 1. var > 0 ] whileTrue.

Zpráva whileTrue řekne bloku, aby se vyhodnocoval tak dlouho, dokud jeho výsledek nebude pravdivý. Výsledkem bloku je vždy výsledek posledního výrazu (v našem případě var > 0). Vyhodnocení bloku proběhne vždy nejméně jednou.

Metoda whileTrue má ještě jednu variantu přijímající jako parametr blok, který je vyhodnocován spolu s volaným blokem. Ten nemusí být vyhodnocen ani jednou.

| var |
var := 10.
[ var > 0 ] whileTrue: [ var := var - 1 ].

Samozřejmě existují ještě varianty s negativní logikou, tedy whileFalse a whileFalse:.

Další příklad cyklu jsme si ukázali již minule. Byl to cyklus for řešený pomocí metody to:do: třídy Number vyhodnocující jednoparametrový blok.

1 to: 10 do: [ :i | Transcript show: i ].

Má ještě variantu to:by:do:, která definuje krok růstu indexu.

1 to: 10 by: 2 do: [ :i | Transcript show: i ].

Pokud potřebujete klesající index, použijete krok roven –1. Pokud by vás určování záporného indexu příliš otravovalo, nikdo vám nemůže zabránit vytvořit si ve třídě Number vlastní metodu pojmenovanou např.downTo:do: úpravou standardní metody to:do:.

Nejjednodušší cykly, kdy nás index nezajímá a máme daný počet opakování, se řeší pomocí metody timesRepeat: třídy Number.

10 timesRepeat: [ Transcript show: 'a' ].

Z běžných řídících struktur nám už zbývá pouze výběr z více alternativ (switch, case), který obstarávají metody caseOf: a caseOf:otherwise: třídy Object. Přijímají jako parametr kolekci asociací bloků, což nejenže brutálně zní, ono se to i dosti brutálně zapisuje. Dokonce více, než je zdevastovaná mysl běžného smalltalkera schopna snést, takže se tato konstrukce moc nepoužívá (v bezmála půl milionu řádků zdrojových kódů Squeaku se vyskytuje asi sedmkrát). Oproti jiným možným vyjádřením má však využití metody caseOf:otherwise: jednu podstatnou výhodu – je optimalizováno na úrovni bytekódu, takže je zdaleka nejrychlejší.

root_podpora

| symbol |
symbol := #a.
symbol caseOf: {
   [#a] -> [1+1].
   ['b' asSymbol] -> [2+2].
   [#c] -> [3+3]
} otherwise: [4+4].

Jak si z minulého dílu asi nepamatujete, konstrukce { výraz. výraz. výraz } vytvoří pole obsahující výsledky jednotlivých výrazů. Binární selektor → vytvoří asociaci, tedy dvojici klíč/hodnota (třída Association). Klíč vznikne vyhodnocením bloku na levé straně asociace, pravá strana obsahuje blok vyhodnocený v případě shody klíče s příjemcem zprávy caseOf:otherwise:. Pokud není použita sekce otherwise a žádná shoda objektu s klíčem není nalezena, je vyvolána výjimka.

Odkazy

www.squeak.org
www.comtalk.net
VisualWorks – nekomerční verze

Byl pro vás článek přínosný?