Hlavní navigace

Clojure aneb jazyk umožňující tvorbu bezpečných vícevláknových aplikací pro JVM (2.část)

19. 6. 2012
Doba čtení: 22 minut

Sdílet

V dnešní části seriálu o programovacím jazyku Java i o virtuálním stroji Javy budeme pokračovat v popisu programovacího jazyka Clojure. Seznámíme se především s tím, jakým způsobem se v Clojure pracuje se složenými formami a taktéž s formami speciálními, protože právě to představuje ústřední součást tohoto programovacího jazyka.

Obsah

1. Clojure aneb jazyk umožňující tvorbu bezpečných vícevláknových aplikací pro JVM (2.část)

2. Symboly a „keywords“

3. Složené formy aneb sémantika zápisu kolekcí

4. Příklad konstrukce kolekcí v jazyku Clojure

5. Vlastnosti společné všem typům kolekcí

6. Seznam jako forma zápisu programů aneb kód==data

7. Demonstrační příklady: aritmetické funkce (prefixový zápis operátorů)

8. Speciální formy

9. Odkazy na Internetu

1. Clojure aneb jazyk umožňující tvorbu bezpečných vícevláknových aplikací pro JVM (2.část)

V předchozí části tohoto seriálu jsme si řekli základní informace o programovacím jazyku Clojure i o způsobu jeho použití v rámci aplikací běžících nad virtuálním strojem Javy – JVM (Clojure ovšem již dnes existuje i ve variantě určené pro CLI a dokonce i ve variantě překládané do JavaScriptu, který je některými autory považovaný za „assembler pro web“). Připomeňme si, že se v případě Clojure jedná o programovací jazyk, jehož kořeny můžeme hledat v LISPu a obecně ve funkcionálních programovacích jazycích. Programy napsané v Clojure jsou automaticky překládány do bajtkódu JVM a posléze jsou vykonávány naprosto stejným způsobem, jako bajtkód získaný překladem programu napsaného v Javě, Scale atd. Díky tomu nemusí být v Clojure implementován ani optimalizující překladač (překlad do nativního kódu je záležitostí JITu) ani vyhrazený správce paměti, protože všechna data, s nimiž Clojure manipuluje, jsou ukládána na haldu (heap) spravovanou některým ze správců paměti (GC), které jsou dostupné v JVM.

Díky tomu, že všechny objekty jsou uloženy na haldě, je taktéž možné relativně snadno oboustranně komunikovat mezi kódem napsaným v Javě (a používajícím tedy například třídy implementující rozhraní Collection či Map) a kódem napsaným v Clojure, kde se dosti intenzivně využívají seznamy, vektory, mapy a množiny. Minule jsme si taktéž řekli, jakým způsobem je možné spustit smyčku REPL programovacího jazyka Clojure. REPL budeme používat i ve všech dnes uvedených demonstračních příkladech. Smyčka REPL očekává, že uživatel bude na standardní vstup zapisovat takzvané formy (form) (v jiných jazycích se většinou používá termín „výraz“), přičemž každá forma je nejdříve zkontrolována na korektní zápis, potom je vyhodnocena a následně je na standardní výstup zapsána hodnota, na kterou se forma vyhodnotila. Tato hodnota získaná vyhodnocením formy může být jakéhokoli podporovaného typu, může se tedy jednat jak o číslo, tak i o seznam či klidně i o funkci (což není v Clojure tak neobvyklé, jak by se možná mohlo zdát).

V programovacím jazyku Clojure existují čtyři základní typy forem:

  1. literály (numerické hodnoty, pravdivostní hodnoty, znaky, řetězce; vyhodnocují se samy na sebe)
  2. symboly a takzvaná „klíčová hesla“ (keywords)
  3. složené formy (seznamy, vektory, množiny a mapy, seznamy mají navíc zvláštní postavení kvůli způsobu svého vyhodnocení)
  4. speciální formy (podobají se složeným formám – seznamům, ale interně se vyhodnocují odlišným způsobem)

2. Symboly a „keywords“

Nejjednodušší typ formy – literály – jsme si již popsali minule, takže se pojďme věnovat dalším třem typům forem. Druhým typem formy podporovaným programovacím jazykem Clojure jsou takzvané symboly známé už z LISPu, ke kterým se v Clojure ještě přidávají keywords. Začněme s popisem „keywords“. Překlad tohoto slova je poněkud problematický kvůli jeho dvojímu významu (alespoň v programování), takže se pokusím používat sousloví „klíčová hesla“, protože termín „keywords“ v Clojure neznamená, že by se jednalo o rezervovaná klíčová slova jazyka. Klíčová hesla jsou na použití jednodušší než symboly, protože se ve smyčce REPL vyhodnocují samy na sebe a nemůže jim být přiřazena žádná hodnota. Na co se tedy vlastně v praxi tento typ formy hodí? Jedním z důvodů zavedení tohoto typu formy do programovacího jazyka Clojure byla podpora pro datového typu (kolekce) mapa, v němž je možné uchovávat dvojice klíč:hodnota. A jako klíč jsou s výhodou používána právě klíčová hesla, protože jejich hodnotu nelze měnit a navíc se jejich hešovací hodnota může vypočítat pouze jedenkrát. Smyčka REPL pozná, že uživatel používá klíčové heslo, z toho, že je těsně před ním napsána dvojtečka. Jak již bylo řečeno výše, vyhodnotí se heslo na sebe samu:

