Hlavní navigace

Programovací jazyk Clojure 15: tvorba uživatelských maker

Pavel Tišnovský

Dnes budeme společně pokračovat v popisu makrosystému, který tvoří nedílnou součást jazyka Clojure. Minule jsme si popsali makra objektu Reader, především makra nazvaná quote, syntax-quote, unquote a unquote-splicing, která využijeme dnes při tvorbě skutečných uživatelských maker.

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

9. Odkazy na Internetu

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

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

18. 9. 2012 19:59

V CL makrech se pouzivaji trosku jine znaky, backquote ma zhruba stejny vyznam (az na jmenne prostory v Closure), ale namisto ~ a ~@ se pouziva carka (vypada to trosku divne) a ,@

Vyznam # je zhruba shodny, pokud tedy muzu posoudit - az na to, ze Reader makra lze v CL menit.

18. 9. 2012 18:19

Goheeca (neregistrovaný)

Důležité je nejdřív si uvědomit, že lispová makra získávají lispový kód jako data a mají k dispozici celý CL k tomu, aby ty data (kód) přetvořily na kód, který je následně vyhodnocován. A pak bych odkázal třeba na anaforická makra, kde je na alambdě pěkně vidět jak to funguje (máme anonymní funkci, v které chceme používat rekurzi, ovšem samotná lambda to neumožnuje, tak to vylepšíme ...).
Pro trknutí:

(defmacro print-me ((&whole w &rest r))
  (print w)
  r)

Použití:


Podnikatel.cz: Chtějte údaje k dani z nemovitostí do mailu

Chtějte údaje k dani z nemovitostí do mailu

Podnikatel.cz: K EET. Štamgast už peníze na stole nenechá

K EET. Štamgast už peníze na stole nenechá

Podnikatel.cz: V restauraci bez cigaret? Sněmovna kývla

V restauraci bez cigaret? Sněmovna kývla

Vitalia.cz: Taky věříte na pravidlo 5 sekund?

Taky věříte na pravidlo 5 sekund?

Lupa.cz: Propustili je z Avastu, už po nich sahá ESET

Propustili je z Avastu, už po nich sahá ESET

Měšec.cz: Jak levně odeslat balík přímo z domu?

Jak levně odeslat balík přímo z domu?

Měšec.cz: U levneELEKTRO.cz už reklamaci nevyřídíte

U levneELEKTRO.cz už reklamaci nevyřídíte

Vitalia.cz: Jsou čajové sáčky toxické?

Jsou čajové sáčky toxické?

Vitalia.cz: Láska na vozíku: Přitažliví jsme pro tzv. pečovatelky

Láska na vozíku: Přitažliví jsme pro tzv. pečovatelky

120na80.cz: Pánové, pečujte o svoje přirození a prostatu

Pánové, pečujte o svoje přirození a prostatu

Podnikatel.cz: Udávání a účtenková loterie, hloupá komedie

Udávání a účtenková loterie, hloupá komedie

Měšec.cz: mBank cenzuruje, zrušila mFórum

mBank cenzuruje, zrušila mFórum

Podnikatel.cz: Chaos u EET pokračuje. Jsou tu další návrhy

Chaos u EET pokračuje. Jsou tu další návrhy

120na80.cz: Na ucho teplý, nebo studený obklad?

Na ucho teplý, nebo studený obklad?

Vitalia.cz: Když přijdete o oko, přijdete na rok o řidičák

Když přijdete o oko, přijdete na rok o řidičák

Podnikatel.cz: Udávání kvůli EET začalo

Udávání kvůli EET začalo

Vitalia.cz: Mondelez stahuje rizikovou čokoládu Milka

Mondelez stahuje rizikovou čokoládu Milka

Měšec.cz: Zdravotní a sociální pojištění 2017: Připlatíte

Zdravotní a sociální pojištění 2017: Připlatíte

Vitalia.cz: Paštiky plné masa ho zatím neuživí

Paštiky plné masa ho zatím neuživí

Vitalia.cz: Pamlsková vyhláška bude platit jen na základkách

Pamlsková vyhláška bude platit jen na základkách