Obsah
1. Programovací jazyk Clojure 15: tvorba uživatelských maker
2. Generování kódu ve skriptovacích programovacích jazycích
3. Dva způsoby vytvoření seznamu obsahujícího část programu vykonaného funkcí eval
4. Využití funkcí eval a read-string
5. Tvorba jednoduchých maker s využitím defmacro
6. Expanze maker: macroexpand-1 a macroexpand
7. Použití reader maker „syntax-quote“ a „unquote“ při tvorbě uživatelských maker
8. Problém s vytvářením lokálních (unikátních) symbolů a řešení tohoto problému
1. Programovací jazyk Clojure 15: tvorba uživatelských maker
V předchozí části seriálu o vlastnostech programovacího jazyka Java i virtuálního stroje Javy (JVM) jsme si nejdříve zopakovali význam smyčky REPL (Read-Eval-Print-Loop), kterou je vybavena většina skriptovacích jazyků i jazyků založených na LISPu, a posléze jsme se zabývali popisem interních maker používaných objektem Reader. Připomeňme si, že tato makra jsou aplikována ihned při načítání forem ze standardního vstupu, popř. při načítání jednotlivých forem ze zdrojového souboru. Pro jednoduchost si můžeme představit, že tato makra provádí textové substituce, i když tyto substituce jsou obecně složitější, než například substituce prováděné preprocesorem programovacího jazyka C či C++, a to zejména z toho důvodu, že reader makra používají informace získané z interpretru jazyka Clojure – to se týká především makra nazvaného „syntax-quote“, které dokáže (kromě jiného) získat informace o jmenném prostoru, v němž je nějaký symbol definován.
V následující tabulce jsou vypsána makra objektu Reader, která lze v aplikacích napsaných v programovacím jazyce Clojure využít:
# | Makro | Název | Význam |
---|---|---|---|
1 | ; | comment | umožňuje obejít zápis (comment nějaký text) u komentářů |
2 | \ | character | používané při zápisu znakových literálů |
3 | ^ | metadata | přidání metadat k symbolům, seznamům, vektorům, mapám a množinám |
4 | ' | quote | nahrazuje zápis (quote …) |
5 | ` | syntax-quote | provádí plnou kvalifikaci symbolů + zde lze použít makra ~ a ~@ |
6 | ~ | unquote | zajistí, že se vyhodnotí pouze označená část formy (= provede substituci této části výsledkem) |
7 | ~@ | unquote-splicing | podobné předchozími makru, ovšem výsledná sekvence se vloží ve formě samostatných prvků do „obalující“ sekvence |
8 | @ | deref | nahrazuje zápis (deref …) |
9 | # | dispatch | má různé funkce: donutí reader, aby použil makro z jiné tabulky maker |
Makro dispatch má ve skutečnosti několik významů v závislosti na tom, jaký znak je uveden ihned po křížku (#):
# | Dvojice znaků | Způsob použití | Význam |
---|---|---|---|
1 | #{ | #{prvky} | zápis množiny |
2 | #" | #„regexp-pattern“ | zápis regulárního výrazu |
3 | #' | #'var | quotování proměnných |
4 | #( | #(telo funkce) | zkrácený zápis anonymní funkce |
5 | #_ | #_text | text je ignorován – alternativní způsob komentáře |
Aby toho nebylo málo, má křížek (hash) ještě jeden význam – pomocí něho je totiž možné vytvářet lokální symboly s jednoznačným jménem, což je problematika týkající se maker, které se budeme podrobněji věnovat v sedmé kapitole.
2. Generování kódu ve skriptovacích programovacích jazycích
Před popisem tvorby uživatelských maker je vhodné se zmínit o funkcích eval a read-string, protože právě s využitím těchto dvou funkcí je možné ukázat způsob interpretace programů v Clojure a skládání programů z do sebe vnořených seznamů. Již několikrát jsme si v tomto seriálu řekli, že programovací jazyk Clojure, resp. přesněji řečeno jeho varianta určená pro běh nad JVM, v sobě spojuje některé přednosti interpretovaných jazyků i jazyků překládaných (kompilovaných). Jakýkoli výraz, neboli forma, je nejdříve načten objektem Reader, který na zadaný vstupní text aplikuje svoje makra. Posléze je výsledek předán internímu překladači, který z textu vytvoří bajtkód (přitom může v případě potřeby těsně před překladem aplikovat uživatelská makra). Vytvořený bajtkód je následně zpracován virtuálním strojem Javy naprosto stejným způsobem jako bajtkód vzniklý překladem běžných javovských zdrojových kódů.
Obvykle bývá vstupní zdrojový text napsaný v Clojure předáván interpretru interaktivně, tj. ve smyčce REPL, nebo je překlad proveden ze zdrojových kódů, a to buď implicitně (Clojure se chová jako interpret a bajtkód je vytvářen jen v paměti) nebo explicitně (vzniknou soubory .class). Ovšem vzhledem k tomu, že tento překlad (a následné vyhodnocení formy) může probíhat až v čase běhu aplikace (v runtime), je možné, aby byla část programu vytvořena dynamicky, například na základě dat získaných od uživatele atd. Jedná se o vlastnost, která se poprvé objevila v LISPu před více než padesáti lety a programovací jazyk Clojure samozřejmě tuto vlastnost podporuje taktéž. Základem této technologie je funkce eval, které se musí předat korektní výraz (forma). Tato forma je funkcí eval interně přeložena a vyhodnocena, přičemž návratovou hodnotou funkce eval je hodnota vyhodnocené formy.
3. Dva způsoby vytvoření seznamu obsahujícího část programu vykonaného funkcí eval
Důležité je nezapomenout na to, že funkce eval očekává, že jí bude předána korektní forma a nikoli řetězec či něco podobného. Připomeňme si, že korektní forma je seznamem, na jehož prvním místě je uveden název nějaké funkce (či speciální formy), za nímž následují parametry této funkce. Tyto parametry mohou být opět rekurzivně zapsány ve formě funkce s parametry. Podívejme se nyní na příklad vytvoření seznamu, který je současně i korektně zapsanou formou. Následně je tento seznam vyhodnocen pomocí eval:
; vytvoření nové globální proměnné ; a přiřazení SEZNAMU do této proměnné user=> (def hello-code '(println "Hello world!")) #'user/hello-code ; hodnotu proměnné (tedy obsah seznamu) ; lze samozřejmě kdykoli získat user=> hello-code (println "Hello world!") ; i když proměnná obsahuje seznam s korektním ; voláním funkce, není možné použít následující ; formu pro zavolání této funkce user=> (hello-code) ClassCastException clojure.lang.PersistentList cannot be cast to clojure.lang.IFn user/eval1 (NO_SOURCE_FILE:3) ; namísto toho se musí použít funkce eval user=> (eval hello-code) Hello world! nil user=>
Seznam obsahující formu lze samozřejmě vytvořit i „dynamičtěji“ s použitím funkce list. Větší dynamičnost spočívá v tom, že se díky nepoužití makra quote mohou jednotlivé prvky seznamu získat jako výsledek nějaké jiné funkce:
; vytvoření nové globální proměnné ; a přiřazení SEZNAMU do této proměnné user=> (def hello-code2 (list 'println "Hello world")) #'user/hello-code2 ; hodnotu proměnné (tedy obsah seznamu) ; lze samozřejmě kdykoli získat user=> hello-code2 (println "Hello world") ; i když proměnná obsahuje seznam s korektním ; voláním funkce, není možné použít následující ; formu pro zavolání této funkce user=> (hello-code2) ClassCastException clojure.lang.PersistentList cannot be cast to clojure.lang.IFn user/eval1 (NO_SOURCE_FILE:3) ; namísto toho se musí použít funkce eval user=> (eval hello-code2) Hello world nil user=>
4. Využití funkcí eval a read-string
V předchozí kapitole jsme si řekli, že do funkce eval je možné předat pouze korektně zapsanou formu. V některých případech je však určitý výraz nebo i větší část programů dostupná pouze ve formě řetězce – ten může být přečten například ze souboru, zadán uživatelem v nějakém GUI dialogu atd. Problém nastane v případě, kdy se pokusíme tento řetězec předat funkci eval v domnění, že se předávaný řetězec „automagicky“ bude transformovat na korektní formu a ta se následně vyhodnotí. Předpoklad, že eval bude jako svůj parametr akceptovat řetězec, může vycházet ze zkušeností vývojáře s jinými programovacími jazyky, kde tomu tak skutečně může být, ovšem v Clojure to neplatí a pro toto chování jsou i dobré důvody – mimo jiné i bezpečnost (a taktéž to, že parsování řetězce skutečně není prací pro eval). Podívejme se nyní, co se stane, pokud se pokusíme nechat vyhodnotit řetězec obsahující zápis korektní formy, ovšem pouze v textové podobě:
; vytvoření symbolu a přiřazení hodnoty ; (řetězce) k tomuto symbolu user=> (def hello-code "(println \"Hello world!\")") #'user/hello-code ; symbol je skutečně navázán na řetězec user=> hello-code "(println \"Hello world!\")" ; toto zaručeně nebude fungovat user=> (hello-code) ClassCastException java.lang.String cannot be cast to clojure.lang.IFn user/eval9 (NO_SOURCE_FILE:10) ; následující zápis sice fungovat bude, ; ale to jen proto, že se řetězec vyhodnotí ; na ten samý řetězec (což asi není očekávaný výsledek) user=> (eval hello-code) "(println \"Hello world!\")" user=>
Pro převod řetězce na objekt zpracovatelný pomocí eval je však možné použít funkci read. Návratovou hodnotou této funkce může být hodnota jakéhokoli typu, tj. například číslo, seznam, vektor, symbol atd. V řetězci však musí být uložena textová podoba korektní formy, jinak dojde k chybě:
; číslo uložené v řetězci se převede na skutečnou ; číselnou hodnotu user=> (read-string "42") 42 ; textová podoba seznamu uloženého ; v řetězci se stane seznamem user=> (read-string "(* 6 7)") (* 6 7) ; textová podoba seznamu uloženého ; v řetězci se stane seznamem ; obsahujícím podseznamy (nedojde ke zploštění seznamu) user=> (read-string "(+ 1 (+ 2 (+ 3 (+ 4 5))))") (+ 1 (+ 2 (+ 3 (+ 4 5)))) ; textová podoba vektoru uloženého ; v řetězci se stane seznamem user=> (read-string "[1 2 3 4]") [1 2 3 4] ; toto však není korektní forma!!! user=> (read-string "(((") RuntimeException EOF while reading clojure.lang.Util.runtimeException (Util.java:170) user=>
Nyní již víme, jak lze z řetězce získat například seznam a tento seznam následně vyhodnotit:
; vytvoření symbolu a přiřazení hodnoty ; (řetězce) k tomuto symbolu user=> (def hello-code "(println \"Hello world!\")") #'user/hello-code ; převod řetězce na skutečný objekt user=> (read-string hello-code) (println "Hello world!") ; tento objekt již LZE vyhodnotit user=> (eval (read-string hello-code)) Hello world! nil user=>
5. Tvorba jednoduchých maker s využitím defmacro
Konečně se dostáváme k ústřednímu tématu tohoto článku – k tvorbě uživatelských maker. Význam uživatelských maker spočívá především v tom, že se tato makra provedou (aplikují) při zpracování zadávaných forem těsně předtím, než jsou tyto formy zkompilovány do bajtkódu. To má dva důsledky: makro je v daném místě programu vykonáno pouze jednou a navíc makro může pracovat přímo s uživatelem zadanou formou bez toho, aby se daná forma automaticky vyhodnotila. To mj. znamená, že s využitím uživatelských maker lze realizovat i speciální formy, například nové typy programových smyček (to si ukážeme příště) apod. V následujícím textu se budeme snažit vytvořit poměrně jednoduché makro nazvané trace sloužící k tomu, aby se při jeho použití vypisovaly na standardní výstup trasovací informace o tom, jaký výraz (forma) je volána a jaká je návratová hodnota této formy po vyhodnocení. Toto makro by tedy mělo být při zápisu:
(trace vyraz)
…expandováno (v první velmi nedokonalé verzi) na následující formu:
(let [x vyraz] (println "vyraz = " x) x)
Důvod, proč je využita lokální proměnná je jednoduchý – hodnotu vyhodnoceného výrazu musíme vypsat a současně i vrátit (což je ono x na posledním řádku); navíc by nebylo správné výraz vyhodnocovat dvakrát kvůli vedlejším efektům. Za vyraz je přitom možné dosadit jakýkoli platný výraz akceptovaný interpretrem Clojure, což znamená, že po zápisu:
(trace (* 6 7))
By se měla ve skutečnosti vytvořit (v compile time) a následně v čase běhu (runtime) vyhodnotit tato forma:
(let [x (* 6 7)] (println "(* 6 7)=" x) x)
Resp. pravděpodobně spíše následující forma:
(let [x (* 6 7)] (println (quote (* 6 7)) "=" x) x)
(připomeňme si, že quote se obvykle zapisuje pomocí apostrofu, což však zde nebude možné, protože předchozí výraz budeme konstruovat jako seznam a nikoli zapisovat z klávesnice).
Vygenerování výrazu odpovídajícího požadovanému chování trace vlastně není příliš složité, když si připomeneme, jak jsme výrazy vytvářeli v předchozích dvou kapitolách. Stačí si uvědomit, že apostrofy zakážou vyhodnocení symbolu, který je zapsán ihned za apostrofem a funkce list slouží k vytvoření seznamu z prvků, které jsou funkci list předány, tj. zápis (list a b c) vrátí seznam (a b c):
(defn gen-trace [vyraz] (list 'let ['x vyraz] (list 'println (list 'quote vyraz) "=" 'x) 'x))
Chování tohoto „generátoru výrazů trace“ si můžeme jednoduše otestovat:
user=> (gen-trace 42) (let [x 42] (println (quote 42) "=" x) x) ; pozor na to, že musíme zabránit vyhodnocení ; symbolů, které předáváme generátoru výrazů ; (nejedná se o speciální formu) user=> (gen-trace 'x) (let [x x] (println (quote x) "=" x) x) ; pozor na to, že musíme zabránit vyhodnocení ; symbolů, které předáváme generátoru výrazů ; (nejedná se o speciální formu) user=> (gen-trace '(* 6 7)) (let [x (* 6 7)] (println (quote (* 6 7)) "=" x) x) user=>
Podle předchozích testů můžeme usoudit, že generátor vytváří korektní kód (formu), tudíž lze jeho výstup – onen vygenerovaný seznam – předat funkci eval:
user=> (eval (gen-trace '(* 6 7))) (* 6 7) = 42 42 user=>
Opět se zdá, že vše je v pořádku, takže zbývá udělat další krok – vytvořit skutečné uživatelské makro. To je velmi jednoduché, protože postačuje namísto defn (definice funkce) použít defmacro (definice makra). Výsledkem je objekt aplikovaný v čase zadávání nových forem, což nám umožní předávat makru výrazy bez nutnosti jejich quotování (uvození pomocí znaku apostrof nebo quote):
(defn gen-trace [vyraz] (list 'let ['x vyraz] (list 'println (list 'quote vyraz) "=" 'x) 'x)) ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ (defmacro trace [vyraz] (list 'let ['x vyraz] (list 'println (list 'quote vyraz) "=" 'x) 'x))
6. Expanze maker: macroexpand-1 a macroexpand
Tvar makra vytvořeného na konci předchozí kapitoly sice ještě není konečný (a to hned z několika důvodů), ovšem již nyní je možné toho makro otestovat na několika jednoduchých demonstračních příkladech. Pokud nějaký korektní výraz „obalíme“ voláním makra trace, měl by se na standardní výstup vypsat tvar tohoto výrazu a za znakem = pak i jeho hodnota. Nakonec se tato hodnota i vrátí jako výsledek, což znamená, že makro trace můžeme vkládat přímo do programového kódu, aniž by to tento kód nějak narušilo, pokud samozřejmě budeme akceptovat vedlejší efekt, který makro provádí:
; nejprve zadáme interpretru tvar makra (defmacro trace [vyraz] (list 'let ['x vyraz] (list 'println (list 'quote vyraz) "=" 'x) 'x)) ; otestování makra na hodnotě user=> (trace 42) 42 = 42 42 ; otestování makra na quotovaném symbolu ; (došlo zde pouze k expanzi reader makra) user=> (trace '42) (quote 42) = 42 42 ; otestování makra na volání funkce user=> (trace (* 6 7)) (* 6 7) = 42 42 ; otestování makra na volání složitější funkce user=> (trace (list (range 1 10) (range 20 30))) (list (range 1 10) (range 20 30)) = ((1 2 3 4 5 6 7 8 9) (20 21 22 23 24 25 26 27 28 29)) ((1 2 3 4 5 6 7 8 9) (20 21 22 23 24 25 26 27 28 29)) user=>
Jak při tvorbě nových uživatelských maker, tak i při používání již vytvořených maker, které jsou součástí knihoven programovacího jazyka Clojure je mnohdy důležité prozkoumat, jak bylo makro expandováno, tj. jakým způsobem změnilo výraz, který byl makru předán. Pro tyto účely lze využít dvojici funkcí nazvaných macroexpand a macroexpand-1. Funkce macroexpand-1, které se předá volání makra (jde o funkci, takže je zapotřebí volání zkoumaného makra quotovat!), vrátí expandovaný tvar makra, tj. formu, která je dále zpracována. Přitom se provádí vždy jen expanze prvně aplikovaného makra. Jedná se o funkci, která se velmi často používá při ladění uživatelských maker, protože nás většinou nezajímá, jak se expandují další makra (například when apod.) použitá v expandovaném výrazu. Pokud je z nějakého důvodu nutné vidět celou expanzi výrazu až do té míry, v jaké je výraz předán překladači, lze pro tento účel použít funkci nazvanou macroexpand (její chování velmi zhruba odpovídá chování preprocesoru jazyka C či C++, který taktéž vrátí všechna makra rekurzivně expandovaná).
Podívejme se, jak funkce macroexpand-1 expanduje naše makro trace:
user=> (macroexpand-1 '(trace 42)) (let [x 42] (println (quote 42) "=" x) x) user=> (macroexpand-1 '(trace '42)) (let [x (quote 42)] (println (quote (quote 42)) "=" x) x) user=> (macroexpand-1 '(trace (* 6 7))) (let [x (* 6 7)] (println (quote (* 6 7)) "=" x) x) user=> (macroexpand-1 '(trace (list (range 1 10) (range 20 30)))) (let [x (list (range 1 10) (range 20 30))] (println (quote (list (range 1 10) (range 20 30))) "=" x) x) user=>
7. Použití reader maker „syntax-quote“ a „unquote“ při tvorbě uživatelských maker
Podívejme se ještě jednou na výraz, na který jsme chtěli expandovat zápis (trace vyraz):
(let [x vyraz] (println (quote vyraz) " = " x) x)
Abychom vytvořili tento výraz ve formě rekurzivně zanořeného seznamu, definovali jsme makro trace s následující podobou:
(defmacro trace [vyraz] (list 'let ['x vyraz] (list 'println (list 'quote vyraz) "=" 'x) 'x))
Toto makro sice v rámci možností funguje (až na několik chybiček), ovšem způsob jeho zápisu a vlastně i způsob jeho vytvoření je, eufemicky řečeno, poměrně nehezký. Proč tomu tak je, je zřejmé – ve vytvářeném seznamu jsme museli všechny funkce quotovat, seznam se musel explicitně vytvářet s využitím funkce list atd. Bylo by určitě mnohem zajímavější, jednodušší a současně i elegantnější mít možnost zápisu makra ve formě jakési šablony, do níž by se pouze dosazovaly části původního výrazu předaného makru pro expanzi. Požadavek na snadnou formu makra pomocí „šablony“ ve skutečnosti není nijak nový, protože se začal objevovat již záhy po vytvoření programovacího jazyka LISP, tj. před zhruba padesáti lety. Tvůrci různých variant LISPu se tomuto požadavku snažili vyhovět různými způsoby; my si v následujícím textu řekneme, jak je tomu v případě Clojure. Vylepšený – a nutno říci, že prozatím nefunkční – tvar makra trace může vypadat následovně:
(defmacro trace2 [vyraz] `(let [x ~vyraz] (println '~vyraz "=" x) x))
Co se vlastně změnilo? Již zde nepoužíváme nepěkný způsob vytváření seznamu s využitím funkcí list a quotovaných jmen volaných funkcí a speciálních forem (konkrétně let, println a quote). Namísto toho je požadovaný výsledný tvar makra jednoduše zapsán a před tento zápis je vložen znak zpětného apostrofu, který se v objektu Reader přepíše (expanduje) jako makro „syntax-quote“, jehož funkci jsme si popsali již v předchozí části tohoto seriálu. Ovšem pouze s aplikací „syntax-quote“ se daleko nedostaneme, protože ve skutečnosti potřebujeme hned na dvou místech do makra vložit vstupní výraz. Aby to bylo možné, je uvnitř „syntax-quote“ použita tilda, neboli reader makro nazvané „dequote“, které pro nejbližší následující symbol ruší funkci quotování. Předchozí věta sice může znít složitě, ale při praktickém použití nám postačuje vědět, že pokud v makru potřebujeme použít parametr tohoto makra, zapisuje se před něj tilda, protože v opačném případě by se namísto konkrétního obsahu tohoto parametru v makru pouze použilo jeho jméno.
8. Problém s vytvářením lokálních (unikátních) symbolů a řešení tohoto problému
Nezbývá, než naše nové makro, o němž jsem napsal, že je nefunkční, vyzkoušet. Nejprve ho nadefinujeme:
user=> (defmacro trace2 [vyraz] `(let [x ~vyraz] (println '~vyraz "=" x) x)) #'user/trace2 user=>
Poté se ho pokusíme použít na expanzi jednoduchého výrazu 42:
user=> (trace2 42) CompilerException java.lang.RuntimeException: Can't let qualified name: user/x, compiling:(NO_SOURCE_PATH:110) user=>
Co se vlastně stalo? Překladač Clojure si stěžuje na to, že nezná symbol user/x, což je pravda, protože jsme takový symbol (globální pro celý jmenný prostor user) nevytvářeli. Zde jsme se vlastně nepřímo dotkli jedné chyby v původním makru – nefungovalo by správně ve chvíli, kdyby bylo použito v souvislosti s výrazem používajícím symbol x. Překladač Clojure si v tomto případě stěžuje z toho důvodu, že „syntax-quote“ kromě dalších manipulací se svým vstupem nahrazuje jména všech symbolů jejich plnými jmény, tj. například doplňuje názvy jmenných prostorů atd. To je zajisté výhodné při použití makra v jiném jmenném prostoru. Jak expanze vypadá?:
user=> (macroexpand-1 '(trace2 42)) (clojure.core/let [user/x 42] (clojure.core/println (quote 42) "=" user/x) user/x) user=>
Aby makro fungovalo správně, musíme umět vytvořit lokální symbol s unikátním jménem. Pro tento účel se používá (tak jako v mnoha dalších situacích) znak křížku zapisovaný ZA nějakým symbolem. Clojure zápis symbol# expanduje na symbol_generované_jmeno, které bude unikátní, což je přesně to, co potřebujeme. Ve třetí verzi našeho makra tedy nahradíme x za x#:
(defmacro trace3 [vyraz] `(let [x# ~vyraz] (println '~vyraz "=" x#) x#))
Bude to fungovat?:
; vytvoříme nové makro user=> (defmacro trace3 [vyraz] `(let [x# ~vyraz] (println '~vyraz "=" x#) x#)) #'user/trace3 ; otestujeme ho na jednoduchém volání funkce user=> (trace3 (* 6 7)) (* 6 7) = 42 42 ; finito
Na závěr se ještě podíváme na expanzi třetí verze našeho makra. Zejména nás bude zajímat, jak byl expandován zápis x#:
user=> (macroexpand-1 '(trace3 (* 6 7))) (clojure.core/let [x__165__auto__ (* 6 7)] (clojure.core/println (quote (* 6 7)) "=" x__165__auto__) x__165__auto__)
Je zřejmé, že symbol x__165__auto__ je jak lokální, tak i unikátní (to možná zřejmé není, ale je tomu tak :-), což je přesně to, co potřebujeme.
Příště si ukážeme, jak uživatelská makra využít praktičtějším způsobem.
9. Odkazy na Internetu
- Clojure Macro Tutorial (Part I, Getting the Compiler to Write Your Code For You)
http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-i-getting.html - Clojure Macro Tutorial (Part II: The Compiler Strikes Back)
http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-ii-compiler.html - Clojure Macro Tutorial (Part III: Syntax Quote)
http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-ii-syntax.html - Tech behind Tech: Clojure Macros Simplified
http://techbehindtech.com/2010/09/28/clojure-macros-simplified/ - Fatvat – Exploring functional programming: Clojure Macros
http://www.fatvat.co.uk/2009/02/clojure-macros.html - Eulerovo číslo
http://cs.wikipedia.org/wiki/Eulerovo_číslo - List comprehension
http://en.wikipedia.org/wiki/List_comprehension - List Comprehensions in Clojure
http://asymmetrical-view.com/2008/11/18/list-comprehensions-in-clojure.html - Clojure Programming Concepts: List Comprehension
http://en.wikibooks.org/wiki/Clojure_Programming/Concepts#List_Comprehension - Clojure core API: for macro
http://clojure.github.com/clojure/clojure.core-api.html#clojure.core/for - cirrus machina – The Clojure for macro
http://www.cirrusmachina.com/blog/comment/the-clojure-for-macro/ - Clojure.org: Clojure home page
http://clojure.org/downloads - Clojure.org: Vars and the Global Environment
http://clojure.org/Vars - Clojure.org: Refs and Transactions
http://clojure.org/Refs - Clojure.org: Atoms
http://clojure.org/Atoms - Clojure.org: Agents as Asynchronous Actions
http://clojure.org/agents - A Couple of Clojure Agent Examples
http://lethain.com/a-couple-of-clojure-agent-examples/ - Clojure – Functional Programming for the JVM
http://java.ociweb.com/mark/clojure/article.html - Clojure quick reference
http://faustus.webatu.com/clj-quick-ref.html - 4Clojure
http://www.4clojure.com/ - ClojureDoc
http://clojuredocs.org/ - Clojure (Wikipedia EN)
http://en.wikipedia.org/wiki/Clojure - Clojure (Wikipedia CS)
http://cs.wikipedia.org/wiki/Clojure - Riastradh's Lisp Style Rules
http://mumble.net/~campbell/scheme/style.txt - Dynamic Languages Strike Back
http://steve-yegge.blogspot.cz/2008/05/dynamic-languages-strike-back.html - Scripting: Higher Level Programming for the 21st Century
http://www.tcl.tk/doc/scripting.html - Java Virtual Machine Support for Non-Java Languages
http://docs.oracle.com/javase/7/docs/technotes/guides/vm/multiple-language-support.html - New JDK 7 Feature: Support for Dynamically Typed Languages in the Java Virtual Machine
http://java.sun.com/developer/technicalArticles/DynTypeLang/ - JSR 223: Scripting for the JavaTM Platform
http://jcp.org/en/jsr/detail?id=223 - JSR 292: Supporting Dynamically Typed Languages on the JavaTM Platform
http://jcp.org/en/jsr/detail?id=292 - Java 7: A complete invokedynamic example
http://niklasschlimm.blogspot.com/2012/02/java-7-complete-invokedynamic-example.html - InvokeDynamic: Actually Useful?
http://blog.headius.com/2007/01/invokedynamic-actually-useful.html - A First Taste of InvokeDynamic
http://blog.headius.com/2008/09/first-taste-of-invokedynamic.html - Java 6 try/finally compilation without jsr/ret
http://cliffhacks.blogspot.com/2008/02/java-6-tryfinally-compilation-without.html - An empirical study of Java bytecode programs
http://www.mendeley.com/research/an-empirical-study-of-java-bytecode-programs/ - Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
http://www.mobilefish.com/tutorials/java/java_quickguide_jvm_instruction_set.html - The JVM Instruction Set
http://mpdeboer.home.xs4all.nl/scriptie/node14.html - Control Flow in the Java Virtual Machine
http://www.artima.com/underthehood/flowP.html - Root.cz: Využití komprimovaných ukazatelů na objekty v JVM
http://www.root.cz/clanky/vyuziti-komprimovanych-ukazatelu-na-objekty-v-nbsp-jvm/ - 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/ - The JavaTM Virtual Machine Specification, Second Edition
http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html - The class File Format
http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html - javap – The Java Class File Disassembler
http://docs.oracle.com/javase/1.4.2/docs/tooldocs/windows/javap.html - javap-java-1.6.0-openjdk(1) – Linux man page
http://linux.die.net/man/1/javap-java-1.6.0-openjdk - Using javap
http://www.idevelopment.info/data/Programming/java/miscellaneous_java/Using_javap.html - Examine class files with the javap command
http://www.techrepublic.com/article/examine-class-files-with-the-javap-command/5815354 - BCEL Home page
http://commons.apache.org/bcel/ - BCEL Manual
http://commons.apache.org/bcel/manual.html - Byte Code Engineering Library (Wikipedia)
http://en.wikipedia.org/wiki/BCEL - Java programming dynamics, Part 7: Bytecode engineering with BCEL
http://www.ibm.com/developerworks/java/library/j-dyn0414/ - Bytecode Engineering
http://book.chinaunix.net/special/ebook/Core_Java2_Volume2AF/0131118269/ch13lev1sec6.html - BCEL Tutorial
http://www.smfsupport.com/support/java/bcel-tutorial!/ - ASM Home page
http://asm.ow2.org/ - Seznam nástrojů využívajících projekt ASM
http://asm.ow2.org/users.html - ObjectWeb ASM (Wikipedia)
http://en.wikipedia.org/wiki/ObjectWeb_ASM - Java Bytecode BCEL vs ASM
http://james.onegoodcookie.com/2005/10/26/java-bytecode-bcel-vs-asm/ - Bytecode Outline plugin for Eclipse (screenshoty + info)
http://asm.ow2.org/eclipse/index.html - aspectj (Eclipse)
http://www.eclipse.org/aspectj/ - Aspect-oriented programming (Wikipedia)
http://en.wikipedia.org/wiki/Aspect_oriented_programming - AspectJ (Wikipedia)
http://en.wikipedia.org/wiki/AspectJ - EMMA: a free Java code coverage tool
http://emma.sourceforge.net/ - Cobertura
http://cobertura.sourceforge.net/ - FindBugs
http://findbugs.sourceforge.net/ - GNU Classpath
www.gnu.org/s/classpath/ - Java VMs Compared
http://bugblogger.com/java-vms-compared-160/ - JSRs: Java Specification Requests – JSR 223: Scripting for the Java Platform
http://www.jcp.org/en/jsr/detail?id=223 - Scripting for the Java Platform
http://java.sun.com/developer/technicalArticles/J2SE/Desktop/scripting/ - Scripting for the Java Platform (Wikipedia)
http://en.wikipedia.org/wiki/Scripting_for_the_Java_Platform - Java Community Process
http://en.wikipedia.org/wiki/Java_Specification_Request - Java HotSpot VM Options
http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html - Great Computer Language Shootout
http://c2.com/cgi/wiki?GreatComputerLanguageShootout - Java performance
http://en.wikipedia.org/wiki/Java_performance - Trying the prototype
http://mail.openjdk.java.net/pipermail/lambda-dev/2010-August/002179.html - Better closures (for Java)
http://blogs.sun.com/jrose/entry/better_closures - Lambdas in Java: An In-Depth Analysis
http://www.infoq.com/articles/lambdas-java-analysis - Class ReflectiveOperationException
http://download.java.net/jdk7/docs/api/java/lang/ReflectiveOperationException.html - Scala Programming Language
http://www.scala-lang.org/ - Run Scala in Apache Tomcat in 10 minutes
http://www.softwaresecretweapons.com/jspwiki/run-scala-in-apache-tomcat-in-10-minutes - Fast Web Development With Scala
http://chasethedevil.blogspot.cz/2007/09/fast-web-development-with-scala.html - Top five scripting languages on the JVM
http://www.infoworld.com/d/developer-world/top-five-scripting-languages-the-jvm-855 - Proposal: Indexing access syntax for Lists and Maps
http://mail.openjdk.java.net/pipermail/coin-dev/2009-March/001108.html - Proposal: Elvis and Other Null-Safe Operators
http://mail.openjdk.java.net/pipermail/coin-dev/2009-March/000047.html - Java 7 : Oracle pushes a first version of closures
http://www.baptiste-wicht.com/2010/05/oracle-pushes-a-first-version-of-closures/ - Groovy: An agile dynamic language for the Java Platform
http://groovy.codehaus.org/Operators - Better Strategies for Null Handling in Java
http://www.slideshare.net/Stephan.Schmidt/better-strategies-for-null-handling-in-java - Control Flow in the Java Virtual Machine
http://www.artima.com/underthehood/flowP.html - Java Virtual Machine
http://en.wikipedia.org/wiki/Java_virtual_machine - ==, .equals(), compareTo(), and compare()
http://leepoint.net/notes-java/data/expressions/22compareobjects.html - New JDK7 features
http://openjdk.java.net/projects/jdk7/features/ - Project Coin: Bringing it to a Close(able)
http://blogs.sun.com/darcy/entry/project_coin_bring_close - CloseableFinder source code
http://blogs.sun.com/darcy/resource/ProjectCoin/CloseableFinder.java - Joe Darcy blog about JDK
http://blogs.sun.com/darcy - Java 7 – more dynamics
http://www.baptiste-wicht.com/2010/04/java-7-more-dynamics/ - New JDK 7 Feature: Support for Dynamically Typed Languages in the Java Virtual Machine
http://java.sun.com/developer/technicalArticles/DynTypeLang/index.html