user=> :x
:x
user=> :jine_heslo
:jine_heslo
user=> :dalsi-heslo
:dalsi-heslo
user=> :jeste.jine.heslo
:jeste.jine.heslo

Ve skutečnosti je dvojtečka uváděná před klíčovým heslem jen syntaktickým cukrem, protože „plný“ zápis (v tomto případě speciální) formy představující klíčové heslo vypadá takto:

user=> (keyword "jine_heslo")
:jine_heslo

Podobným typem formy jsou symboly, před jejichž jménem se používá ampersand. Symbolům může být přiřazena hodnota a z tohoto důvodu se používají pro pojmenování funkcí, proměnných či jmenných prostorů. Se symboly se ještě v tomto článku několikrát setkáme, nyní si tedy jen ukažme, jak se symboly zapisují a jak je smyčka REPL vyhodnotí:

user=> 'toto-je-symbol
toto-je-symbol

(povšimněte si, že po vyhodnocení v REPL „zmizí“ apostrof původně uvedený před jménem symbolu)

Podobně jako dvojtečka byla pouze syntaktickým cukrem pro speciální formu (keyword „xxx“), je i ampersand zkrácenou podobou speciální formy (quote symbol). Předchozí příklad by tedy šel zapsat i následujícím způsobem:

user=> (quote toto-je-symbol)
toto-je-symbol

To, že se v obou případech jedná skutečně o stejný symbol, lze zjistit s využitím funkce ekvivalence (zde již trošku předbíháme, takže se prosím k příkladu vraťte po dočtení celého článku):

