Hlavní navigace

Vytváření vlastních řídicích struktur v jazyce Smalltalk

 Autor: Unsplash.com
Před téměř 40 lety byl světu představen Smalltalk-80, produkt deseti let vývoje ve firmě Xerox. V článku představíme jednu z jeho zajímavých vlastností – možnost vytvářet vlastní řídicí struktury.
Lukáš Petrlík 4. 8. 2020
Doba čtení: 6 minut

Sdílet

Zatímco ve většině běžných programovacích jazyků je řízení běhu programu – větvení a cykly – součástí syntaxe programovacího jazyka, ve Smalltalku (a odvozených jazycích jako je Self a Newspeak) bylo řízení běhu programu přesunuto do standardní knihovny.

To má dva zajímavé důsledky. Jednak má Smalltalk velmi jednoduchou syntaxi, se kterou se zájemce může seznámit během několika minut. Druhým důsledkem je možnost vytvářet nové řídicí struktury obdobným způsobem, jaký použili autoři standardní knihovny.

V dalším textu si nejprve vysvětlíme základní syntaxi Smalltalku a ukážeme si, jak je ve standardní knihovně Smalltalku řešeno větvení a cykly. Pak se podíváme na jednu možnost implementace vlastní řídicí struktury.

Trocha Smalltalku do začátku

Abychom mohli ilustrovat popis také pomocí příkladů skutečného kódu, uveďme si nejprve, jak vypadá základní syntaxe Smalltalku pro volání metod a pro bloky.

Volání metod ve Smalltalku

Metodu objektu zavoláme ve Smalltalku takto:

objekt metoda 

Volání má přesně stejný význam jako volání

objekt.metoda(); 

objektových programovacích jazycích hlavního proudu.

Smalltalk je čistě objektový jazyk, proto i boolovské literály jsou objekty a jejich metody můžeme volat stejným způsobem:

false not.  "vrací true"
true not.   "vrací false" 

Text v uvozovkách je smalltalkový komentář, tečka odděluje příkazy.

Pokud má metoda jeden argument, bude její volání vypadat takto:

false xor: true.  "výsledek je true" 

V příkladu jsme metodu xor: objektu false zavolali s argumentem  true.

Syntaxe pro volání metody s více argumenty je trochu podobná pojmenovaným argumentům v Pythonu nebo Dartu – selektor metody má více částí (klíčových slov) a argumenty se zadávají mezi nimi. Například metodu between:and: objektu 7 můžeme zavolat takto:

7 between: 6 and: 8 “argumenty jsou 6 a 8, vrací true” 

Metoda vrátí hodnotu true, protože 7 leží v intervalu <6; 8>.

Definice metod ve Smalltalku

Smalltalk pracuje se zdrojovými texty dost odlišně od programovacích jazyků hlavního proudu. Programátoři píší kód v interním IDE Smalltalku, kterým může být třeba základní prohlížeč System Browser nebo debugger. Nově vytvořené nebo upravené metody systém ihned přeloží a zdrojovou definici uloží na disk v interním textovém formátu.

Metodu not třídy True bude System Browser zobrazovat takto – tělo metody je zobrazeno ve spodním panelu:


System Browser v dialektu Smalltalku Pharo

V ukázkách kódu zapíšeme tutéž metodu spolu se jménem třídy, ve které je metoda definována:

True>>not
    "Metoda not třídy True vrací vždy false."
    ^ false 

Znak ^ označuje návrat z metody s návratovou hodnotou, je to tedy ekvivalent příkazu  return.

Ve třídě False je metoda not definována obdobně:

False>>not
    "Metoda not třídy False vždy vrací true"
    ^ true 

Bloky ve Smalltalku

Blok kódu (anonymní funkce) se ve Smalltalku zapisuje v hranatých závorkách:

[ příkazy ] 

Blok je také objekt, a jeho tělo se vykoná voláním metody value, tedy volání

[ příkazy ] value 

vykoná příkazy bloku a vrátí hodnotu posledního vykonaného příkazu.

Větvení

Ve Smalltalku má třída Boolean dva potomky, singletony True a False. Na instanci třídy True se můžeme odkázat literálem true (s malým písmenem na začátku), na instanci třídy False literálem  false.

Pro větvení programu implementují třídy True a False několik metod, z nichž jako první si ukážeme metodu  ifTrue:.

true ifTrue: [ 'tělo bloku se vykoná' ].
false ifTrue: [ 1 / 0 ] "dělení nulou nenastane". 

Metoda ifTrue: třídy True vykoná blok předaný jako argument, metoda ifTrue: třídy False blok nevykoná.

Asi je zřejmé, jak budou obě implementace metody ifTrue: vypadat – implementace ve třídě True vždy vykoná tělo bloku předaného jako argument, zatímco ve třídě False nikdy tělo bloku nevykoná.

True>>ifTrue: alternativeBlock
    ^alternativeBlock value

False>>ifTrue: alternativeBlock
    ^nil 

Ve Smalltalku je tedy (alespoň na syntaktické úrovni) větvení implementováno pomocí polymorfismu.

