Obsah
1. Programovací jazyk Clojure 16: složitější uživatelská makra
2. Zápis kódu generovaného makrem s využitím „šablony“
3. Příprava na vylepšení makra trace
4. Funkce s proměnným počtem parametrů
5. Příklad funkce s proměnným počtem parametrů
7. Vytvoření nového typu programové smyčky forloop s využitím maker
8. Úprava programové smyčky forloop
1. Programovací jazyk Clojure 16: složitější uživatelská makra
V dnešní části seriálu o programovacím jazyce Java i o virtuálním stroji Javy (JVM) se budeme podrobněji zabývat popisem makrosystému jazyka Clojure postaveného nad JVM. Minule jsme si řekli, že makra v Clojure jsou aplikována na zadávané či načítané formy ve chvíli načítání těchto forem a nikoli až v čase běhu programu – makra zde tedy slouží ke stejnému účelu, jako například zcela odlišným způsobem koncipovaná makra použitá v programovacích jazycích C a C++. V předchozím článku jsme si taktéž ukázali dva způsoby tvorby uživatelských maker. První způsob spočíval v poněkud namáhavé konstrukci výsledného kódu s využitím funkcí a speciálních forem list, quote a concat. Vážnou nevýhodou tohoto způsobu tvorby maker je především dlouhý i nepřehledný zápis makra a taktéž to, že se uživatel při tvorbě složitějšího makra obvykle nevyhne nutnosti toto makro několikrát přepsat, protože někde zapomene uvést quote, nebo naopak quote přidá i tam, kde ve skutečnosti nemá být.
Připomeňme si nejdříve, jak jsme vytvořili první uživatelské makro. Toto makro se jmenovalo trace a když bylo zavoláno s určitým výrazem, mělo dojít k vygenerování kódu, který nejdříve vypsal tento výraz (přesněji řečeno jeho tisknutelnou podobu) a následně vypsal i hodnotu vzniklou vyhodnocením tohoto výrazu. Současně měla být tato hodnota i návratovou hodnotou vygenerovaného kódu, aby se vložením makra do složitějšího programu neovlivnila správná funkce tohoto programu. Kód, který měl být makrem trace vygenerován, vypadá zhruba takto:
(let [x vyraz] (println (quote vyraz) " = " x) x)
Můžeme zde vidět použití lokální proměnné x, díky níž se předaný výraz vyhodnotí pouze jednou, což je nutné zajistit zejména tehdy, pokud by nějaká funkce volaná z výrazu měla vedlejší efekt. 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))
O tom, že makro pracuje (alespoň zdánlivě) korektně, jsme se mohli přesvědčit použitím funkce macroexpand-1, která zobrazí expandované makro:
; nejprve zadáme interpretru tvar makra (defmacro trace [vyraz] (list 'let ['x vyraz] (list 'println (list 'quote vyraz) "=" 'x) 'x)) ; otestování makra na volání jednoduché funkce user=> (trace (* 6 7)) (* 6 7) = 42 42 ; zobrazíme si expandované makro user=> (macroexpand-1 '(trace (* 6 7))) (let [x (* 6 7)] (println (quote (* 6 7)) "=" x) x) user=>
2. Zápis kódu generovaného makrem s využitím „šablony“
Zápis makra uvedený v předchozí kapitole ve skutečnosti není ani příliš pěkný, ani čitelný, protože jsme byli nuceni všechny funkce (které se měly objevit ve výsledném kódu) quotovat, tj. zapsat před ně znak apostrofu, což je zkrácený způsob zápisu (quote …). Dále se výsledný seznam musel explicitně vytvářet s využitím funkce list atd. Mnohem čitelnější a elegantnější je použít zápis makra využívající takzvaná reader makra nazvaná „syntax-quote“ (zapisováno formou znaku zpětného apostrofu, back-tick) a „unquote“ (zapisováno jako znak tilda). S využitím zpětného apostrofu bylo možné tělo makra, tj. generovaný výraz, quotovat, ovšem s tou výjimkou, že symboly, před nimiž byl zapsán znak tildy („unquote“) byly naopak nahrazeny hodnotou přiřazenou k tomuto symbolu. S využitím znaků ` a ~ je tedy možné zapsat makro ve formě jakési šablony (template), do které se pouze doplňují hodnoty vybraných symbolů. Druhá verze makra trace je nepochybně jednodušší a čitelnější než verze první. Povšimněte si především toho, že v makru můžeme použít například i '~vyraz, protože znak ' („quote“) je uvnitř ` pouze opisován do kódu generovaného makrem a nemá zde tedy svůj obvyklý význam:
(defmacro trace2 [vyraz] `(let [x ~vyraz] (println '~vyraz "=" x) x))
Použití znaku ` však přineslo ještě jeden efekt – ke všem symbolům použitým v makru se přidal i jejich jmenný prostor (namespace/ns), což je sice zcela správně (makro bude možné použít i v jiném jmenném prostoru), ovšem jmenný prostor se doplnil i u symbolu x, který by měl být ve skutečnosti symbolem lokálním. Aby makro fungovalo zcela 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é_číslo, 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#))
Nyní tedy již známe téměř všechny nástroje používané při tvorbě maker:
Znak | Význam při tvorbě maker |
---|---|
` | většinou uzavírá celé tělo makra, tj. šablonu vytvářeného výrazu |
~ | ruší význam znaku `, ovšem pouze pro jediný symbol uvedený ihned za tímto znakem |
# | symbol zapsaný před tímto znakem bude doplněn takovým způsobem, aby se jednalo o unikátní jméno |
Ještě jsme si nevysvětlili význam dvouznaku ~@ (reader makro „unqote-splicing“), který bude využit v následujících kapitolách.
3. Příprava na vylepšení makra trace
Prozatím poslední varianta makra trace, která byla ukázána v předchozí kapitole, sice pracuje korektně, ale možná jste si všimli, že při použití tohoto makra je nutné při jeho vkládání do zdrojových kódů nejenom uvést název tohoto makra, ale navíc se musí okolo předávané funkce/výrazu/formy napsat i kulaté závorky:
; otestování makra na volání funkce user=> (trace (* 6 7)) (* 6 7) = 42 42 ; volání funkce apply user=> (trace4 (apply * (range 1 6))) (apply * (range 1 6)) = 120 120 ; 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=>
Toto chování samozřejmě není zcela špatné (použití makra se ze syntaktického hlediska neliší od volání běžné funkce či speciální formy), ovšem použití makra trace se stává poněkud složitější, než by mohlo být, protože ve chvíli, kdy potřebujeme do zdrojového kódu toto makro vložit (a vytvářet tak trasovací informace při běhu aplikace), je nutné nejenom před nějaký výraz zapsat (trace, ale dále je nutné najít ještě odpovídající pravou kulatou závorku a celé volání uzavřít pomocí ). To je již komplikovaný zásah do zdrojových kódů, zejména v případě, že programátor rád tvoří složité výrazy s mnoha závorkami, kvůli nimž se význam názvu programovacího jazyka LISP (z něhož je Clojure přímo odvozen) vysvětluje jako Lost In Stupid Parentheses. Jedním z důvodů existence maker je zjednodušit zápis programů, ne ho ještě udělat více složitý. Naším cílem tedy bude upravit makro trace takovým způsobem, abychom mohli psát prostě:
; otestování makra na volání funkce user=> (trace * 6 7) (* 6 7) = 42 42 ; volání funkce range user=> (trace range 1 10) (range 1 10) = (1 2 3 4 5 6 7 8 9) (1 2 3 4 5 6 7 8 9) ; volání funkce apply user=> (trace4 apply * (range 1 6)) (apply * (range 1 6)) = 120 120 ; 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=>
Asi nebudete příliš překvapeni zjištěním, že makro půjde přesně takovým způsobem upravit, a to relativně jednoduchým způsobem. Ještě před popisem potřebné úpravy si však musíme vysvětlit další vlastnost programovacího jazyka Clojure: způsob tvorby funkcí s proměnným počtem parametrů.
4. Funkce s proměnným počtem parametrů
V mnoha programovacích jazycích se jejich tvůrci snažili více či méně elegantně vyřešit problém tvorby funkcí či metod s proměnným počtem parametrů. Nejedná se sice o vlastnost, která by musela být v programovacím jazyku nutně implementována, ovšem její zahrnutí do syntaxe a sémantiky jazyka znamená pro programátory možnost tvorby univerzálněji použitelných funkcí. Zejména se tak dá efektivně obejít vytváření přetížených funkcí či metod, které jsou mnohdy přetěžovány jen z toho důvodu, že v některých případech jim potřebujeme předat doplňující parametry. Zajímavé je, že dokonce i v těch programovacích jazycích, které (třeba jen zpočátku) možnost tvorby funkcí s proměnným počtem parametrů nepovolovaly, se tato vlastnost objevila, například ve formě „speciálních příkazů“, které sice programátoři nemohli sami vytvářet, ale mohli alespoň tyto již připravené speciální příkazy používat (což na druhou stranu ukazuje na nekoncepčnost při návrhu těchto jazyků).
Pro příklady nemusíme chodit daleko – již programovací jazyk Pascal obsahoval „speciální příkazy“ write a writeln s proměnným počtem parametrů, stejně jako programovací jazyk Basic s jeho příkazy PRINT a INPUT.
Tvůrci některých dalších programovacích jazyků byli vůči programátorům-uživatelům férovější a zahrnuli tuto možnost přímo do syntaxe a sémantiky. Týká se to například programovacích jazyků C, C++, TCL, Python a podpory pro proměnný počet parametrů metod jsme se nakonec dočkali i v Javě (i když zde je možná typické, že nejprve musel přijít konkurent jménem C#, aby se tvůrci Javy konečně „rozhoupali“ pro implementaci nové vlastnosti do jazyka). Podporu pro proměnný počet parametrů obsahuje i programovací jazyk Clojure, v němž je možné s využitím znaku & oddělit lokální jména povinných parametrů od jména sekvence, do níž se uloží všechny zbylé parametry. Povinné parametry se označují termínem „poziční parametry“, zbylé parametry pak termínem „další (next) parametry“. Podívejme se nejprve na způsob zápisu funkce obsahující pouze dva poziční parametry (nejedná se o nic nového – podobný zápis byl v tomto seriálu použit již mockrát):
(defn fce1 [x y] (println "x = " x) (println "y = " y))
Použití této funkce je snadné:
user=> (fce1 1 2) x = 1 y = 2 nil user=> (fce1 "Hello" "world") x = Hello y = world nil user=>
Musíme ovšem přesně dodržet počet předávaných parametrů, jinak dojde při zavolání funkce k chybě:
user=> (fce1 1) ArityException Wrong number of args (1) passed to: user$fce1 clojure.lang.AFn.throwArity (AFn.java:437) user=> (fce1 1 2 3) ArityException Wrong number of args (3) passed to: user$fce1 clojure.lang.AFn.throwArity (AFn.java:437) user=> user=>
5. Příklad funkce s proměnným počtem parametrů
Pokusme se funkci fce1 z předchozí kapitoly upravit takovým způsobem, aby akceptovala jeden povinný (poziční) parametr a poté libovolné množství dalších parametrů. Úprava je v tomto případě velmi snadná, protože postačuje vložit znak ampersandu (&) mezi jména obou formálních parametrů. Počet jmen formálních parametrů před ampersandem totiž odpovídá počtu povinných parametrů, s nimiž se funkce může volat, za ampersandem je pak jméno symbolu s lokální platností, do něhož se ukládají zbylé parametry. Jelikož jejich počet není dopředu znám, je k tomuto symbolu buď přiřazena hodnota nil (žádný volitelný parametr nebyl předán), nebo je mu přiřazen seznam (byl předán alespoň jeden a nebo více volitelných parametrů):
(defn fce2 [x & y] (println "x = " x) (println "y = " y))
Použití této funkce je již mnohem zajímavější, protože se všechny nepovinné parametry vkládají do seznamu navázaného na lokální symbol y:
; dva parametry - první je uložen do x, druhý ; do prvního prvku seznamu y user=> (fce2 1 2) x = 1 y = (2) nil ; dva parametry - první je uložen do x, druhý ; do prvního prvku seznamu y user=> (fce2 "Hello" "world") x = Hello y = (world) nil ; tři parametry - první je uložen do x, druhý ; a třetí do seznamu y user=> (fce2 "Don't" "panic" '!) x = Don't y = (panic !) nil ; pouze jeden parametr uložený do x ; lokální symbol y je přiřazen k hodnotě nil!!! user=> (fce2 1) x = 1 y = nil nil ; tři parametry - první je uložen do x, druhý ; a třetí do seznamu y user=> (fce2 1 2 3) x = 1 y = (2 3) nil ; složitější příklad, kdy se do y vloží seznam ; obsahující jako své prvky další podseznamy user=> (fce2 1 '(2 3) '(4 5) '(6 7)) x = 1 y = ((2 3) (4 5) (6 7)) nil user=>
Ve skutečnosti ovšem můžeme zajít ještě dále a vytvořit funkci akceptující skutečně úplně libovolný počet parametrů, včetně žádného parametru:
(defn fce3 [& x] (println "x = " x))
Opět si uveďme několik demonstračních příkladů:
; dva parametry - budou uloženy do seznamu ; na nějž bude navázán symbol x user=> (fce3 1 2) x = (1 2) nil ; dva parametry - budou uloženy do seznamu ; na nějž bude navázán symbol x user=> (fce3 "Hello" "world") x = (Hello world) nil ; tři parametry - budou uloženy do seznamu ; na nějž bude navázán symbol x user=> (fce3 "Don't" "panic" '!) x = (Don't panic !) nil ; pouze jediný parametr - seznam se však přesto vytvoří user=> (fce3 1) x = (1) nil ; pokud funkci nepředáme žádný parametr, ; bude x navázán na hodnotu nil!!! user=> (fce3) x = nil nil user=>
6. Vylepšené makro trace
Změna makra trace takovým způsobem, aby toto makro akceptovalo libovolný počet parametrů, přičemž první parametr bude jméno funkce, je poměrně snadné. Zatímco předchozí tvar makra byl tento:
(defmacro trace3 [vyraz] `(let [x# ~vyraz] (println '~vyraz "=" x#) x#))
…nyní upravíme makro tak, aby se všechny předávané parametry uložily do seznamu. Při vyhodnocení předaného „výrazu“ (nejedná se o skutečný výraz, přesněji řečeno nejde o korektní formu) přitom využijeme známého faktu, že seznam může být považován za volání funkce – jméno funkce je uloženo v prvním prvku seznamu, zbylé prvky pak představují parametry předané této funkci. Znak & má stejný význam při definici funkcí pomocí defn i při definici maker s využitím defmacro, tudíž je následující zápis korektní:
(defmacro trace4 [& params] `(let [x# ~params] (println '~params "=" x#) x#))
Vyzkoušejme si „vylepšenou“ funkci tohoto makra. Povšimněte si, že pokud je zapotřebí někam do kódu přidat generování trasovacích informací, postačuje na toto místo kódu vložit pouze symbol trace4. To znamená, že žádné závorky ani další znaky se již (až na jednu výjimku) nemusí používat:
; zde může dojít k překvapení user=> (trace4) nil = nil nil user=> (trace4 + 1 2) (+ 1 2) = 3 3 user=> (trace4 Math/random) (Math/random) = 0.5518909120897316 0.5518909120897316 user=> (trace4 apply * (range 1 6)) (apply * (range 1 6)) = 120 120 user=>
Zajímavá taktéž může být expanze tohoto makra, zejména v prvním případě, protože zde se namísto seznamu přiřadí symbolu params hodnota nil:
user=> (macroexpand-1 '(trace4)) (clojure.core/let [x__1__auto__ nil] (clojure.core/println (quote nil) "=" x__1_ _auto__) x__1__auto__) user=> (macroexpand-1 '(trace4 Math/random)) (clojure.core/let [x__1__auto__ (Math/random)] (clojure.core/println (quote (Mat h/random)) "=" x__1__auto__) x__1__auto__) user=> (macroexpand-1 '(trace4 apply * (range 1 6))) (clojure.core/let [x__1__auto__ (apply * (range 1 6))] (clojure.core/println (qu ote (apply * (range 1 6))) "=" x__1__auto__) x__1__auto__) user=>
Malý úkol pro čtenáře: makro trace4 ve skutečnosti nebude správně fungovat ve chvíli, kdy se mu předá například atom – celé číslo, pravdivostní hodnota atd. Zkuste analyzovat následující dvojici příkazů (volání makra a jeho expanzi) a navrhnout takovou úpravu makra, která tuto chybu eliminuje:
user=> (trace4 42) ClassCastException java.lang.Long cannot be cast to clojure.lang.IFn user/eval1 35 (NO_SOURCE_FILE:72) user=> (macroexpand-1 '(trace4 42)) (clojure.core/let [x__1__auto__ (42)] (clojure.core/println (quote (42)) "=" x__ 1__auto__) x__1__auto__) user=>
7. Vytvoření nového typu programové smyčky forloop s využitím maker
Nyní se pustíme do tvorby složitějšího makra, konkrétně do makra představujícího programovou smyčku, která tak vlastně rozšíří syntaxi a sémantiku programovacího jazyka Clojure o novou konstrukci. Postup při tvorbě tohoto makra je velmi detailně popsán v trojici článků „Clojure Macro Tutorial“ – odkaz na tyto články najdete na začátku deváté kapitoly (při prvním čtení je možná nejlepší články prostudovat v pořadí I.díl, III.díl a teprve nakonec II.díl). Nicméně se vraťme k naší programové smyčce. Potřebovali bychom vytvořit počítanou smyčku typu for, které by se předala trojice parametrů – název řídicí proměnné smyčky (počitadla), počáteční hodnota počitadla a koncová hodnota počitadla. Za parametry by mělo následovat tělo smyčky, v němž je možné využít řídicí proměnnou, popř. globální proměnné atd. Pro jednoduchost předpokládejme, že krok smyčky bude vždy roven jedné, i když ve skutečnosti nebude větším problémem naše makro rozšířit i o možnost zadání libovolného jiného kroku. Způsob volání tohoto makra by měl vypadat zhruba následovně:
(forloop [counter start end] code)
Makro by mělo na základě zadaných údajů vygenerovat tento kód, v němž je použita tail rekurze:
(loop [counter start] (when (<= counter end) code (recur (inc counter))))
Konkrétní příklad:
(forloop [i 1 100] (println i))
Kód vygenerovaný makrem forloop:
(loop [i 1] (when (<= i 100) (println i) (recur (inc i))))
Při tvorbě a ladění maker můžeme postupovat dvěma způsoby. První způsob spočívá v tom, že se nevytváří přímo makro pomocí defmacro, ale namísto toho se vytváří funkce sloužící jako generátor kódu. Vygenerovaný kód se buď pouze zobrazí, nebo je ho možné kdykoli vyhodnotit pomocí eval. Druhý způsob spočívá v přímém vytvoření makra a následně v použití funkce macroexpand-1 pro prohlédnutí expandovaného makra. Podle mě je první způsob výhodnější, proto ho také použijeme zde. Následující funkci, která je pojmenovaná forloop-generator, se musí předat vektor s trojicí hodnot [jméno-řídicí-proměnné počáteční-hodnota-proměnné koncová-hodnota-proměnné] a za tímto vektorem může následovat kód, který odpovídá tělu generované smyčky. Žádné nové techniky jsme v této funkci nepoužili, dokonce ani prozatím nepoužíváme reader makra „syntax-quote“ či „unquote“:
(defn forloop-generator [[variable start end] & code] (list 'loop [variable start] (concat (list 'when) (list (list '<= variable end)) code (list (list 'recur (list 'inc variable))))))
Nyní se můžeme podívat na kód generovaný touto funkcí, nesmíme však zapomenout na to, že jak jméno řídicí proměnné, tak i tělo smyčky je zapotřebí quotovat, protože (prozatím) nevoláme plnohodnotné makro, ale obyčejnou funkci:
user=> (forloop-generator '[i 1 10] '(println i)) (loop [i 1] (when (<= i 10) (println i) (recur (inc i)))) user=> (forloop-generator ['i 1 10] '(println i)) (loop [i 1] (when (<= i 10) (println i) (recur (inc i)))) user=>
S využitím eval nakonec můžeme vyzkoušet, zda lze generovaný kód vůbec spustit (vyhodnotit) a zda dostaneme očekávaný výsledek:
user=> (eval (forloop-generator ['i 1 10] '(println i))) 1 2 3 4 5 6 7 8 9 10 nil user=>
8. Úprava programové smyčky forloop
Pokusme se nyní nahradit nečitelné generování kódu s využitím quote, list a concat za generování s použitím šablony, tj. za pomoci reader maker „syntax-quote“ a „unquote“. Postup je velmi jednoduchý a už jsme si ho ukazovali v předchozích kapitolách – celý generovaný kód bude uvozen znakem zpětného apostrofu („syntax-quote“) a pouze tam, kde budeme potřebovat použít hodnotu nějakého symbolu použijeme znak tildy před jménem tohoto symbolu („unquote“). Druhá verze našeho generátoru kódu tedy bude vypadat následovně:
(defn forloop-generator2 [[variable start end] & code] `(loop [~variable ~start] (when (<= ~variable ~end) ~code (recur (inc ~variable)))))
Na první pohled prozatím není v tomto zápisu patrná žádná chyba, proto si generátor kódu vyzkoušíme:
user=> (forloop-generator2 '[i 1 10] '(println i)) (clojure.core/loop [i 1] (clojure.core/when (clojure.core/<= i 10) ((println i)) (recur (clojure.core/inc i)))) user=>
Vidíme, že jména symbolů byla rozšířena o jmenný prostor, což je dobře. Dále se v kódu správně nahradily části ~variable, ~start a ~end za i, 1 a 10, což je taktéž správně. Jediný problém však spočívá v tom, že tělo smyčky má okolo sebe ještě jedny kulaté závorky, což již korektní není. O tom, že se skutečně jedná o chybu, se můžeme snadno přesvědčit ve chvíli, kdy se pokusíme smyčku zavolat, tj. vyhodnotit:
user=< (eval (forloop-generator2 '[i 1 10] '(println i))) 1 NullPointerException user/eval36 (NO_SOURCE_FILE:26) user=>
Jak lze této chybě zabránit? Quotování použité při předávání těla smyčky do generátoru kódu nemůžeme odstranit, protože by se interpret snažil vyhodnotit (println i) ještě před spuštěním funkce forloop-generator2. Druhé závorky se vytvořily proto, že symboly tvořící tělo smyčky jsou předávány formou nepovinných parametrů, které se vždy ukládají do seznamu. Aby se tato chyba napravila, musíme být schopni nějak zploštit seznam představující tělo smyčky, tj. získat z tohoto seznamu pouze jeho prvky. To je operace, kterou provádí poslední reader makro nazvané „unquote-splicing“, které se zapisuje dvojicí znaků ~@. Třetí verze generátoru smyčky bude vypadat následovně:
(defn forloop-generator3 [[variable start end] & code] `(loop [~variable ~start] (when (<= ~variable ~end) ~@code (recur (inc ~variable)))))
Nezbývá než tuto verzi smyčky otestovat:
user=> (forloop-generator3 '[i 1 10] '(println i)) (clojure.core/loop [i 1] (clojure.core/when (clojure.core/<= i 10) (println i) ( recur (clojure.core/inc i)))) user=> (eval (forloop-generator3 '[i 1 10] '(println i))) 1 2 3 4 5 6 7 8 9 10 nil user=>
Chování se zdá být v pořádku, takže můžeme přistoupit k vytvoření makra, což je jednoduché, protože prostě nahradíme defn za defmacro:
(defmacro forloop [[variable start end] & code] `(loop [~variable ~start] (when (<= ~variable ~end) ~@code (recur (inc ~variable)))))
A ihned můžeme toto makro odzkoušet – tentokrát již samozřejmě není zapotřebí (a ani to není korektní) quotovat předávaný vektor ani tělo smyčky!
user=> (forloop [x 1 10] (println x)) 1 2 3 4 5 6 7 8 9 10 nil user=> (forloop [x 1 10] (println (* x x))) 1 4 9 16 25 36 49 64 81 100 nil user=> ; finito
Zbývá nám pouze dořešit jediný zbývající problém, což je druhý úkol pro čtenáře. Makro je vhodné upravit takovým způsobem, aby se koncová hodnota počitadla vyhodnocovala pouze jedenkrát a nikoli na začátku každé iterace. V našich demonstračních příkladech jsme jako koncovou hodnotu používali celé číslo, ovšem kdyby se jednalo o volání funkce s vedlejším efektem, vznikl by problém. Řešení této chyby není ve skutečnosti těžké – postačuje použít lokální generovaný symbol naplněný ve speciální formě let, která bude celou smyčku „obalovat“ – viz též poslední verzi makra trace, kde se používá podobné řešení.
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