user=>(= 'toto-je-symbol (quote toto-je-symbol))
true

Zatím je to poměrně nuda, že? Ale už se pomalu dostáváme k další dvojici forem – složeným formám a speciálním formám – jejichž vyhodnocování představuje srdce programovacího jazyka Clojure.

3. Složené formy aneb sémantika zápisu kolekcí

V této kapitole si popíšeme takzvané složené formy, protože právě tyto formy představují, společně s formami speciálními, jeden z nejdůležitějších prvků jazyka Clojure (a podobně i původního LISPu). V tradičním LISPu je složenou formou především seznam (list), což je ovšem jen zjednodušeně zapsaný řetězec takzvaných tečka-dvojic. Koncept tečka-dvojic byl v Clojure opuštěn (samotný znak tečky zde dostal jiný význam – volání metod Javy), ovšem k seznamům navíc přibyly i další způsoby zápisu složených forem, které se používají (opět v podstatě jako syntaktický cukr) pro zápis následujících datových struktur: vektoru (vector), množiny (set) a mapy (map). Všechny čtyři typy složených forem (budeme je dále nazývat kolekce), ať již se jedná o seznam, vektor, množinu či mapu, jsou z obou stran uvozeny závorkami, přičemž musí být zachována párovost závorek (ke každé otevírací závorce přísluší jedna závorka uzavírací), která je kontrolována před vyhodnocením složené formy ve smyčce REPL.

V předchozím textu bylo napsáno, že základním typem složené formy je seznam (list), jehož prvky se již tradičně (více než padesát let!) zapisují do kulatých závorek. Pro zápis vektorů (vector) se používají hranaté závorky, mapy (map) využívají závorky složené a množiny (set) taktéž závorky složené, ovšem před otevírací závorkou se musí napsat křížek (hash, #). V následující tabulce jsou vypsány všechny čtyři typy složených forem. Kromě seznamů lze ostatní tři složené formy (vektory, mapy, množiny) vytvořit i pomocí vhodného konstruktoru (třetí sloupec), ovšem přesný význam tohoto zápisu si uvedeme až v následujících kapitolách:

Typ kolekce Zápis (syntaktický cukr) Konstruktor
Seznam (prvky) (prvky)
Vektor [prvky] (vector prvky)
Mapa {dvojice klíč-hodnota} (hash-map dvojice klíč-hodnota)
Množina #{unikátní prvky} (hash-set unikátní prvky)

4. Příklad konstrukce kolekcí v jazyku Clojure

V tabulce zobrazené v předchozí kapitole byl obsah všech čtyř typů kolekcí popsán jen velmi vágně slovem „prvky“, proto asi čtenáře bude zajímat, jaké prvky v nich mohou být uloženy. Může se jednat jak o literály (popsané v předchozí části tohoto seriálu), symboly, klíčová hesla, tak i – což je nejzajímavější – o jiné kolekce. Není tedy vůbec neobvyklé pracovat například se seznamy uloženými v jiných seznamech, s mapami (v podstatě se strukturami či záznamy), v nichž jsou uloženy vektory atd. Vzhledem k tomu, že Clojure je dynamicky typovaným programovacím jazykem, lze v jakémkoli okamžiku získat typ každého prvku uloženého v kolekci. Jak se to ve skutečnosti provádí si vysvětlíme v části věnované predikátům. Podívejme se nyní na několik demonstračních příkladů, kde u každého příkladu bude vysvětleno, co se vlastně provádí a z jakého důvodu Clojure některý typ formy nedokáže zpracovat:

Seznamy musí být uvozeny ampersandem, tj. syntaktickým cukrem pro speciální formu quote. Důvod si vysvětlíme až za chvíli:

user=> '(1 2 3 4)
(1 2 3 4)

Prázdný seznam:

user=> '()
()

Seznam obsahující další seznamy (zde jde vlastně o nevyvážený binární strom):

user=> '(1 (2 (3 (4 5))))
(1 (2 (3 (4 5))))

Vektor můžeme považovat za pole konstantních prvků:

user=> [1 2 3 4]
[1 2 3 4]

Vektor vektorů:

user=> [ [1 2 3] [4 5 6] [7 8 9]]
[[1 2 3] [4 5 6] [7 8 9]]

Vektor seznamů:

user=> [ '(:jedna :dva) '(:tri :ctyri) '(:pet :sest) ]
[(:jedna :dva) (:tri :ctyri) (:pet :sest)]

Typická mapa:

user=> {:prvni 1, :druhy 2}
{:druhy 2, :prvni 1}

Ve skutečnosti se čárky za dvojicemi klíč-hodnota nemusí zapisovat:

user=> {:prvni 1 :druhy 2}
{:druhy 2, :prvni 1}

Zápis mapy může být někdy i poměrně „divoký“:

user=> {:seznam '(:toto :je :seznam), :vektor [:toto :je :vektor] }
{:seznam (:toto :je :seznam), :vektor [:toto :je :vektor]}

Zbývají nám již jen množiny:

user=> #{"toto" "je" "mnozina"}
#{"mnozina" "toto" "je"}

(povšimněte si toho, že prvky v množině nejsou vypsány ve stejném pořadí, jak do ní byly vloženy, interně se totiž používá hešovací tabulka).

Prvky v množině nesmí být duplikátní!

Kontrolují se samozřejmě shodné hodnoty literálů:

user=> #{1 1 3}
IllegalArgumentException Duplicate key: 1  clojure.lang.PersistentHashSet.create
WithCheck (PersistentHashSet.java:68)

Řetězce jsou, jak již víme, taktéž literály (zde je duplikován řetězec „nesmi“):

user=> #{"nesmi" "mit" "dva" "stejne" "prvky" "skutecne" "nesmi"}
IllegalArgumentException Duplicate key: nesmi  clojure.lang.PersistentHashSet.createWithCheck (PersistentHashSet.java:68)

Clojure poctivě zkontroluje i ekvivalenci seznamů atd.:

user=> #{ '(:stejny :seznam) '(:stejny :seznam) }
IllegalArgumentException Duplicate key: (quote (:stejny :seznam))  clojure.lang.
PersistentHashSet.createWithCheck (PersistentHashSet.java:68)

5. Vlastnosti společné všem typům kolekcí

Všechny čtyři typy kolekcí popsaných v předchozích dvou kapitolách, mají několik společných vlastností, které – společně s technikami popsanými v následujícím textu – umožňují, aby se v programovacím jazyku Clojure mohly poměrně snadno vytvářet vícevláknové programy. Základní vlastností společnou všem čtyřem typům kolekcí je jejich neměnitelnost (immutability). To znamená, že již ve chvíli, kdy je kolekce vytvořena, je po celou další dobu její existence v běžícím programu určen její obsah, tj. hodnoty všech prvků kolekce. Na první pohled to sice možná může vypadat zvláštně, ale i s takto se chovajícími kolekcemi je možné v reálným programech pracovat a to dokonce velmi efektivním způsobem. Ostatně i ve standardní knihovně programovacího jazyka Java existují některé třídy, jejichž instance jsou neměnné. Typickým a všeobecně známým příkladem jsou řetězce představované třídou String, barvy představované třídou Color či instance tříd BigInteger (celé celé číslo se znaménkem o libovolném rozsahu) a BigDecimal (racionální číslo o libovolné přesnosti).

Kromě neměnitelnosti (immutability) je další společnou vlastností všech čtyř typů kolekcí jejich persistence. Většina standardních funkcí poskytovaná programovacím jazykem Clojure se totiž snaží o to, aby jednou vytvořené sekvence (dejme tomu seznam) byly znovupoužity i v případě, že je vytvořen nový seznam, který v sobě obsahuje i seznam starší (ten stále existuje a mohou na něj existovat reference používané například i v jiných paralelně běžících vláknech). Vzhledem k tomu, že se obsah starého seznamu nemůže změnit (seznam je neměnitelný), může například funkce cons známá již z LISPu jednoduše k seznamu přidat nový první prvek (head) s tím, že tento prvek ukazuje na původní seznam – jinými slovy není nutné, alespoň v tomto případě, vytvářet kopii (ať již plytkou či hlubokou) původního seznamu, což přispívá k tomu, že mnohé operace nad kolekcemi jsou ve skutečnosti velmi rychlé, i když by se podle jejich popisu mohlo zdát, že jejich implementace vyžaduje provedení časově složitých operací.

Podobně funkce pop, kterou skalní LISPaři pravděpodobně znají pod názvem cdr, vrátí „nový“ seznam vytvořený z původního seznamu takovým způsobem, že se z něj „odstraní“ první prvek – ve skutečnosti se však žádný nový seznam nevytváří a již vůbec se nikde žádný prvek neodstraňuje, pouze se jednoduše vrátí reference na druhý prvek původního seznamu, popř. prázdný seznam (), pokud žádný druhý prvek v seznamu uložen není:

user=> (pop '("prvni" "druhy" "treti"))
("druhy" "treti")
user=>(pop '("prvni"))
()
user=>(pop '("prvni" '("dalsi" "seznam")))
((quote ("dalsi" "seznam")))

Třetí společnou vlastností všech čtyř typů kolekcí je to, že se v každém případě vždy správně vyhodnotí jejich ekvivalence či neekvivalence, nezávisle na tom, jakým způsobem kolekce vznikla – Clojure v tomto případě nemůže pouze jednoduše porovnat rovnost referencí ukazujících na porovnávané kolekce (například dva seznamy), ale mnohdy musí rekurzivně procházet jak samotnými porovnávanými kolekcemi, tak i jejich prvky, jež taktéž mohou být kolekcemi. Naproti tomu v Javě to není vždy tak jednoduché, jak by se možná mohlo na první pohled zdát, protože porovnání dvou objektů vyžaduje překrytí metody Object.equals() a tím pádem i detailní znalost interních struktur porovnávaných objektů.

6. Seznam jako forma zápisu programů aneb kód==data

Seznamy, tj. jeden ze čtyř podporovaných typů kolekcí, mají v programovacím jazyku Clojure ještě jeden dosti zásadní význam. Samotný program zapsaný v Clojure není totiž nic jiného než seznam (či seznamy), přičemž prvním prvkem seznamu je jméno funkce a zbylé prvky seznamu jsou chápány jako parametry této funkce. Příkladem může být například funkce println, která vypíše obsah svých parametrů na standardní výstup a posléze je provedeno odřádkování. Klasický program typu „Hello world“ by tedy mohl v Clojure vypadat následovně:

user=> (println "Hello" "world")
Hello world

Funkce samozřejmě mohou vracet i nějaké výsledky, což nám umožňuje zapisovat program funkcionálním stylem – tedy jako funkci, jejíž parametry jsou vyhodnoceny na základě nějakých jiných funkcí vyhodnocovaných rekurzivně. Mějme například funkci pojmenovanou + (což je v Clojure korektní jméno funkce), která provede součet všech svých parametrů, a dále funkci pojmenovanou *, která naopak vynásobí všechny své parametry. Můžeme tedy psát:

user=> (+ 1 2 3)
6

nebo též:

user=> (* 6 7)
42

Ovšem taktéž je možné použít následující zápis, kdy se nejdříve vyhodnotí vnitřní funkce a teprve poté se jejich výsledky použijí pro násobení:

user=> (* (+ 1 2 3) (+ 3 4))
42

Pravidla pro vyhodnocení forem jsou v programovacím jazyku Clojure velmi jednoduchá a přímočará, na rozdíl od mnoha jiných programovacích jazyků. Tato pravidla lze ve zjednodušené podobě sepsat do několika bodů:

  1. čísla, řetězce, pravdivostní hodnoty a další literály jsou vyhodnoceny samy na sebe (což je logické – jedná se o dále nedělitelné objekty)
  2. klíčová hesla (keywords) jsou taktéž vyhodnocována sama na sebe: výsledkem :foo je opět :foo.
  3. hodnotou symbolu je objekt, který je na tento symbol navázán (analogie z jiných programovacích jazyků – hodnotou proměnné zapsané svým jménem je hodnota uložená do proměnné)
  4. seznamy jsou vyhodnocovány tak, že se první prvek seznamu chápe jako jméno funkce (či speciální formy), které je předán zbytek seznamu jako parametry této funkce (formy)
  5. pokud seznam obsahuje podseznamy, jsou tyto podseznamy vyhodnoceny nejdříve, přičemž úroveň rekurzivního zanořování při vyhodnocování podseznamů není teoreticky omezena (tj. podseznamy lze vnořovat do téměř libovolné úrovně)
  6. další tři typy kolekcí jsou vyhodnoceny na stejnou kolekci, ovšem s tím, že případné seznamy zde uložené (jako prvky) jsou nejprve vyhodnoceny podle předchozích dvou bodů.

Poslední pravidlo může být možná trošku nejasné, pojďme si tedy ukázat jednoduchý příklad:

user=> [ (+ 1 2) (+ 3 4) (+ 5 6) (+ 7 8)]
[3 7 11 15]

7. Demonstrační příklady: aritmetické funkce (prefixový zápis operátorů)

Vzhledem k tomu, že vyhodnocování seznamů – volání funkcí – představuje důležitou součást programovacího jazyka Clojure, ukážeme si způsob tohoto vyhodnocování na několika demonstračních příkladech. První řádek uvedený pod poznámkou (uvozenou znakem ;) představuje text zapsaný uživatelem na klávesnici, řádek druhý vypisuje samotná smyčka REPL (vynecháváme zde tedy prompt – výzvu):

Vyhodnocení seznamu obsahujícího jako první prvek funkci:

(max 10 20)
20

Vyhodnocení seznamu obsahujícího další seznamy (každý podseznam samozřejmě znamená volání funkce):

(max (min 10 20) (min 30 40))
30

Zatímco v naprosté většině „mainstreamových“ programovacích jazyků, jakými jsou například Céčko, Java, JavaScript či Python, se aritmetické a logické výrazy zapisují v takzvané infixové notaci, při níž jsou binární operátory zapisovány mezi dvojici operandů, tvůrci jazyka Clojure (resp. přesněji řečeno již tvůrci LISPu) se od tohoto způsobu zápisu distancovali – namísto toho jsou v Clojure všechny základní aritmetické i logické (a samozřejmě též relační) operace zapisovány jako volání funkcí či speciálních forem, tj. vždy v prefixové podobě. Důvodů, proč byla zvolena tato forma zápisu výrazů, je více. Prvním důvodem je fakt, že syntaxe LISPu byla původně navrhována s tím, že později dojde k její změně, tj. samotná syntaxe nebyla pro tvůrce tohoto programovacího jazyka tak prioritní jako jeho sémantika (paradoxní přitom je, že se nakonec syntaxe LISPu nezměnila, takzvané M-výrazy se nedočkaly většího rozšíření, podobně jako další snahy o úpravu syntaxe LISPu tak, aby se eliminovalo množství závorek či právě prefixový zápis aritmetických výrazů).

Druhý důvod spočíval v tom, že zavedení infixových operátorů by do jazyka zavádělo zbytečné další komplikace: musely by se například řešit a přesně specifikovat priority operací (a u některých operací i jejich asociativita), se zapsanými výrazy by se složitěji prováděly různé symbolické manipulace (integrace, derivace, zjednodušování výrazů), infixové operátory by nebylo možné předávat jako parametry do jiných funkcí atd. Vzhledem k tomu, že aritmetické operátory jsou zapisovány jako volání funkcí, musí se znak či jméno příslušného operátoru uvádět ve vyhodnocovaném seznamu na prvním místě, podobně jako jméno jakékoli jiné funkce. Všechny dílčí podvýrazy se samozřejmě vyhodnocují dříve než celý výraz, což plně koresponduje s pravidly, která jsme si uvedli v předchozí kapitole (podvýraz je zapsán formou volání nějaké funkce). Většina aritmetických funkcí není omezena pouze na dva parametry, což znamená, že je například možné zavoláním jedné funkce nazvané + sečíst i více než dvě numerické hodnoty:

; začneme pozvolna jako na základní škole :-)
(+ 1 1)
2
; operace rozdílu - druhý argument funkce je odečten od prvního
(- 1 2)
-1
; součet řady čísel
(+ 1 2 3 4 5 6 7 8 9 10)
55
; níže uvedený výraz v infixové notaci odpovídá: 1-2-3-4-5....-10:
(- 1 2 3 4 5 6 7 8 9 10)
-53
; POZOR - závorky v LISPu nemají mnoho společného
; s vyjádřením priority aritmetických operací
; (nelze je použít tak volně jako například v céčku)
(* (+ 1 2) (+ 3 4))
21
(+ (* 1 2) (* 3 4))
14
; Clojure umí, podobně jako některé implementace LISPu,
; pracovat se zlomky, tj. snaží se racionální
; čísla vyjádřit formou zlomku (ideální jazyk do škol :-)
(/ 1 2)
1/2
(/ 1 2 3)
1/6
; další forma, která se vyhodnotí na zlomek
(/ 3 2)
3/2
; zkusíme výpočet složitějšího zlomku
(/ (+ 1 2) (+ 3 4))
3/7
; neracionální (reálná) čísla se vypisují tak, jak to
; známe z ostatních programovacích jazyků (samozřejmě
; v případě speciálních požadavků programátora lze použít
; různé formátovací funkce na úpravu výstupu)
(* 0.3 (/ (+ 1 2) (+ 3 4)))
0.1285714285714286

Programovací jazyk Clojure obsahuje i úplnou sadu relačních operátorů, které v závislosti na hodnotách předaných parametrů (operandů) vrací hodnotu true (pravda) či false (nepravda). Zde se Clojure spíše podobá Scheme než klasickému LISPu (s jeho T a nil), což je ostatně jen dobře:

; porovnání dvou číselných hodnot
; relace "menší než"
(< 1 2)
true
; relace "větší než"
(> 1 2)
false
; relace "menší nebo rovno"
(<= 1 2)
true
; relace "větší nebo rovno"
(>= 1 2)
false
; porovnání dvou výrazů na ekvivalenci
(= 1 2)
false
(= 1 1)
true
; podvýrazy se nejprve vyhodnotí a posléze se porovnají
; vyhodnocené výsledky (v tomto případě dva atomy)
(= (+ 1 1) (/ 4 2))
true
; na ekvivalenci lze porovnávat i seznamy (mapy, vektory...), nikoli pouze literály
(= '(1 2) '(1 2))
true
(= '(1 2) '(2 1))
false

8. Speciální formy

Poslední důležitou vlastností programovacího jazyka Clojure, s níž se v dnešním článku alespoň ve stručnosti seznámíme, je použití takzvaných speciálních forem, tj. čtvrtého typu forem zpracovávaných smyčkou REPL. Ze syntaktického hlediska jsou speciální formy zapisovány naprosto stejným způsobem jako běžné funkce (tj. v kulatých závorkách je nejprve zapsáno jméno funkce a posléze její parametry), ovšem existuje zde jeden významný rozdíl – zatímco u funkcí jsou všechny jejich parametry nejdříve rekurzivně vyhodnoceny a teprve posléze je funkce zavolána, u speciálních forem k tomuto vyhodnocení obecně nedochází, resp. jsou vyhodnoceny pouze některé parametry (které konkrétně, to závisí na tom, o jakou speciální formu se jedná). K čemu jsou speciální formy dobré? Typickým a pro praxi naprosto nezbytným příkladem je zápis podmíněných bloků kódu. V tomto případě potřebujeme, aby se nějaká část programu vykonala pouze v případě, že je splněna (popř. nesplněna) nějaká podmínka, v opačném případě nemá být tato část programu vůbec vykonána.

Pomocí běžných funkcí by nebylo možné tuto funkcionalitu splnit, protože by se kód (předaný jako parametr – jinou možnost v Clojure ostatně prakticky nemáme) vykonal ještě před zavoláním „podmínkové“ funkce. Z toho vyplývá, že samotná podmínka, i když se syntakticky podobá volání funkce, je speciální formou. V jazyku Clojure existuje pro zápis podmíněného příkazu mj. i speciální forma if, která očekává tři parametry:

CS24_early

  1. podmínku (výraz=formu, která se vyhodnotí na true či false
  2. formu vyhodnocenou v případě, že je podmínka splněna
  3. formu vyhodnocenou v případě, že podmínka není splněna

Příklady použití speciální formy if:

; na základě podmínky se vyhodnotí (a vrátí jako výsledek)
; buď řetězec "mensi" nebo "vetsi"
(if (< 1 2) "mensi" "vetsi")
"mensi"
; opačná podmínka - je vyhodnocen pouze druhý řetězec
(if (> 1 2) "mensi" "vetsi")
"vetsi"
; test na ekvivalenci
(if (= 1 2) "rovno" "nerovno")
"nerovno"
; další test na ekvivalenci
(if (= 1 1) "rovno" "nerovno")
"rovno"
; použití složitějších funkcí ve větvi "then" a "else"
(if (= 1 1) (+ 10 20) (/ 10 20))
30
; zde je výsledkem zlomek
(if (= 1 2) (+ 10 20) (/ 10 20))
1/2
; samotná speciální forma if může být volána uvnitř složitějšího výrazu
(* 84 (if (= 1 2) (+ 10 20) (/ 10 20)))
42N
; ještě jste se neztratili v závorkách? Zkusíme tedy vnořenou
; speciální formu if:
(* 112 (if (< (/ 2 3) (* 2 3)) (if (= 1 2) (+ 10 20) (- 1 5/8)) (/ 10 20)))
42N
; (že by Velká otázka byla zapsána právě takto?)

Teoreticky by bylo možné pomocí speciální formy implementovat i programové smyčky, ovšem pro tento účel se ve funkcionálních jazycích používá spíše mnohem obecnější mechanismus – rekurze a speciálně též tail rekurze, která se musí v Clojure uvést explicitně. Podrobnosti si řekneme v další části tohoto seriálu.

9. Odkazy na Internetu

  1. Clojure home page
    http://clojure.org/downloads
  2. Clojure – Functional Programming for the JVM
    http://java.ociweb.com/mar­k/clojure/article.html
  3. Clojure quick reference
    http://faustus.webatu.com/clj-quick-ref.html
  4. 4Clojure
    http://www.4clojure.com/
  5. ClojureDoc
    http://clojuredocs.org/
  6. Clojure (Wikipedia EN)
    http://en.wikipedia.org/wiki/Clojure
  7. Clojure (Wikipedia CS)
    http://cs.wikipedia.org/wiki/Clojure
  8. Riastradh's Lisp Style Rules
    http://mumble.net/~campbe­ll/scheme/style.txt
  9. Dynamic Languages Strike Back
    http://steve-yegge.blogspot.cz/2008/05/dynamic-languages-strike-back.html
  10. Scripting: Higher Level Programming for the 21st Century
    http://www.tcl.tk/doc/scripting.html
  11. Java Virtual Machine Support for Non-Java Languages
    http://docs.oracle.com/ja­vase/7/docs/technotes/gui­des/vm/multiple-language-support.html
  12. New JDK 7 Feature: Support for Dynamically Typed Languages in the Java Virtual Machine
    http://java.sun.com/develo­per/technicalArticles/Dyn­TypeLang/
  13. JSR 223: Scripting for the JavaTM Platform
    http://jcp.org/en/jsr/detail?id=223
  14. JSR 292: Supporting Dynamically Typed Languages on the JavaTM Platform
    http://jcp.org/en/jsr/detail?id=292
  15. Java 7: A complete invokedynamic example
    http://niklasschlimm.blog­spot.com/2012/02/java-7-complete-invokedynamic-example.html
  16. InvokeDynamic: Actually Useful?
    http://blog.headius.com/2007/01/in­vokedynamic-actually-useful.html
  17. A First Taste of InvokeDynamic
    http://blog.headius.com/2008/09/first-taste-of-invokedynamic.html
  18. Java 6 try/finally compilation without jsr/ret
    http://cliffhacks.blogspot­.com/2008/02/java-6-tryfinally-compilation-without.html
  19. An empirical study of Java bytecode programs
    http://www.mendeley.com/research/an-empirical-study-of-java-bytecode-programs/
  20. Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
    http://www.mobilefish.com/tu­torials/java/java_quickgu­ide_jvm_instruction_set.html
  21. The JVM Instruction Set
    http://mpdeboer.home.xs4a­ll.nl/scriptie/node14.html
  22. Control Flow in the Java Virtual Machine
    http://www.artima.com/under­thehood/flowP.html
  23. Root.cz: Využití komprimovaných ukazatelů na objekty v JVM
    http://www.root.cz/clanky/vyuziti-komprimovanych-ukazatelu-na-objekty-v-nbsp-jvm/
  24. Root.cz: JamVM aneb alternativa k HotSpotu nejenom pro embedded zařízení a chytré telefony
    http://www.root.cz/clanky/jamvm-aneb-alternativa-k-hotspotu-nejenom-pro-embedded-zarizeni-tablety-a-chytre-telefony/
  25. The JavaTM Virtual Machine Specification, Second Edition
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/VMSpec­TOC.doc.html
  26. The class File Format
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/Clas­sFile.doc.html
  27. javap – The Java Class File Disassembler
    http://docs.oracle.com/ja­vase/1.4.2/docs/tooldocs/win­dows/javap.html
  28. javap-java-1.6.0-openjdk(1) – Linux man page
    http://linux.die.net/man/1/javap-java-1.6.0-openjdk
  29. Using javap
    http://www.idevelopment.in­fo/data/Programming/java/mis­cellaneous_java/Using_javap­.html
  30. Examine class files with the javap command
    http://www.techrepublic.com/ar­ticle/examine-class-files-with-the-javap-command/5815354
  31. BCEL Home page
    http://commons.apache.org/bcel/
  32. BCEL Manual
    http://commons.apache.org/bcel/ma­nual.html
  33. Byte Code Engineering Library (Wikipedia)
    http://en.wikipedia.org/wiki/BCEL
  34. Java programming dynamics, Part 7: Bytecode engineering with BCEL
    http://www.ibm.com/develo­perworks/java/library/j-dyn0414/
  35. Bytecode Engineering
    http://book.chinaunix.net/spe­cial/ebook/Core_Java2_Volu­me2AF/0131118269/ch13lev1sec6­.html
  36. BCEL Tutorial
    http://www.smfsupport.com/sup­port/java/bcel-tutorial!/
  37. ASM Home page
    http://asm.ow2.org/
  38. Seznam nástrojů využívajících projekt ASM
    http://asm.ow2.org/users.html
  39. ObjectWeb ASM (Wikipedia)
    http://en.wikipedia.org/wi­ki/ObjectWeb_ASM
  40. Java Bytecode BCEL vs ASM
    http://james.onegoodcooki­e.com/2005/10/26/java-bytecode-bcel-vs-asm/
  41. Bytecode Outline plugin for Eclipse (screenshoty + info)
    http://asm.ow2.org/eclipse/index.html
  42. aspectj (Eclipse)
    http://www.eclipse.org/aspectj/
  43. Aspect-oriented programming (Wikipedia)
    http://en.wikipedia.org/wi­ki/Aspect_oriented_program­ming
  44. AspectJ (Wikipedia)
    http://en.wikipedia.org/wiki/AspectJ
  45. EMMA: a free Java code coverage tool
    http://emma.sourceforge.net/
  46. Cobertura
    http://cobertura.sourceforge.net/
  47. FindBugs
    http://findbugs.sourceforge.net/
  48. GNU Classpath
    www.gnu.org/s/classpath/
  49. Java VMs Compared
    http://bugblogger.com/java-vms-compared-160/
  50. JSRs: Java Specification Requests – JSR 223: Scripting for the Java Platform
    http://www.jcp.org/en/jsr/de­tail?id=223
  51. Scripting for the Java Platform
    http://java.sun.com/develo­per/technicalArticles/J2SE/Des­ktop/scripting/
  52. Scripting for the Java Platform (Wikipedia)
    http://en.wikipedia.org/wi­ki/Scripting_for_the_Java_Plat­form
  53. Java Community Process
    http://en.wikipedia.org/wi­ki/Java_Specification_Requ­est
  54. Java HotSpot VM Options
    http://www.oracle.com/technet­work/java/javase/tech/vmop­tions-jsp-140102.html
  55. Great Computer Language Shootout
    http://c2.com/cgi/wiki?Gre­atComputerLanguageShootout
  56. Java performance
    http://en.wikipedia.org/wi­ki/Java_performance
  57. Trying the prototype
    http://mail.openjdk.java.net/pi­permail/lambda-dev/2010-August/002179.html
  58. Better closures (for Java)
    http://blogs.sun.com/jrose/en­try/better_closures
  59. Lambdas in Java: An In-Depth Analysis
    http://www.infoq.com/articles/lambdas-java-analysis
  60. Class ReflectiveOperationException
    http://download.java.net/jdk7/doc­s/api/java/lang/Reflective­OperationException.html
  61. Scala Programming Language
    http://www.scala-lang.org/
  62. Run Scala in Apache Tomcat in 10 minutes
    http://www.softwaresecret­weapons.com/jspwiki/run-scala-in-apache-tomcat-in-10-minutes
  63. Fast Web Development With Scala
    http://chasethedevil.blog­spot.cz/2007/09/fast-web-development-with-scala.html
  64. Top five scripting languages on the JVM
    http://www.infoworld.com/d/developer-world/top-five-scripting-languages-the-jvm-855
  65. Proposal: Indexing access syntax for Lists and Maps
    http://mail.openjdk.java.net/pi­permail/coin-dev/2009-March/001108.html
  66. Proposal: Elvis and Other Null-Safe Operators
    http://mail.openjdk.java.net/pi­permail/coin-dev/2009-March/000047.html
  67. Java 7 : Oracle pushes a first version of closures
    http://www.baptiste-wicht.com/2010/05/oracle-pushes-a-first-version-of-closures/
  68. Groovy: An agile dynamic language for the Java Platform
    http://groovy.codehaus.org/Operators
  69. Better Strategies for Null Handling in Java
    http://www.slideshare.net/Step­han.Schmidt/better-strategies-for-null-handling-in-java
  70. Control Flow in the Java Virtual Machine
    http://www.artima.com/under­thehood/flowP.html
  71. Java Virtual Machine
    http://en.wikipedia.org/wi­ki/Java_virtual_machine
  72. ==, .equals(), compareTo(), and compare()
    http://leepoint.net/notes-java/data/expressions/22com­pareobjects.html
  73. New JDK7 features
    http://openjdk.java.net/pro­jects/jdk7/features/
  74. Project Coin: Bringing it to a Close(able)
    http://blogs.sun.com/darcy/en­try/project_coin_bring_clo­se
  75. CloseableFinder source code
    http://blogs.sun.com/darcy/re­source/ProjectCoin/Closea­bleFinder.java
  76. Joe Darcy blog about JDK
    http://blogs.sun.com/darcy
  77. Java 7 – more dynamics
    http://www.baptiste-wicht.com/2010/04/java-7-more-dynamics/
  78. New JDK 7 Feature: Support for Dynamically Typed Languages in the Java Virtual Machine
    http://java.sun.com/develo­per/technicalArticles/Dyn­TypeLang/index.html

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

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.