Ve standardní knihovně Smalltalku jsou implementovány i další varianty větvení, např.

podmínka ifFalse: [ blok ]
podmínka ifTrue: [ trueBlok ] ifFalse: [ falseBlok ] 

Smyčky

V knihovně najdeme také dvě varianty jednoduchých smyček:

[ podmínka ] whileTrue: [ těloSmyčky ]
[ podmínka ] whileFalse: [ těloSmyčky ] 

Na rozdíl od větvení jsou metody whileTrue: a whileFalse: metodami bloku. Ve smyčce je totiž nutné si podmínku zapamatovat a vyhodnotit jí opakovaně, k čemuž se blok ideálně hodí. Tělo smyčky je pak metodám whileTrue: a whileFalse: předáváno jako argument.

Pokud budeme zvědaví na implementaci metody whileTrue:, System Browser nám zobrazí následující rekurzivní definici1:

BlockClosure>>whileTrue: aBlock

    (self value) ifTrue: [
        aBlock value.
        self whileTrue: aBlock
    ].
    ^ nil 

Zde si tvůrci Smalltalku z výkonnostních důvodů vypomohli nečistým a nesportovním trikem: pokud je podmínkový blok literálem, je překládán přímo překladačem a metoda whileTrue: se nevolá, k žádné rekurzi tedy nedochází. Skutečná metoda se zavolá pouze pro bloky, které nejsou literály.

Konstrukce vlastního řízení běhu – metoda case:do:

Nyní již máme dostatečnou představu o možnostech Smalltalku, abychom mohli implementovat vlastní konstrukci pro řízení běhu, odpovídající příkazu switch/ case známého z běžně používaných programovacích jazyků. Smalltalk ani jeho knihovna takovou konstrukci neobsahuje, protože v něm není zapotřebí – místo switch/ case se v běžném programování používá např. dědičnost nebo slovník ( Dictionary) s vykonatelnými bloky (viz např. metoda Object>>caseOf: popsaná ve čtvrtém díle seriálu o Squeaku na root.cz.

Pro implementaci naší konstrukce zavedeme novou třídu s názvem Case, jejíž factory metoda on: naplní dvě instanční proměnné:

  • proměnnou hodnota naplní hodnotou předanou jako argument factory metodě on:
  • do proměnné provedeno přiřadí počáteční hodnotu  false.

Dále vytvoříme instanční metodu case:do:, která podmíněně vykoná blok. Tuto metodu zapíšeme z didaktických důvodů takto:

Case>>case: pripad do: blok
    (provedeno not) & (pripad = hodnota)
        ifTrue: [
            provedeno := true.
            blok value
        ] 

Opravdový Smalltalkista by metodu zapsal trochu jinak, ale její význam je snad takto pochopitelnější – pokud žádný case ještě nebyl proveden a parametr pripad odpovídá zapamatované hodnotě, označíme case jako provedený přiřazením hodnoty true a vykonáme předaný blok.

Jak by vypadalo použití této konstrukce?

Abychom v kódu nemuseli vícekrát opakovat objekt, jehož metodu voláme, použijeme tzv. kaskádování. Ve Smalltalku se pro kaskádování používá syntaxe

objekt
      metoda1;
      metoda2 

která je ekvivalentní volání

objekt metoda1.
objekt metoda2. 

Znakem středník oddělujeme jednotlivá volání metod stejného objektu.

Základní použití naší třídy Case by mohlo s využitím kaskádování vypadat asi takto:

(Case on: 2)
    case: 1 do: [ “jedna” ];
    case: 2 do: [ “dva” ]. 

Pomocí factory metody on: vytvoříme krátkodobou instanci třídy Case, nad kterou pak budeme pro jednotlivé případy postupně volat její metodu case:do:. Metoda zajistí, že se vykoná nejvýše jeden blok.

Samozřejmě jsou možná další vylepšení, abychom si připadali skoro jako v Céčku nebo v Javě, ta ale už nejsou předmětem tohoto článku.

Závěr

V článku jsme si ukázali, jakým způsobem jsou v knihovně jazyka Smalltalk implementovány větvení a základní cykly, a pokusili jsme se podobným způsobem implementovat ekvivalent příkazu switch/ case.

Z výkonnostních důvodů samozřejmě provádí překladač optimalizace jemu známých řídicích konstrukcí, takže skutečná implementace je poněkud složitější. Přesto si myslím, že je to mechanismus zajímavý a možná snad inspirativní pro případné tvůrce nových programovacích jazyků.

Na závěr bych chtěl poděkovat svému kamarádovi a dlouholetému smalltalkistovi Standovi Bendovi za přečtení článku.

KL20-tip-hlasovani

Poznámka

Pro zjednodušení výkladu jsem vybral definici metody whileTrue: ze smalltalkového dialektu Pharo. Většina ostatních Smalltalků obsahuje přímočařejší, zato trochu méně srozumitelnou definici této metody:

BlockClosure>>whileTrue: aBlock
    [self value] whileTrue: [aBlock value] 

V komentáři metody najdeme vysvětlení, že selektor whileTrue: v těle metody je přímo přeložen kompilátorem.