Makra v Racketu i v dalších lispovských jazycích

3. 9. 2019
Doba čtení: 50 minut

Sdílet

Většina jazyků odvozených od Lispu nabízí systém pro tvorbu a používání maker. Jazyk Racket není výjimkou, jeho makrosystém je třeba oproti Common Lispu vylepšen, kvůli zajištění korektnosti a konzistence maker.

Obsah

1. Role maker v lispovských programovacích jazycích

2. Makra v Common Lispu

3. Vytvoření makra v Common Lispu

4. Výpis expandovaného makra, řešení dvojího vyhodnocení parametrů

5. Typické Common Lispovské makro „loop“: operace nad seznamy

6. Další příklady použití knihovního makra „loop“: počítané smyčky

7. Základy práce se systémem maker v programovacím jazyku Clojure

8. První písmeno ve zkratce REPL: objekt Reader a jeho makra

9. Makra „quote“ a „syntax-quote“

10. Makra „unquote“ a „unquote-splicing“

11. Makra v programovacím jazyku Racket

12. Vytvoření makra s využitím define-syntax-rule

13. Syntaktické objekty, použití syntax->datum

14. Makro pro prohození obsahu dvou proměnných

15. Makro s volitelnou aritou

16. Použití … v makru

17. Původní (nehygienická) makra v Racketu

18. Repositář s demonstračními příklady

19. Literatura

20. Odkazy na Internetu

1. Role maker v lispovských programovacích jazycích

Jednou z nejzajímavějších vlastností většiny klasických lispovských jazyků je možnost tvorby maker. Vzhledem k tomu, že aplikace naprogramované v LISPu (například v Common Lispu), Scheme či v některém dalším lispovském jazyce (Clojure) jsou tvořeny, stejně jako data, s využitím rekurzivně vnořených seznamů, jsou makra v LISPu založena na manipulaci se seznamy tvořícími program, což je velký rozdíl například oproti makrům implementovaným v céčkovém preprocesoru, kde se jedná o poměrně jednoduché textové záměny (ostatně samotný preprocesor je většinou zcela oddělený od překladače). Vzhledem k tomu, že lispovská makra dokážou manipulovat s vlastním programem (resp. s jeho uloženou podobou), je možné pomocí nich vytvářet například úplně nové jazykové konstrukce (různé smyčky, podmíněné příkazy, částečně vyhodnocované formy atd.) s vlastní syntaxí, což je poměrně unikátní vlastnost, kterou u většiny dalších programovacích jazyků nenajdeme.

Poznámka: existují samozřejmě i další jazyky vybavené pokročilým makrosystémem. Na stránkách Rootu jsme se seznámili například s následujícími jazyky a jejich koncepty maker:
  1. Práce s makry v programovacím jazyku Rust
    https://www.root.cz/clanky/prace-s-makry-v-programovacim-jazyku-rust/
  2. Programovací jazyk Julia: metaprogramování, makra a AST
    https://www.root.cz/clanky/pro­gramovaci-jazyk-julia-metaprogramovani-makra-a-ast/

Způsob definice maker se v některých ohledech podobá definici funkcí, ale mezi funkcemi a makry existuje jeden zásadní rozdíl.

Funkce vytvořené v LISPu/Scheme/Clojure získávají jako svoje argumenty hodnoty, tj. většinou atomy, (anonymní) funkce nebo seznamy, a vrací taktéž nějakou hodnotu – opět se může jednat o atom, (anonymní) funkci nebo seznam. Funkce jsou vyhodnocovány (volány) až při spuštění programu a jejich argumenty jsou vyhodnocovány dříve, než se funkce zavolá. Makra ovšem jako svůj vstup získávají lispovský kód (zapsaný formou rekurzivně zanořeného seznamu) a vrací taktéž lispovský kód, což nepředstavuje oproti funkcím žádný zásadnější rozdíl. Ovšem na rozdíl od funkcí jsou makra volána již při prvotním zpracovávání programu, podobně jako jsou céčková makra zpracovávána céčkovým preprocesorem (cpp) ještě před vlastní kompilací. Teprve výsledek volání makra (nazývaný taktéž expanze makra) je považován za zápis výrazu, který může být dále zpracován, tj. buď vyhodnocen (interpretační varianty LISPu) nebo zkompilován (varianty LISPu vybavené překladačem). Poznamenejme ještě, že v těle makra se může vyskytovat volání dalšího makra, což znamená, že LISP musí při expanzi maker použít rekurzi (tuto rekurzi lze v případě potřeby zakázat).

Poznámka: různé varianty LISPu používají i různý přístup ke tvorbě maker. V dnešním článku si nejprve (čistě pro porovnání a pro zajištění kontextu) si nejprve ve stručnosti řekneme, jak je tato problematika řešena v Common Lispu a posléze i, jak jsou makra implementována v programovacím jazyce Clojure. Druhá část článku je pochopitelně věnována jazyku Racket, jehož systém maker je poněkud odlišný.

Rozdíly mezi makry a funkcemi můžeme ve stručnosti shrnout takto:

Funkce Makro
volána v runtime voláno (expandováno) v compile/eval time
na vstupu akceptuje libovolné hodnoty akceptuje „syntaktické objekty“
na výstupu může vracet libovolnou hodnotu výstupem je opět syntaktický objekt (expandované makro)
používá se pro implementaci knihoven i vlastních algoritmů používá se pro rozšíření a úpravu sémantiky (DSL)

2. Makra v Common Lispu

Nejprve si ve stručnosti ukažme klasický makrosystém použitý v Common Lispu. Při zápisu maker se poměrně často používají znaky ` (zpětný apostrof), , (čárka) a @ (zavináč). Zpětný apostrof se zapisuje před seznam, kde má podobný význam jako běžný apostrof, který, jak již víme, zabraňuje tomu, aby byl seznam vyhodnocen (jedná se o zkrácený zápis speciální formy quote). V případě zpětného apostrofu je taktéž zabráněno vyhodnocení seznamu, který se za apostrofem nachází, ovšem navíc lze uvnitř takového seznamu použít symbol čárka, který označuje ty položky seznamu, které se naopak vyhodnotit mají – díky tomu je možné přesně řídit, jak se má seznam vyhodnotit, a to až na úroveň jeho jednotlivých prvků. Symbol zavináče zapsaný (společně s čárkou) před nějakou položku seznamu taktéž vede k vyhodnocení této položky, ale odlišným způsobem – položka (kterou může být například další seznam) se přímo vloží do seznamu uvozeného zpětným apostrofem.

Zní to celé složitě? Nejlepší bude, když si způsob použití všech tří symbolů ukážeme na jednoduchých příkladech:

; vytvoříme si dvě proměnné x a y obsahující hodnoty 1 a 2
(setq x 1)
1
(setq y 2)
2
 
; pokus o vyhodnocení seznamu (x y) musí skončit chybou, protože
; x není jméno žádné funkce ale proměnné obsahující atom
(x y)
*** - EVAL: undefined function X
 
; použití normálního apostrofu zabraňuje vyhodnocení seznamu
; (speciální forma quote)
'(x y)
(X Y)
 
; použití zpětného apostrofu taktéž zabraňuje vyhodnocení seznamu
`(x y)
(X Y)
 
; zabráníme vyhodnocení celého seznamu, ale naopak si vynutíme vyhodnocení y
; (y se tedy nahradí svou hodnotou)
`(x ,y)
(X 2)
 
; zabráníme vyhodnocení celého seznamu, ale naopak si vynutíme vyhodnocení x
; (x se tedy nahradí svou hodnotou)
`(,x y)
(1 Y)
 
; seznam jako celek vyhodnocen není (ty by vedlo k chybě - viz výše)
; ale jsou vyhodnoceny obě proměnné x a y
`(,x ,y)
(1 2)

Vidíme tedy, že vyhodnocování seznamu a jeho položek můžeme velmi přesně řídit a obejít tak standardní pravidla. Použití zavináče je ovšem již poněkud komplikovanější:

; proměnná z obsahuje seznam se třemi symboly
(setq z '(www root cz))
(WWW ROOT CZ)
 
; zabráníme vyhodnocení seznamu obsahujícího z (tedy další seznam)
`(1 2 3 z 4 5 6)
(1 2 3 Z 4 5 6)
 
; seznam jako celek není vyhodnocen, ale proměnná z ano
; (před prvkem z je čárka, celý seznam je uvozen zpětným apostrofem)
`(1 2 3 ,z 4 5 6)
(1 2 3 (WWW ROOT CZ) 4 5 6)
 
; podobné předchozímu výrazu, ovšem s tím rozdílem, že je seznam uložený
; v proměnné z "rozpuštěn" (zmizí závorky okolo trojice symbolů
; www root cz)
`(1 2 3 ,@z 4 5 6)
(1 2 3 WWW ROOT CZ 4 5 6)
Poznámka: prozatím se může zdát, že symboly , ` a @ mají jen minimální význam. Důležité ovšem je, že jejich zpracování se provádí před spuštěním programu (nikoli v runtime), takže je lze použít pro tvorbu a expanzi maker, což ostatně uvidíme hned v navazující kapitole.

3. Vytvoření makra v Common Lispu

Nyní si konečně můžeme nějaké jednoduché makro ukázat. Namísto formy defun se v Common Lispu při tvorbě maker používá speciální forma pojmenovaná defmacro. Můžeme se tedy pokusit vytvořit makro, které nahradí svůj parametr výrazem, jenž vypočítá druhou mocninu tohoto parametru:

(defmacro Square (x)
  `(* ,x ,x))

Toto makro funguje následujícím způsobem: při prvotním načítání LISPovských výrazů (forem) se jakýkoli výskyt (Square cokoli) nahradí formou (* cokoli cokoli) (zde je pěkně vidět, z jakého důvodu existují symboly ` a ,). Teprve tato forma je předána interpretru nebo překladači pro další zpracování, například přímému vyhodnocení. Když je LISPovský program překládán, tak se přeloží až výsledek aplikace makra, což je opět shodné se způsobem překladu céčkového programu.

Makro si můžeme ihned vyzkoušet:

(Square 42)
1764
 
(Square (+ 1 2))
9
Poznámka: toto makro je tak jednoduché, že neukazuje celou sílu makrosystému Common Lispu. Ostatně podobného výsledku dosáhneme i v C/C++, například takto:
#define Square(x) ((x)*(x))

Kvůli sémanticky omezenému makrosystému C je nutné symbol x správně uzávorkovat.

Poznámka: obě makra, tedy jak LISPovské, tak i céčkovské, se ovšem budou chovat nekorektně, jak si to ukážeme v další kapitole.

4. Výpis expandovaného makra, řešení dvojího vyhodnocení parametrů

Při tvorbě maker se může (a to velmi snadno a často :-) stát, že makro kvůli nějaké chybě neprovádí přesně tu činnost, jakou programátor zamýšlel. Zatímco funkce jsou volány až v době běhu programu, tj. je možné do jejich těla vkládat různé ladicí příkazy (výpis hodnot, logování, aserce) či dokonce běh programu trasovat, u maker to není možné, protože jsou expandována již při překladu programu.

Ovšem v Common Lispu je možné si pomocí zabudované funkce macroexpand nebo macroexpand-1 zobrazit výpis makra po expanzi, což může být velmi užitečné (podobně je možné si nechat zobrazit výsledek činnosti preprocesoru programovacího jazyka C). Zatímco funkce macroexpand zobrazí plně expandované makro (tj. výsledný LISPovský kód po expanzi všech maker, a to i těch vnořených), je po zavolání makra macroexpand-1 zobrazeno makro pouze po první expanzi, což znamená, že uvnitř expandovaného kódu se mohou vyskytovat i volání dalších maker. V praxi se při ladění maker častěji používá právě funkce macroexpand-1, jejíž použití je velmi jednoduché, což si ostatně můžeme snadno ověřit:

; nejprve znovu vytvoříme makro nazvané Square
(defmacro Square (x)
  `(* ,x ,x))
 
; expanze makra při volání funkce Square s parametrem jenž je atomem
(macroexpand-1 '(Square 42))
(* 42 42) ;
 
; expanze makra při volání funkce Square s parametrem jenž je formou
(macroexpand-1 '(Square (+ 1 2)))
(* (+ 1 2) (+ 1 2)) ;
 
; makro se expanduje i v případě, že se použije neznámý symbol "plus"
(macroexpand-1 '(Square (plus 1 2)))
(* (PLUS 1 2) (PLUS 1 2)) ;

Až při pohledu na expandované makro nás může napadnout, že vlastně nepracuje dokonale, v některých případech dokonce pracuje chybně. Problém spočívá v tom, že se předaný parametr po expanzi makra vyhodnocuje dvakrát, což samozřejmě není větší problém v případě, že se předává/vyhodnocuje atomická hodnota (číslo) nebo jednoduchý výraz, ovšem kdyby se jednalo například o funkci načítající hodnotu z databáze nebo ze souboru (popř. ze standardního vstupu), bylo by toto načítání prováděno dvakrát a ne jedenkrát, jak by každý programátor při pohledu na program volající makro očekával. Můžeme si to ostatně vyzkoušet (funkce read provádí načtení výrazu ze standardního vstupu):

(Square (read))
2 ; zadáno uživatelem z klávesnice
3 ; zadáno (druhá a jiná! hodnota) taktéž z klávesnice
6 ; výsledek volání makra = 2*3

Makro se můžeme pokusit „opravit“ například tak, že se použije pomocná lokální proměnná temp:

(defmacro Square2 (x)
    `(let ((temp ,x)) (* temp temp)))
 
; podíváme se na expanzi při jeho volání
; s parametrem (+ 1 2)
(macroexpand-1 '(Square2 (+ 1 2)))
(LET ((TEMP (+ 1 2))) (* TEMP TEMP)) ;
; vidíme, že se výraz (+ 1 2) vyhodnocuje pouze jedenkrát
 
; otestujeme volání makra při předání funkce read
(Square2 (read))
2 ; zadáno uživatelem z klávesnice
4 ; správný výsledek a navíc program vyžadoval pouze jediný vstup z klávesnice

Pomocí macroexpand-1 je možné prozkoumat i makra dostupná v základní knihovně jazyka:

; makra or a and jsou implementovány tak, aby podporovaly
; zkrácené vyhodnocení logických výrazů
(macroexpand-1 '(or foo bar))
(COND (FOO) (T BAR)) ;
 
(macroexpand-1 '(and (foo t) (foo nil)))
(COND ((NOT (FOO T)) NIL) (T (FOO NIL))) ;
Poznámka: mohlo by se zdát, že jsme všechny problémy makra vyřešili. Ovšem u složitějších maker musíme zajistit ještě jednu „maličkost“ – interní lokální proměnné vytvořené při expanzi makra by měly mít unikátní jméno, aby se náhodou nestalo, že makro bude voláno s parametry, které se budou jmenovat stejně, jako lokální proměnné makra. Tento problém je v Racketu (popsaného ve druhé částí článku) řešen elegantněji.

5. Typické Common Lispovské makro „loop“: operace nad seznamy

Jedním z nejužitečnějších maker, které se nachází ve standardní knihovně Common Lispu, je makro nazvané prozaicky loop, jenž je doplněné o několik dalších pomocných maker a symbolů (ty jsou použity pro doplnění syntaxe o další „klíčová slova“).

S využitím makra loop lze v LISPu (tj. jazyku založeném částečně na funkcionálním paradigmatu, který původně vedl uživatele k používání rekurze namísto psaní programových smyček) realizovat značné množství různých typů programových smyček, například smyčky počítané (s možností změny kroku, o který se změní hodnota čítače či čítačů smyček při každé iteraci), smyčky s podmínkou (odpovídá například Pascalovským smyčkám typu while-do a repeat-until) či iterační smyčky, která v každé iteraci operuje nad prvky seznamů, polí či asociativních polí (for-each). Na příkladu makra loop je pěkně a názorně ukázána jedna z největších výhod LISPovských maker – právě pomocí maker a některých dalších vlastností LISPu je možné v případě potřeby vytvořit zcela nový jazyk s vlastní syntaxí (doménově specifický jazyk).

Na následujících demonstračních příkladech si ukážeme některé možnosti, které makro loop programátorům nabízí:

Smyčka, která postupně iteruje nad všemi elementy uloženými v seznamu (tj. postupně všemi prvky prochází):

(loop for i in '(a b c d) do (print i))
 
A
B
C
D
Poznámka: samotné makro loop vrací jako svoji návratovou hodnotu (většinou) atom NIL, který ve výpisech pro větší přehlednost neuvádím. Pokud si příklady budete sami spouštět v interpretru Common Lispu, bude po skončení smyčky na standardní výstup navíc vypsán řádek obsahující právě NIL.

Pokud se ve zpracovávaném seznamu nachází další rekurzivně vnořené seznamy, jsou při iterování seznamem chápány jako jeden prvek:

(loop for i in '(a (b c) d) do (print i))
 
A
(B C)
D

Programová smyčka, která postupně iteruje (prochází) přes CDR zvoleného seznamu. V první iteraci je do proměnné i přiřazen celý seznam s, ve druhé iteraci (cdr s), ve třetí iteraci (cdr (cdr s)) atd.:

(loop for i on '(a b c d) do (print i))
 
(A B C D)
(A B C)
(B C)
(C)

Zpracování seznamu obsahujícího ve druhém prvku podseznam:

(loop for i on '(a (b c) d) do (print i))
 
(A (B C) D)
((B C) D)
(D)

Iterace nad dvojicí seznamů a postupná konstrukce seznamu obsahujícího dvojice prvků ze seznamu prvního a druhého. Výsledek není v tomto případě tisknut, ale je vrácen jako návratová hodnota smyčky, tj. lze ho například přiřadit proměnné:

(loop for x in '(a b c d e)
      for y in '(1 2 3 4 5)
      collect (list x y))
 
((A 1) (B 2) (C 3) (D 4) (E 5))

Přiřazení výsledného seznamu vytvořeného smyčkou do proměnné:

(setq a (loop for x in '(a b c d e)
              for y in '(1 2 3 4 5)
              collect (list x y)))
 
((A 1) (B 2) (C 3) (D 4) (E 5))
 
; tisk hodnoty proměnné a
a
((A 1) (B 2) (C 3) (D 4) (E 5))

6. Další příklady použití knihovního makra „loop“: počítané smyčky

V předchozím textu jsme si řekli, že makro loop je možné použít mj. i pro tvorbu takzvaných počítaných smyček, tj. takových smyček, které v každé iteraci zvyšují nebo naopak snižují hodnotu čítače (lokální proměnné platné v rámci smyčky). Syntaxe, kterou makro loop pro tento typ smyček používá, je v mnoha ohledech podobná syntaxi Pascalu či některých verzí starobylého Basicu, jak se ostatně můžete sami přesvědčit na následujících demonstračních příkladech.

Nejprve je uveden základní tvar počítané smyčky se zadáním horní a dolní meze čítače. Hodnota čítače se v tomto případě v každé iteraci zvětšuje o jedničku, jak je to ostatně u počítaných smyček běžné:

(loop for i from 1 to 10 do (print i))
 
1
2
3
4
5
6
7
8
9
10

Počítaná smyčka s čítačem, jehož hodnota se v každé iteraci zmenšuje. Povšimněte si použití slova downto, které se vyskytuje například i v Pascalu:

(loop for i from 10 downto 1 do (print i))
 
10
9
8
7
6
5
4
3
2
1

U počítaných smyček lze měnit krok, tj. hodnotu, o kterou se čítač smyčky v každé iteraci zvětší nebo naopak zmenší. Zde se použije slovo by:

(loop for i from 1 to 10 by 1.5 do (print i))
 
1
2.5
4.0
5.5
7.0
8.5
10.0

V mnoha implementacích programovacího jazyka LISP je podporován i numerický datový typ „zlomek“, což je racionální číslo vyjádřené čitatelem a jmenovatelem odděleným znakem / (lomítko). V počítaných smyčkách lze samozřejmě zlomky využívat, jak je to patrné z následujících dvou příkladů:

(loop for i from 0 to 10 by 3/2 do (print i))
 
0
3/2
3
9/2
6
15/2
9
 
(loop for i from 10 downto 0 by 4/3 do (print i))
 
10
26/3
22/3
6
14/3
10/3
2
2/3

Hodnoty čítače je možné v případě potřeby omezit prakticky libovolnou podmínkou. V následujícím příkladu je použit predikát evenp, který vrací hodnotu T (pravda) v tom případě, kdy je parametr tohoto predikátu sudé číslo:

(loop for i from 1 to 10 when (evenp i) do (print i))
 
2
4
6
8
10

Hodnoty, kterých postupně nabývá čítač smyčky, lze mít uloženy v seznamu (i když tento příklad je poněkud umělý, protože lze napsat jednodušším způsobem):

(loop with a = '(1 42 3) for i in a do (print i))
 
1
42
3

Na závěr si ukážeme způsob zápisu programové smyčky, která současně prochází všemi prvky seznamu a navíc mění hodnotu čítače. Právě tento typ smyčky mnohdy citelně chybí v ostatních programovacích jazycích, které nabízí buď striktně počítanou smyčku nebo smyčku typu for-each:

(loop for x in '(a b c d e)
      for y from 1 do
      (format t "~s = ~s~%" y x))
 
1 = A
2 = B
3 = C
4 = D
5 = E
Poznámka: funkce format se v mnoha ohledech podobá například céčkové funkci printf. Prvním parametrem této funkce je výstupní proud, do kterého se má výsledek tisknout (s tou výjimkou, že nil značí, že se výsledek nikam netiskne, ale je navrácen ve formě řetězce, a T naopak odpovídá standardnímu výstupu), druhým parametrem je formátovací řetězec a další parametry jsou postupně tisknuty na výstup podle pravidel zadaných ve formátovacím řetězci. Ve výše uvedeném příkladu jsou použita dvě formátovací pravidla: ~s = tisk hodnoty, ~% = odřádkování.

7. Základy práce se systémem maker v programovacím jazyku Clojure

V této části článku si ukážeme základní koncepty práce s makry v jazyku Clojure. Základní vlastnosti samotného interpretu jazyka Clojure jsou přitom odvozeny od interpretrů používaných ve většině variant programovacího jazyka LISP, což znamená, že autoři Clojure (resp. přesněji řečeno především jeho původní a dodnes pravděpodobně nejaktivnější autor Rich Hickley) vychází z ověřených technologií, které byly poprvé implementovány již před více než padesáti roky v rámci vývoje LISPu a na něj navazujících jazyků (Scheme). Základem interpretru programovacího jazyka Clojure je, stejně jako v LISPu, smyčka nazývaná REPL (Read-Evaluate-Print-Loop), jejíž název vyplývá z toho, že mohla být relativně jednoduše implementována způsobem ukázaným pod tímto odstavcem. Ostatně z historického pohledu je zajímavé, že nějak podobně vlastně LISP vznikl, když si jeho autor (John McCarthy) uvědomil, že na základě implementace rekurzivní podoby funkce eval a několika dalších pomocných funkcí dokáže vytvořit plnohodnotný programovací jazyk (viz též úvodní článek tohoto seriálu):

(loop (print (eval (read))))

Není bez zajímavosti, že s AST se v LISP/Clojure může manipulovat za použití stejných mechanismů (funkcí/forem/maker), které se používají i při běžném programování – jinými slovy to znamená, že jazyk maker je stále jazykem, v němž se zapisují programy (na rozdíl od zmíněného céčka a C++, kde je jazyk maker zcela odlišný).

8. První písmeno ve zkratce REPL: objekt Reader a jeho makra

Před popisem systému maker v programovacím jazyku Clojure si ještě musíme říci, že ve skutečnosti existují dva typy maker – takzvaná reader macros, neboli makra zabudovaná přímo do objektu/modulu, který načítá formy ze standardního vstupu a potom běžná makra známá i z dalších lispovských programovacích jazyků.

Nejprve se budeme zabývat makry používanými při načítání forem ze standardního vstupu. Důvodů, proč je lepší začít s popisem této skupiny maker je více, například fakt, že tato makra nelze vytvářet a existující makra nelze modifikovat (na rozdíl od výše zmíněného Common Lispu, kde to možné je) a taktéž to, že se tato makra používají v prakticky všech zdrojových kódech, aniž by si vývojáři většinou uvědomovali, že ve svých programech vůbec nějaká makra používají :-) Důvod existence reader maker je jednoduchý – umožňují zkrácení zápisu programů, zajišťují možnost zápisu komentářů (ty totiž nejsou považovány za běžné formy, protože nevrací žádnou hodnotu, ani nil) a taktéž je možné s pomocí tohoto typu maker přidávat k symbolům, seznamům, vektorům, mapám atd. takzvaná metadata.

Zjednodušeně řečeno je možné říci, že reader makra pracují podobně jako preprocesor v programovacích jazycích C a C++, protože text zapisovaný či posílaný na standardní vstup je nejprve těmito makry zpracován a posléze je – stále v textové podobě – poslán funkci read pro parsing a vyhodnocení. Tato makra tedy slouží pro provádění „pouhých“ textových substitucí a nikoli k modifikaci AST, jak je tomu u běžných maker. Která reader makra jsou v programovacím jazyku Clojure podporována, nám prozradí následující tabulka, z níž je patrné, že některá makra provádí skutečně značně jednoduchou činnost, například pouhé odstranění komentářů:

# 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

S popisem maker vestavěných do objektu Reader začneme jen pozvolna, napřed si totiž popíšeme ta nejjednodušší makra. V jazyku Clojure je nejjednodušším reader makrem s velkou pravděpodobností makro nazvané „comment“ zapisované pomocí znaku ; (středník). Veškerý text, který je zapsaný mezi středníkem a koncem řádku je ignorován, takže toto makro lze použít pro jednoduchý zápis jednořádkových komentářů. Pokud by toto reader makro neexistovalo, muselo by se pro zápis komentářů namísto toho používat normální makro comment, které se však musí zapisovat stejně, jako jakákoli jiná forma, tj. i s kulatými závorkami, což na čitelnosti komentářů určitě nepřidá. Nicméně makro comment má svou nezastupitelnou úlohu, protože pomocí něho můžeme do komentáře „uzavřít“ i delší část kódu – ostatně toto makro je použito i v samotných zdrojových kódech jazyka Clojure.

Podívejme se nyní na několik jednoduchých demonstračních příkladů:

; jednořádkové komentáře
user=> ; toto je komentar
user=> ; ignorovany v REPL\
user=>
 
; víceřádkový komentář
user=> (comment
komentar
muze
mit
nekolik
radku)
; povšimněte si, že (comment) vrací hodnotu nil
nil
 
user=>
Poznámka: zápis user=> představuje standardní výzvu (prompt) interpretru programovacího jazyka Clojure.

Další velmi často používané reader makro se zapisuje s využitím znaku \ (zpětné lomítko). Toto makro slouží pro zápis znakových konstant (literálů) do zdrojového kódu. Tisknutelné znaky je možné zapsat buď přímo za zpětné lomítko, nebo je možné použít kód libovolného znaku z×Unicode, přičemž kód tohoto znaku musí být zapsán v hexadecimální soustavě za dvojici znaků „\u“:

; běžný tisknutelný znak:
user=> \a
\a
 
; další běžný tisknutelný znak:
user=> \1
\1
 
; znak "u":
user=> \u
\u
; znak s hexadecimální hodnotou 0x40, neboli 64:
 
user=> \u0040
\@
 
; pokus o zápis neplatného znakového literálu:
user=> \aa
RuntimeException Unsupported character: \aa  clojure.lang.Util.runtimeException (Util.java:170)
 
user=>

Posledním „jednoduchým“ reader makrem je makro zapisované s využitím znaku ^. Toto makro slouží k přiřazení metadat k symbolu, seznamu, vektoru, množině či mapě. Základní způsob využití makra ^ je následující:

; nastavení metadat k vektoru
user=> ^{:atribut1 "Hodnota1" :atribut2 "Hodnota2"} [1 2 3]
[1 2 3]
 
; uložení vektoru a k němu přiřazeným metadatům
; do proměnné vektor
user=> (def vektor ^{:atribut1 "Hodnota1" :atribut2 "Hodnota2"} [1 2 3])
#'user/vektor
 
; přečtení stavu proměnné vektor
user=> vektor
[1 2 3]
 
; přečtení metadat přiřazených k proměnné vektor
user=> (meta vektor)
{:atribut2 "Hodnota2", :atribut1 "Hodnota1"}
 
user=>

Navíc existuje i alternativní způsob použití makra ^, který slouží pro naplnění jediného atributu s názvem :tag:

; uložení vektoru a k němu přiřazeným metadatům
; do proměnné vektor
user=> (def vektor ^"xyzzy" [1 2 3])
#'user/vektor
 
; přečtení stavu proměnné vektor
user=> vektor
[1 2 3]
 
; přečtení metadat přiřazených k proměnné vektor
user=> (meta vektor)
{:tag "xyzzy"}
 
user=>

Makro „deref“ má již složitější chování. Může být použito pro přečtení hodnoty, přesněji řečeno stavu reference a v programovacím jazyku Clojure se používá u mnoha typů referencí (referenčních typů):

# Referenční typ
1 @ref
2 @agent
3 @var
4 @atom
5 @delay
6 @future
7 @promise
Poznámka: v jazyku Clojure se nepoužívají proměnné v běžném smyslu tohoto slova. Namísto proměnných se používají právě různé typy referencí.

Chování makra @ se však u různých typů referencí liší. Zatímco @ref, @var či @atom pouze vrátí aktuální stav reference, v případě použití @future, @promise či @agent se ve skutečnosti musí počkat na dokončení (asynchronního) výpočtu, který běží v jiném vláknu. Pro jistotu si připomeňme tabulku s funkcemi a makry použitými při práci s nejdůležitějšími referenčními typy:

# Typ Var Ref Atom Agent
1 Vytvoření (def name value) (ref value) (atom value) (agent value)
2 Nastavení hodnoty (set! name value) (ref-set ref value) (reset! atom value) ×
3 Aplikace funkce × (alter ref funkce) (swap! atom funkce) (send agent funkce)
4 Čtení hodnoty name @ref @atom @agent

Demonstrační příklad na použití referencí typu ref, tedy „normálních“ proměnných platných v rámci aktuálního jmenného prostoru (ve skutečnosti je s referencemi typu ref možné provádět mnoho operací, které by s běžnými proměnnými nebyly možné):

; vytvoření refu
user=> (def my-ref (ref 42))
#'user/my-ref
 
; vytvoření refu
user=> (def string-ref (ref "Hello world"))
#'user/string-ref
 
; přečtení refu pomocí makra @
user=> @my-ref
42
 
; přečtení refu pomocí makra @
user> @string-ref
"Hello world"
 
user=>

Demonstrační příklad na použití atomů a samozřejmě i makra @:

; vytvoření nového atomu
user=> (def x (atom 42))
#'user/x
 
; globální symbol x je navázán
; na atom a nikoli na stav identity
; (=hodnotu)
user=> x
#<Atom@61a907: 42>
 
; pro získání aktuálního stavu
; je nutné použít dereferenci
user=> (deref x)
42
 
; namísto (deref x) se používá
; makro preprocesoru @
user=> @x
42
 
 
; atomická změna stavu identity
user=> (reset! x 10)
10
 
user=> (reset! x (+ 1 2 3))
6
 
user=> @x
7
 
 
; další možnost atomické změny
; stavu identity - nyní přes funkci
; aplikovanou na atom a popř. i další
; parametry
user=> (swap! x + 1)
7
 
user=> @x
7
 
user=>

Příklad vytvoření agenta, poslání funkce agentovi a čekání na dokončení výpočtu právě s použitím makra @:

; vytvoření agenta
user=> (def my-agent (agent 0))
#'user/my-agent
 
; poslání funkce agentovi (6 je druhý parametr funkce)
user=> (send my-agent + 6)
#<Agent@18622f3: 0>
 
; poslání funkce agentovi (7 je druhý parametr funkce)
user=> (send my-agent * 7)
#<Agent@18622f3: 6>
 
; dereference - získání nového stavu - pomocí makra @
user=> @my-agent
42
 
user=>

Další příklad s makrem @, tentokrát použitým pro objekty future:

; vytvoření objektu typu future a spuštění paralelního vlákna
user=> (def future_fibonacci1 (future (fibonacci 35)))
#'user/future_fibonacci1
 
; vytvoření dalšího objektu typu future a spuštění paralelního vlákna
user=> (def future_fibonacci2 (future (fibonacci 35)))
#'user/future_fibonacci2
 
; čekání na dokončení prvního paralelně běžícího výpočtu
; - zde se využívá makro @
user=> @future_fibonacci1
9227465
 
; čekání na dokončení druhého paralelně běžícího výpočtu
; - zde se využívá makro @
user=> @future_fibonacci2
9227465
 
user=>

Poslední demonstrační příklad: makro @ a objekty typu promise:

; vytvoření objektu typu promise
; a navázání na symbol promise-test
user=> (def promise-test (promise))
#'user/promise-test
 
; nastavení hodnoty
user=> (deliver promise-test 42)
#<core$promise$reify__6153@1318b: 42>
 
; získání nastavené hodnoty pomocí makra @
user=> @promise-test
42
 
user=>

9. Makra „quote“ a „syntax-quote“

Konečně se dostáváme k zajímavějším a užitečnějším makrům, které přibližují možnosti Clojure Common Lispu i dalším pokročilejším lispovským jazykům, pochopitelně včetně Racketu. Jedno z nejdůležitějších a nejčastěji používaných maker se jmenuje „quote“ a zapisuje se pomocí apostrofu. Toto makro zakazuje vyhodnocování seznamů, protože pokud by objekt reader načetl formu ve tvaru (a b c), předal by ji do funkce eval, kde by se tato forma vyhodnotila jako volání funkce a s parametry b a c. Pokud však reader načte formu '(a b c), ztransformuje ji do tvaru (quote (a b c)), přičemž quote je speciální forma zakazující vyhodnocení. Na většinu ostatních objektů kromě seznamů nemá makro „quote“ většinou žádný vliv:

; zde nemá quote žádný vliv
user=> '42
42
 
; zákaz vyhodnocení seznamů jako funkce
user=> '(1 2 3)
(1 2 3)
 
; zde nemá quote žádný vliv
user=> '[1 2 3]
[1 2 3]
 
; stejné jako předchozí forma
user=> [1 2 3]
[1 2 3]
 
; zákaz vyhodnocení seznamů jako funkce
user=> '(* 6 7)
(* 6 7)
 
; zde se však seznam vyhodnotí jako funkce *
user=> (* 6 7)
42
 
user=>

Kromě makra „quote“ ještě objekt reader rozeznává poněkud komplikovanější makro nazývané „syntax-quote“, které se zapisuje pomocí zpětného apostrofu: `. Chování tohoto makra se liší podle toho, s jakým typem objektu je použito, ovšem ve všech případech se makro chová tak, aby nedocházelo k vyhodnocení jeho argumentů, popř. ani k vyhodnocení vnořených forem.

Poznámka: podobnost s Common Lispem pochopitelně není náhodná.

V následujících příkladech dochází k jednoduchému zákazu vyhodnocení předané formy:

user=> `42
42
 
user=> `(1 2 3)
(1 2 3)
 
user=> `[1 2 3]
[1 2 3]
 
user=>

Dále toho makro dokáže nahradit zjednodušené jméno symbolu jeho plně kvalifikovaným jménem. Nejlépe si to opět ukážeme na několika příkladech:

user=> `seq
clojure.core/seq
 
user=> `map
clojure.core/map
 
user=> `Integer/valueOf
java.lang.Integer/valueOf
 
user=>

Zákaz vyhodnocení a současně i náhrada zjednodušeného jména symbolu na plně kvalifikované jméno se projeví tím, že se následující seznamy (a vektor na konci) nevyhodnotí jako funkce, ale například funkce * se nahradí plným jménem:

user=> `(* 6 7)
(clojure.core/* 6 7)
 
user=> `(str "Hello" "world")
(clojure.core/str "Hello" "world")
 
user=> `[* seq str xyzzy neznamy]
[clojure.core/* clojure.core/seq clojure.core/str user/xyzzy user/neznamy]
 
user=>
Poznámka: toto chování je důležité právě při expanzi maker.

10. Makra „unquote“ a „unquote-splicing“

Makro nazvané „unquote“, které se zapisuje s využitím znaku ~ (tilda), dokáže vynutit vyhodnocení určité části výrazu, a to tehdy, pokud je tento výraz umístěn v makru ` (syntax-quote), nikoli však ' (quote).

Poznámka: povšimněte si podobnosti s čárkou použitou v Common Lispu pro podobné účely.

Nejprve si ukažme způsob zápisu tohoto makra i to, jaký má toto makro vliv na zapisované výrazy:

; makro quote zakáže vyhodnocení celého seznamu
user=> '(1 2 (* 6 7) (/ 4 2))
(1 2 (* 6 7) (/ 4 2))
 
; makro syntax-quote zakáže vyhodnocení taktéž a
; současně provede náhradu jmen funkcí za jejich plný tvar
user=> `(1 2 (* 6 7) (/ 4 2))
(1 2 (clojure.core/* 6 7) (clojure.core// 4 2))
 
; pomocí ~ vynutíme vyhodnocení podvýrazu (* 6 7)
user=> `(1 2 ~(* 6 7) (/ 4 2))
(1 2 42 (clojure.core// 4 2))
 
; pomocí ~ vynutíme vyhodnocení podvýrazu (/ 4 2)
user=> `(1 2 (* 6 7) ~(/ 4 2))
(1 2 (clojure.core/* 6 7) 2)
 
; pomocí dvou ~ vynutíme vyhodnocení obou podvýrazů
user=> `(1 2 ~(* 6 7) ~(/ 4 2))
(1 2 42 2)
 
user=>

Podobným způsobem pracuje i makro zapisované pomocí dvou znaků ~@, ovšem to navíc ještě provádí „zplošťování seznamů“. Prozatím si chování tohoto makra ukážeme na velmi jednoduchém umělém příkladu:

; uživatelsky definovaný seznam
user=> (def s '(1 2 3))
#'user/s
 
; makro quote zcela zakáže vyhodnocování
user=> '(1 2 3 (cons s s))
(1 2 3 (cons s s))
 
; makro syntax-quote taktéž, ovšem ještě nahradí
; všechny symboly jejich plnými jmény
user=> `(1 2 3 (cons s s))
(1 2 3 (clojure.core/cons user/s user/s))
 
; vynutíme si vyhodnocení podvýrazu (cons s s)
; který vrací ((1 2 3) 1 2 3)
user=> `(1 2 3 ~(cons s s))
(1 2 3 ((1 2 3) 1 2 3))
 
; dtto, ovšem seznam, jenž je výsledkem (cons s s)
; je zploštěn (jakoby je
odstraněna jedna úroveň zanoření)
user=> `(1 2 3 ~@(cons s s))
(1 2 3 (1 2 3) 1 2 3)
 
user=>

Podívejme se nyní na jednoduché uživatelské makro vytvořené s využitím formy defmacro. V samotném makru použijeme výše zmíněná makra ` (syntax quote) a ~ (unquote):

(defmacro trace1
    [vyraz]
    `(let [x ~vyraz] (println '~vyraz "=" x) x))

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 trace2
    [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 v Clojure:

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

11. Makra v programovacím jazyku Racket

Ve druhé části dnešního článku je ukázán základní způsob tvorby maker v programovacím jazyku Racket. Bude se, alespoň z hlediska teorie, jednat o stejný koncept, s nímž jsme se seznámili v předchozích kapitolách (ostatně Racket je i přes svá mnohá rozšíření stále lispovským jazykem), ovšem s tím rozdílem, že se s makry v Racketu pracuje jednodušším a taktéž bezpečnějším způsobem – samotný makrosystém nás totiž v případě potřeby dokáže odstínit od některých problémů, na něž jsme narazili výše (lokální proměnné v expanzi makra atd.). Dále je možné v Racketu vytvářet makra s proměnným množstvím argumentů, což je sice možné i v Common Lispu či Clojure, ovšem koncept použitý v Racketu je velmi snadno pochopitelný a následně i použitelný (vlastně se syntakticky příliš neliší od funkcí s proměnným počtem parametrů, které jsme si popsali v předchozí části tohoto seriálu).

Poznámka: někdy se můžeme setkat s termínem „hygienická makra“. Makra v Racketu jsou hygienická v tom smyslu, že například automaticky detekují konflikt ve jménech lokálních proměnných a parametrů makra atd. Podobně koncipovaná makra nalezneme v Rustu, jazyku Julia či v Dylanu. V Clojure nebo Common Lispu se naproti tomu musíme o „hygienu“ v některých případech postarat sami.

12. Vytvoření makra s využitím define-syntax-rule

Nejprve se seznámíme se způsobem tvorby maker založených na formě pojmenované define-syntax-rule. Tato makra se nazývají pattern-based macros, což je název plynoucí z toho, že se expanze makra řídí sadou vzorků/pravidel (minimálně jednoho vzorku). Základní použití formy define-syntax-rule vypadá podobně, jako tomu bylo ve výše zmíněné formě defmacro:

(define-syntax-rule (square x)
    (* x x))

Samotný zápis se do značné míry podobá zápisu běžné funkce (alespoň z pohledu syntaxe), ovšem sémantika je odlišná:

  1. Za jménem formy define-syntax-rule se v závorce nachází takzvaný vzorek (pattern). Každý vzorek začíná jménem makra následovaného názvy parametrů. Podle počtu parametrů se určuje, jaká část makra bude v daném kontextu expandována.
  2. Za vzorkem/vzorky následuje šablona makra (template), tj. (zjednodušeně a nepřesně řečeno) kód, na který je makro expandováno.

V našem jednoduchém případě bude volání makra:

(display (square 10))
100

expandováno na:

(* 10 10)

což asi není příliš překvapivé a v tomto případě ani užitečné.

Poznámka: takováto jednoduchá makra se většinou v reálných aplikacích nepíšou, protože v programovacím jazyku Racket neslouží makra primárně k urychlení výpočtů (v tomto případě k takzvanému inliningu kódu), ale spíše k vytváření nové syntaxe a mnohdy i k tvorbě ucelených doménově specifických jazyků (DSL). Velmi dobrým příkladem DSL je výše popsané makro loop z Common Lispu.

Podívat se pochopitelně můžeme i na expanzi tohoto makra, a to buď celou expanzi až na základní symboly jazyka nebo pouze na expanzi makra (bez dalšího zpracování podforem):

(define-syntax-rule (square x)
    (* x x))
 
(display (square 10))
 
(newline)
 
(display (expand-once '(square 10)))
(newline)
 
(display (expand '(square 10)))
(newline)

Výsledkem bude výsledek výrazu, na který se makro expandovalo, expanze samotného makra i celá expanze na primární prvky jazyka:

100
#<syntax:/home/tester/test..rkt:2:4 (* 10 10)>
#<syntax:/home/tester/test..rkt:2:4 (#%app * (quote 10) (quote 10))>

Použití proměnné namísto konstanty 10:

(define-syntax-rule (square x)
    (* x x))
 
(define foobar 10)
 
(display (square foobar))
 
(newline)
 
(display (expand-once '(square foobar)))
(newline)
 
(display (expand '(square foobar)))
(newline)

S výsledkem:

100
#<syntax:/home/tester/test.rkt:2:4 (* foobar foobar)>
#<syntax:/home/tester/test.rkt:2:4 (#%app * foobar foobar)>

Pozor ovšem na to, že se parametr v expandovaném makru vyhodnocuje dvakrát:

(display (expand-once '(square (+ 1 2))))
#<syntax:readline-input:2:3 (* (+ 1 2) (+ 1 2))>

Úprava tohoto problému může vypadat například takto:

(define-syntax-rule (square x)
    (let ([tmp x])
         (* tmp tmp)))
 
(display (square 10))
(newline)
 
(display (expand-once (square 10)))
(newline)
 
(display (expand-once '(square 10)))
(newline)
 
(display (expand-once '(square 10)))
(newline)
 
(display (expand '(square 10)))
(newline)
 
(display (expand-once '(square (+ 1 2))))
(newline)

S výsledky:

100
#<syntax (quote 100)>
#<syntax:readline-input:2:4 (let ((tmp 10)) (* tmp tmp))>
#<syntax:readline-input:2:4 (let ((tmp 10)) (* tmp tmp))>
#<syntax:readline-input:2:4 (let-values (((tmp) (quote 10))) (#%app * tmp tmp))>
#<syntax:readline-input:2:4 (let ((tmp (+ 1 2))) (* tmp tmp))>

13. Syntaktické objekty, použití syntax->datum

V předchozí kapitole jsme mohli vidět, že pokud si necháme vypsat expandované makro, vrátí se hodnota začínající na #<syntax::

(display (expand-once '(square (+ 1 2))))
#<syntax:readline-input:2:4 (let ((tmp (+ 1 2))) (* tmp tmp))>

Jedná se o takzvané syntaktické objekty, které je možné zapisovat i přímo s pomocí znaku #. Ovšem nás bude nyní spíše zajímat, jak z takového objektu získat čitelnou podobu expandovaného kódu. K tomuto účelu slouží forma syntax->datum, kterou lze použít například takto:

(define-syntax-rule (square x)
    (* x x))
 
(display (square 10))
(newline)
 
(display (syntax->datum (expand-once (square 10))))
(newline)
 
(display (syntax->datum (expand-once '(square 10))))
(newline)
 
(display (syntax->datum (expand-once '(square 10))))
(newline)
 
(display (syntax->datum (expand '(square 10))))
(newline)
 
(display (syntax->datum (expand-once '(square (+ 1 2)))))
(newline)

S krásně čitelnými výsledky expanze makra:

100
(quote 100)
(* 10 10)
(* 10 10)
(#%app * (quote 10) (quote 10))
(* (+ 1 2) (+ 1 2))

Podobně si můžeme nechat vypsat expandovanou podobu vylepšené varianty makra square:

(define-syntax-rule (square x)
    (let ([tmp x])
         (* tmp tmp)))
 
(display (square 10))
(newline)
 
(display (syntax->datum (expand-once (square 10))))
(newline)
 
(display (syntax->datum (expand-once '(square 10))))
(newline)
 
(display (syntax->datum (expand-once '(square 10))))
(newline)
 
(display (syntax->datum (expand '(square 10))))
(newline)
 
(display (syntax->datum (expand-once '(square (+ 1 2)))))
(newline)

Tentokrát pochopitelně s odlišnými výsledky, protože se v expandovaném makru objeví forma let:

100
(quote 100)
(let ((tmp 10)) (* tmp tmp))
(let ((tmp 10)) (* tmp tmp))
(let-values (((tmp) (quote 10))) (#%app * tmp tmp))
(let ((tmp (+ 1 2))) (* tmp tmp))

14. Makro pro prohození obsahu dvou proměnných

Ukažme si tedy nepatrně složitější makro, které je mimochodem popsáno i v dokumentaci programovacího jazyka Racket. Toto makro se jmenuje swap a slouží k prohození obsahu dvou proměnných s využitím lokální proměnné ve formě dočasného úložiště:

(define-syntax-rule (swap x y)
    (let ([tmp x])
         (set! x y)
         (set! y tmp)))

Příklad použití makra:

(swap foo bar)

Popř. v uceleném příkladu s dvojicí lokálních proměnných:

(let [(x 10)
      (y 20)]
     (swap x y)
     (display x)
     (newline)
     (display y)
     (newline))
 
20
10

Toto volání makra je expandováno na:

(let ([tmp foo])
    (set! foo bar)
    (set! bar tmp))

Otestování s dvojicí globálních proměnných:

(define-syntax-rule (swap x y)
    (let ([tmp x])
         (set! x y)
         (set! y tmp)))
 
(define foo 10)
(define bar 20)
 
(swap foo bar)
(display foo)
(newline)
(display bar)
(newline)
 
(display (expand-once '(swap foo bar)))
(newline)

Výsledky:

20
10
#<syntax:/home/tester/test.rkt:2:4 (let ((tmp foo)) (set! foo bar) (set! bar tmp))>

Co se však stane v případě, že budeme chtít prohodit obsah proměnných tmp a baz? Mohlo by se zdát, že v tomto případě dojde ke kolizi jména parametru makra a jména lokální proměnné v expandovaném makru:

(let ([tmp tmp])
    (set! tmp baz)
    (set! baz tmp))

Ve skutečnosti ovšem makrosystém programovacího jazyka Racket tomuto problému automaticky zabrání, protože dokáže lokální proměnné v expandovaném makru přejmenovat takovým způsobem, aby byly unikátní:

(let ([tmp_1 tmp])
    (set! tmp baz)
    (set! baz tmp_1))

To však není zdaleka vše, protože se podobným způsobem zabrání tomu, aby se použila pozměněná hodnota symbolu set!. Pokud totiž jednu z proměnných nazveme právě set!, bude expandované makro vypadat takto:

(let ([tmp_1 set1_1])
    (set! set1_1 baz)
    (set! baz tmp_1))

Pokud by k této záměně nedošlo, nebylo by možné funkci set! zavolat.

Otestování:

(define-syntax-rule (swap x y)
    (let ([tmp x])
         (set! x y)
         (set! y tmp)))
 
(let ([set! 10]
      [baz 20])
     (swap set! baz)
     (display set!)
     (newline)
     (display baz)
     (newline))

Výsledek je opět předvídatelný:

20
10

15. Makro s volitelnou aritou

V programovacím jazyku Racket je možné vytvářet makra s volitelnou (proměnnou) aritou, podobně jako je možné vytvářet funkce s proměnnou aritou. Asi nejtypičtějším příkladem mohou být makra nazvaná and a or, které slouží k podmíněnému vyhodnocení zapsaných výrazů. Vyhodnocení probíhá zleva doprava a ve chvíli, kdy je již výsledek výrazu známý (mezivýsledek je #fand nebo #tor), vyhodnocení je ihned ukončeno a další parametry se nijak nevyhodnocují. Díky tomu je možné tato makra použít pro řízení běhu programu a navíc nelze and ani or realizovat běžnou funkcí (tam by se naopak nejprve vyhodnotily všechny parametry, což nechceme).

Problém ovšem spočívá v tom, že and a or nejsou binární operátory (tak jako je tomu v mnoha mainstreamových jazycích), ale formy akceptující libovolný počet parametrů, přičemž pro různé počty parametrů se může provádět odlišný výpočet. K tomuto účelu slouží forma (taktéž makro) nazvaná syntax-rules, které můžeme předat libovolný počet vzorků volání makra i jeho expandované podoby. Například budeme požadovat, aby se zavolání or bez parametrů expandovalo přímo na hodnotu #f, zavolání or s jedním parametrem přímo na hodnotu tohoto parametru a při zavolání se dvěma parametry bude výsledek buď první hodnota (pokud je #t) nebo naopak hodnota druhá:

(define-syntax or
  (syntax-rules ()
    [(or) #f]
    [(or x) x]
    [(or x y) (let ([z x])
                   (if z z y))]))
Poznámka: víte proč se v posledním vzorku používá lokální proměnná z, i když by se zdánlivě mohla namísto formy let přímo zapsat podmínka?

Chování tohoto makra si můžeme velmi snadno ověřit, například na několika variantách volání bez parametrů, s jedním parametrem a se dvěma parametry:

(display (or))
(newline)
 
(display (or #t))
(newline)
 
(display (or #f))
(newline)
 
(display (or #t #f))
(newline)
 
(display (or #f #f))
(newline)

Navíc se taktéž můžeme podívat na způsob expanze tohoto makra:

(display (expand-once '(or)))
(newline)
 
(display (expand-once '(or #f)))
(newline)
 
(display (expand-once '(or #f #f)))
(newline)

S výsledky:

#<syntax:/home/tester/test.rkt:3:10 #f>
#<syntax #f>
#<syntax:/home/tester/test.rkt:5:14 (let ((z #f)) (if z z #f))>

Čitelnější podobu expandovaného makra získáme opět přes syntax->datum:

(display (syntax->datum (expand-once '(or))))
(display (syntax->datum (expand-once '(or #f))))
(display (syntax->datum (expand-once '(or #t))))
(display (syntax->datum (expand-once '(or #f #f))))
(display (syntax->datum (expand-once '(or #f #t))))

Nyní se vypíše:

#f
#f
#t
(let ((z #f)) (if z z #f))
(let ((z #f)) (if z z #t))

16. Použití … v makru

V případě, že budeme chtít makro or modifikovat takovým způsobem, aby pracovalo skutečně s libovolným množstvím argumentů, můžeme pro tento účel použít … (tři tečky). Nejprve si však ukažme, jak Racket reaguje na situaci, kdy se makru předává špatný počet parametrů, který neodpovídá žádnému pravidlu. Použijeme původní podobu makra:

(define-syntax or
  (syntax-rules ()
    [(or) #f]
    [(or x) x]
    [(or x y) (let ([z x])
                   (if z z y))]))

Makro budeme volat se třemi parametry (takové pravidlo neexistuje):

(display (or #f #f #t))

V tomto případě se vypíše chybové hlášení „bad syntax“ společně s výrazem, který je z pohledu interpretru programovacího jazyka Racket chybný:

or: bad syntax
  in: (or #f #f #f)

V definici makra ovšem můžeme použít … (trojici teček), a to jak v samotném vzorku, tak i v šabloně popisující, jak se má makro expandovat:

(define-syntax or
  (syntax-rules ()
    [(or) #f]
    [(or x) x]
    [(or x y) (let ([z x])
                   (if z z y))]
    [(or x y ...) (or x (or y ...))]))

V tomto případě se při expanzi makra nahradí trojice teček za ty parametry, které nejsou explicitně pojmenovány. Jinými slovy to znamená, že se zvýrazněný řádek uplatní u volání makra se třemi a více parametry. Volání se nahradí expanzí makra, nyní ovšem bez prvního parametru a v případě potřeby se expanze provádí rekurzivně – to vše ve chvíli, kdy je makro načteno objektem typu Reader, nikoli v čase běhu.

Chování si můžeme odzkoušet:

(display (or #f #f #t))
#t

A samozřejmě si můžeme zobrazit i expandované makro:

(syntax->datum (expand-once '(or #t #f #t #f #t #f)))
'(or #t (or #f #t #f #t #f))

Plná expanze makra v čitelné podobě:

ict ve školství 24

(syntax->datum (expand '(or #t #f #t #f #t #f)))
 
'(let-values (((z) '#t))
   (if z
     z
     (let-values (((z) '#f))
       (if z
         z
         (let-values (((z) '#t))
           (if z
             z
             (let-values (((z) '#f))
               (if z z (let-values (((z) '#t)) (if z z '#f))))))))))
Poznámka: podobným způsobem jsou implementována makra pro realizaci programových smyček, kde se do … schová celé tělo smyčky.

17. Původní (nehygienická) makra v Racketu

V programovacím jazyku Racket se důrazně doporučuje používat hygienická makra založená na vzorcích a šablonách, tedy makra popsaná v předchozích kapitolách. Ovšem pro účely portace zdrojových kódů z dalších dialektů Scheme nebo (Common) Lispu existuje balíček, který programátorům nabízí i formy defmacro, define-macro atd. Tento modul je popsaný na stránce https://docs.racket-lang.org/compatibility/defmacro.html. Jedná se o klasicky pojatá makra, která dokážou pracovat přímo se stromem reprezentovaným S-výrazem. Kvůli tomu je nutné používat formy pro quote, quasiquote a unquote, které jsme si představili při popisu Common Lispovských maker.

18. Repositář s demonstračními příklady

Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/lisp-families.git (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

# Příklad Popis příkladu Cesta
1 square1.rkt makro pro výpočet druhé mocniny https://github.com/tisnik/lisp-families/blob/master/racket/ma­cros/square1.rkt
2 square2.rkt vylepšení předchozího makra (jedno vyhodnocení parametru) https://github.com/tisnik/lisp-families/blob/master/racket/ma­cros/square2.rkt
3 square1-printable.rkt převod expandovaného makra square do čitelné podoby https://github.com/tisnik/lisp-families/blob/master/racket/ma­cros/square1-printable.rkt
4 square2-printable.rkt převod expandovaného makra square do čitelné podoby https://github.com/tisnik/lisp-families/blob/master/racket/ma­cros/square2-printable.rkt
5 swap1.rkt makro pro prohození obsahu dvou proměnných https://github.com/tisnik/lisp-families/blob/master/racket/ma­cros/swap1.rkt
6 swap2.rkt otestování na globálních proměnných https://github.com/tisnik/lisp-families/blob/master/racket/ma­cros/swap2.rkt
7 swap3.rkt otestování ošetření speciálních případů https://github.com/tisnik/lisp-families/blob/master/racket/ma­cros/swap3.rkt
8 swap4.rkt otestování ošetření speciálních případů https://github.com/tisnik/lisp-families/blob/master/racket/ma­cros/swap4.rkt
9 or1.rkt makro or s proměnnou aritou https://github.com/tisnik/lisp-families/blob/master/racket/ma­cros/or1.rkt
10 or1-printable.rkt převod expandovaného makra or do čitelné podoby https://github.com/tisnik/lisp-families/blob/master/racket/macros/or1-printable.rkt
11 or2.rkt makro or s libovolnou aritou https://github.com/tisnik/lisp-families/blob/master/racket/ma­cros/or2.rkt
12 or2-printable.rkt převod expandovaného makra or do čitelné podoby https://github.com/tisnik/lisp-families/blob/master/racket/macros/or2-printable.rkt

19. Literatura

  1. Peter Seibel
    „Practical Common Lisp“
    2009
  2. Paul Graham
    „ANSI Common Lisp“
    1995
  3. Gerald Gazdar
    „Natural Language Processing in Lisp: An Introduction to Computational Linguistics“
    1989
  4. Peter Norvig
    „Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp“
    1991
  5. Alex Mileler et.al.
    „Clojure Applied: From Practice to Practitioner“
    2015
  6. „Living Clojure: An Introduction and Training Plan for Developers“
    2015
  7. Dmitri Sotnikov
    „Web Development with Clojure: Build Bulletproof Web Apps with Less Code“
    2016
  8. McCarthy
    „Recursive functions of symbolic expressions and their computation by machine, part I“
    1960
  9. R. Kent Dybvig
    „The Scheme Programming Language“
    2009
  10. Max Hailperin
    „Concrete Abstractions“
    1998
  11. Guy L. Steele
    „History of Scheme“
    2006, Sun Microsystems Laboratories
  12. Kolář J., Muller K.:
    „Speciální programovací jazyky“
    Praha 1981
  13. „AutoLISP Release 9, Programmer's reference“
    Autodesk Ltd., October 1987
  14. „AutoLISP Release 10, Programmer's reference“
    Autodesk Ltd., September 1988
  15. McCarthy, John; Abrahams, Paul W.; Edwards, Daniel J.; Hart, Timothy P.; Levin, Michael I.
    „LISP 1.5 Programmer's Manual“
    MIT Press. ISBN 0 262 130 1 1 4
  16. Carl Hewitt; Peter Bishop and Richard Steiger
    „A Universal Modular Actor Formalism for Artificial Intelligence“
    1973
  17. Feiman, J.
    „The Gartner Programming Language Survey (October 2001)“
    Gartner Advisory
  18. Harold Abelson, Gerald Jay Sussman, Julie Sussman:
    Structure and Interpretation of Computer Programs
    MIT Press. 1985, 1996 (a možná vyšel i další přetisk)
  19. Paul Graham
    On Lisp
    Prentice Hall, 1993
    Dostupné online na stránce http://www.paulgraham.com/on­lisptext.html
  20. David S. Touretzky
    Common LISP: A Gentle Introduction to Symbolic Computation (Dover Books on Engineering)
  21. Peter Norvig
    Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp
  22. Patrick Winston, Berthold Horn
    Lisp (3rd Edition)
    ISBN-13: 978–0201083194, ISBN-10: 0201083191
  23. Matthias Felleisen, David Van Horn, Dr. Conrad Barski
    Realm of Racket: Learn to Program, One Game at a Time!
    ISBN-13: 978–1593274917, ISBN-10: 1593274912

20. Odkazy na Internetu

  1. Racket: Macros
    https://www.it.uu.se/edu/cou­rse/homepage/avfunpro/ht13/lec­tures/Racket-3-Macros.pdf
  2. Beautiful Racket / explainers: Macros
    https://beautifulracket.com/ex­plainer/macros.html
  3. Macros (dokumentace k Racketu)
    https://docs.racket-lang.org/guide/macros.html
  4. Model syntaxe jazyka Racket
    https://docs.racket-lang.org/reference/syntax-model.html
  5. Syntax Objects
    https://docs.racket-lang.org/guide/stx-obj.html
  6. Tech behind Tech: Clojure Macros Simplified
    http://techbehindtech.com/2010/09/28/clo­jure-macros-simplified/
  7. Fatvat – Exploring functional programming: Clojure Macros
    http://www.fatvat.co.uk/2009/02/clo­jure-macros.html
  8. Beautiful Racket: an introduction to language-oriented programming using Racket
    https://beautifulracket.com/
  9. Stránky projektu Racket
    https://racket-lang.org/
  10. Dokumentace k projektu Racket
    https://docs.racket-lang.org/index.html
  11. Seznam dostupných balíčků pro Racket
    https://pkgs.racket-lang.org/
  12. Racket na Wikipedii
    https://en.wikipedia.org/wi­ki/Racket_(programming_lan­guage)
  13. Blogy o Racketu a navazujících technologiích
    https://blog.racket-lang.org/
  14. Prográmky psané v Racketu na RosettaCode
    http://rosettacode.org/wi­ki/Category:Racket
  15. Fear of Macros
    https://www.greghendershott.com/fear-of-macros/
  16. Rackjure
    https://github.com/greghen­dershott/rackjure
  17. Matthew Flatt’s proposal to change Racket’s s-expressions based syntax to infix representation creates a stir in the community
    https://hub.packtpub.com/matthew-flatts-proposal-to-change-rackets-s-expressions-based-syntax-to-infix-representation-creates-a-stir-in-the-community/
  18. Racket News
    https://racket-news.com/
  19. Racket: Lisp for learning
    https://lwn.net/Articles/795385/
  20. Future of Racket
    https://www.greghendershot­t.com/2019/07/future-of-racket.html
  21. Kawa: Compiling Scheme to Java
    https://www.mit.edu/afs.new/sip­b/project/kawa/doc/kawa-tour.html
  22. Kawa in Languages shootout
    http://per.bothner.com/blog/2010/Kawa-in-shootout/
  23. Kawa 2.0 Supports Scheme R7RS
    https://developers.slashdot­.org/story/14/12/13/2259225/ka­wa-20-supports-scheme-r7rs/
  24. Kawa — fast scripting on the Java platform
    https://lwn.net/Articles/623349/
  25. Tail call (a její optimalizace)
    https://en.wikipedia.org/wi­ki/Tail_call
  26. SLIME (Wikipedia)
    http://en.wikipedia.org/wiki/SLIME
  27. slime.vim
    http://s3.amazonaws.com/mps/slime.vim
  28. What are the best scheme implementations?
    https://www.slant.co/topic­s/5282/~scheme-implementations
  29. Bigloo homepage
    http://www-sop.inria.fr/mimosa/fp/Bigloo/
  30. FTP s tarbally Bigloo
    ftp://ftp-sop.inria.fr/indes/fp/Bigloo
  31. GOTO 2018 • Functional Programming in 40 Minutes • Russ Olsen
    https://www.youtube.com/wat­ch?v=0if71HOyVjY
  32. TinyScheme (stránka na Sourceforge)
    http://tinyscheme.sourcefor­ge.net/home.html
  33. Embedding Tiny Scheme in a Game
    http://www.silicondelight­.com/embedding-tiny-scheme-in-a-game/
  34. Embedding Scheme for a game mission scripting DSL
    http://carloscarrasco.com/embedding-scheme-for-a-game-mission-scripting-dsl.html
  35. Všechny verze TinyScheme na SourceForge
    https://sourceforge.net/pro­jects/tinyscheme/files/ti­nyscheme/
  36. Fork TinyScheme na GitHubu
    https://github.com/yawnt/tinyscheme
  37. Ackermannova funkce
    https://cs.wikipedia.org/wi­ki/Ackermannova_funkce
  38. Ackermann function na Rosetta Code
    https://rosettacode.org/wi­ki/Ackermann_function#Sche­me
  39. Success Stories (lisp.org)
    https://lisp-lang.org/success/
  40. Allegro Common Lisp Success Stories
    https://franz.com/success/
  41. Clojure Success Stories
    https://clojure.org/commu­nity/success_stories
  42. Scheme Quick Reference
    https://www.st.cs.uni-saarland.de/edu/config-ss04/scheme-quickref.pdf
  43. Slajdy o Scheme (od slajdu číslo 15)
    https://docs.google.com/pre­sentation/d/1abmDnKjrq1tcjGvvRNAK­hOiSTSE2lyagtcEPal07Gbo/e­dit
  44. Scheme Cheat Sheet
    https://github.com/smythp/scheme-cheat-sheet
  45. Embedding Lua, embedding Guile
    http://puntoblogspot.blog­spot.com/2013/04/embedding-lua-embedding-guile.html
  46. Lambda Papers
    https://en.wikisource.org/wi­ki/Lambda_Papers
  47. Revised7Report on the Algorithmic Language Scheme
    https://small.r7rs.org/at­tachment/r7rs.pdf
  48. Video Lectures (MIT, SICP 2005)
    https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6–001-structure-and-interpretation-of-computer-programs-spring-2005/video-lectures/
  49. Why is Scheme my first language in university?
    https://softwareengineerin­g.stackexchange.com/questi­ons/115252/why-is-scheme-my-first-language-in-university
  50. The Perils of JavaSchools
    https://www.joelonsoftware­.com/2005/12/29/the-perils-of-javaschools-2/
  51. How to Design Programs, Second Edition
    https://htdp.org/2019–02–24/index.html
  52. LilyPond
    http://lilypond.org/
  53. LilyPond — Extending (přes Scheme)
    http://lilypond.org/doc/v2­.18/Documentation/extendin­g/scheme-tutorial
  54. Scheme in LilyPond
    http://lilypond.org/doc/v2­.18/Documentation/extendin­g/scheme-in-lilypond
  55. GnuCash
    http://www.gnucash.org/
  56. Custom Reports (in GNU Cash)
    https://wiki.gnucash.org/wi­ki/Custom_Reports
  57. Program by Design
    https://programbydesign.org/
  58. SchemePy
    https://pypi.org/project/SchemePy/
  59. LISP FQA: Section – [1–5] What is the „minimal“ set of primitives needed for a Lisp interpreter?
    http://www.faqs.org/faqs/lisp-faq/part1/section-6.html
  60. femtolisp
    https://github.com/JeffBe­zanson/femtolisp
  61. (How to Write a (Lisp) Interpreter (in Python))
    http://norvig.com/lispy.html
  62. Repositář s Guile Emacsem
    http://git.hcoop.net/?p=bpt/guile.git
  63. Interacting with Guile Compound Data Types in C
    http://www.lonelycactus.com/gu­ilebook/x1555.html
  64. Calling Guile functions from C
    http://www.lonelycactus.com/gu­ilebook/c1204.html#SECCAL­LGUILEFUNC
  65. Arrays, and other compound data types
    http://www.lonelycactus.com/gu­ilebook/charrays.html
  66. Interacting with Guile Compound Data Types in C
    http://www.lonelycactus.com/gu­ilebook/x1555.html
  67. Guile Reference Manual
    https://www.gnu.org/softwa­re/guile/manual/html_node/in­dex.html
  68. Scheme: Summary of Common Syntax
    https://www.gnu.org/softwa­re/guile/manual/html_node/Syn­tax-Summary.html#Syntax-Summary
  69. Scripting with Guile: Extension language enhances C and Scheme
    https://www.ibm.com/develo­perworks/library/l-guile/index.html
  70. Having fun with Guile: a tutorial
    http://dustycloud.org/misc/guile-tutorial.html
  71. Guile: Loading Readline Support
    https://www.gnu.org/softwa­re/guile/manual/html_node/Lo­ading-Readline-Support.html#Loading-Readline-Support
  72. lispy
    https://pypi.org/project/lispy/
  73. Lython
    https://pypi.org/project/Lython/
  74. Lizpop
    https://pypi.org/project/lizpop/
  75. Budoucnost programovacích jazyků
    http://www.knesl.com/budoucnost-programovacich-jazyku
  76. LISP Prolog and Evolution
    http://blog.samibadawi.com/2013/05/lisp-prolog-and-evolution.html
  77. List of Lisp-family programming languages
    https://en.wikipedia.org/wi­ki/List_of_Lisp-family_programming_languages
  78. clojure_py na indexu PyPi
    https://pypi.python.org/py­pi/clojure_py
  79. PyClojure
    https://github.com/eigenhom­bre/PyClojure
  80. Hy na GitHubu
    https://github.com/hylang/hy
  81. Hy: The survival guide
    https://notes.pault.ag/hy-survival-guide/
  82. Hy běžící na monitoru terminálu společnosti Symbolics
    http://try-hy.appspot.com/
  83. Welcome to Hy’s documentation!
    http://docs.hylang.org/en/stable/
  84. Hy na PyPi
    https://pypi.org/project/hy/#des­cription
  85. Getting Hy on Python
    https://lwn.net/Articles/596626/
  86. Programming Can Be Fun with Hy
    https://opensourceforu.com/2014/02/pro­gramming-can-fun-hy/
  87. Přednáška o projektu Hy (pětiminutový lighttalk)
    http://blog.pault.ag/day/2013/04/02
  88. Hy (Wikipedia)
    https://en.wikipedia.org/wiki/Hy
  89. GNU Emacs Lisp Reference Manual: Point
    https://www.gnu.org/softwa­re/emacs/manual/html_node/e­lisp/Point.html
  90. GNU Emacs Lisp Reference Manual: Narrowing
    https://www.gnu.org/softwa­re/emacs/manual/html_node/e­lisp/Narrowing.html
  91. GNU Emacs Lisp Reference Manual: Functions that Create Markers
    https://www.gnu.org/softwa­re/emacs/manual/html_node/e­lisp/Creating-Markers.html
  92. GNU Emacs Lisp Reference Manual: Motion
    https://www.gnu.org/softwa­re/emacs/manual/html_node/e­lisp/Motion.html#Motion
  93. GNU Emacs Lisp Reference Manual: Basic Char Syntax
    https://www.gnu.org/softwa­re/emacs/manual/html_node/e­lisp/Basic-Char-Syntax.html
  94. Elisp: Sequence: List, Array
    http://ergoemacs.org/emac­s/elisp_list_vs_vector.html
  95. Elisp: Property List
    http://ergoemacs.org/emac­s/elisp_property_list.html
  96. Elisp: Hash Table
    http://ergoemacs.org/emac­s/elisp_hash_table.html
  97. Elisp: Association List
    http://ergoemacs.org/emac­s/elisp_association_list.html
  98. The mapcar Function (An Introduction to Programming in Emacs Lisp)
    https://www.gnu.org/softwa­re/emacs/manual/html_node/e­intr/mapcar.html
  99. Anaphoric macro
    https://en.wikipedia.org/wi­ki/Anaphoric_macro
  100. Some Common Lisp Loop Macro Examples
    https://www.youtube.com/wat­ch?v=3yl8o6r_omw
  101. A Guided Tour of Emacs
    https://www.gnu.org/softwa­re/emacs/tour/
  102. The Roots of Lisp
    http://www.paulgraham.com/ro­otsoflisp.html
  103. Evil (Emacs Wiki)
    https://www.emacswiki.org/emacs/Evil
  104. Evil (na GitHubu)
    https://github.com/emacs-evil/evil
  105. Evil (na stránkách repositáře MELPA)
    https://melpa.org/#/evil
  106. Evil Mode: How I Switched From VIM to Emacs
    https://blog.jakuba.net/2014/06/23/e­vil-mode-how-to-switch-from-vim-to-emacs.html
  107. GNU Emacs (home page)
    https://www.gnu.org/software/emacs/
  108. GNU Emacs (texteditors.org)
    http://texteditors.org/cgi-bin/wiki.pl?GnuEmacs
  109. An Introduction To Using GDB Under Emacs
    http://tedlab.mit.edu/~dr/gdbin­tro.html
  110. An Introduction to Programming in Emacs Lisp
    https://www.gnu.org/softwa­re/emacs/manual/html_node/e­intr/index.html
  111. 27.6 Running Debuggers Under Emacs
    https://www.gnu.org/softwa­re/emacs/manual/html_node/e­macs/Debuggers.html
  112. GdbMode
    http://www.emacswiki.org/e­macs/GdbMode
  113. Emacs (Wikipedia)
    https://en.wikipedia.org/wiki/Emacs
  114. Emacs timeline
    http://www.jwz.org/doc/emacs-timeline.html
  115. Emacs Text Editors Family
    http://texteditors.org/cgi-bin/wiki.pl?EmacsFamily
  116. Vrapper aneb spojení možností Vimu a Eclipse
    https://mojefedora.cz/vrapper-aneb-spojeni-moznosti-vimu-a-eclipse/
  117. Vrapper aneb spojení možností Vimu a Eclipse (část 2: vyhledávání a nahrazování textu)
    https://mojefedora.cz/vrapper-aneb-spojeni-moznosti-vimu-a-eclipse-cast-2-vyhledavani-a-nahrazovani-textu/
  118. Emacs/Evil-mode – A basic reference to using evil mode in Emacs
    http://www.aakarshnair.com/posts/emacs-evil-mode-cheatsheet
  119. From Vim to Emacs+Evil chaotic migration guide
    https://juanjoalvarez.net/es/de­tail/2014/sep/19/vim-emacsevil-chaotic-migration-guide/
  120. Introduction to evil-mode {video)
    https://www.youtube.com/wat­ch?v=PeVQwYUxYEg
  121. EINE (Emacs Wiki)
    http://www.emacswiki.org/emacs/EINE
  122. EINE (Texteditors.org)
    http://texteditors.org/cgi-bin/wiki.pl?EINE
  123. ZWEI (Emacs Wiki)
    http://www.emacswiki.org/emacs/ZWEI
  124. ZWEI (Texteditors.org)
    http://texteditors.org/cgi-bin/wiki.pl?ZWEI
  125. Zmacs (Wikipedia)
    https://en.wikipedia.org/wiki/Zmacs
  126. Zmacs (Texteditors.org)
    http://texteditors.org/cgi-bin/wiki.pl?Zmacs
  127. TecoEmacs (Emacs Wiki)
    http://www.emacswiki.org/e­macs/TecoEmacs
  128. Micro Emacs
    http://www.emacswiki.org/e­macs/MicroEmacs
  129. Micro Emacs (Wikipedia)
    https://en.wikipedia.org/wi­ki/MicroEMACS
  130. EmacsHistory
    http://www.emacswiki.org/e­macs/EmacsHistory
  131. Seznam editorů s ovládáním podobným Emacsu či kompatibilních s příkazy Emacsu
    http://www.finseth.com/emacs.html
  132. evil-numbers
    https://github.com/cofi/evil-numbers
  133. Debuggery a jejich nadstavby v Linuxu (1.část)
    http://fedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/
  134. Debuggery a jejich nadstavby v Linuxu (2.část)
    http://fedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/
  135. Debuggery a jejich nadstavby v Linuxu (3): Nemiver
    http://fedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/
  136. Debuggery a jejich nadstavby v Linuxu (4): KDbg
    http://fedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/
  137. Debuggery a jejich nadstavby v Linuxu (5): ladění aplikací v editorech Emacs a Vim
    https://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-5-ladeni-aplikaci-v-editorech-emacs-a-vim/
  138. Org mode
    https://orgmode.org/
  139. The Org Manual
    https://orgmode.org/manual/index.html
  140. Kakoune (modální textový editor)
    http://kakoune.org/
  141. Vim-style keybinding in Emacs/Evil-mode
    https://gist.github.com/tro­yp/6b4c9e1c8670200c04c16036805773d8
  142. Emacs – jak začít
    http://www.abclinuxu.cz/clan­ky/navody/emacs-jak-zacit
  143. Programovací jazyk LISP a LISP machines
    https://www.root.cz/clanky/pro­gramovaci-jazyk-lisp-a-lisp-machines/
  144. Evil-surround
    https://github.com/emacs-evil/evil-surround
  145. Spacemacs
    http://spacemacs.org/
  146. Lisp: Common Lisp, Racket, Clojure, Emacs Lisp
    http://hyperpolyglot.org/lisp
  147. Common Lisp, Scheme, Clojure, And Elisp Compared
    http://irreal.org/blog/?p=725
  148. Does Elisp Suck?
    http://irreal.org/blog/?p=675
  149. Emacs pro mírně pokročilé (9): Elisp
    https://www.root.cz/clanky/emacs-elisp/
  150. If I want to learn lisp, are emacs and elisp a good choice?
    https://www.reddit.com/r/e­macs/comments/2m141y/if_i_wan­t_to_learn_lisp_are_emacs_an­d_elisp_a/
  151. Clojure(Script) Interactive Development Environment that Rocks!
    https://github.com/clojure-emacs/cider
  152. An Introduction to Emacs Lisp
    https://harryrschwartz.com/2014/04/08/an-introduction-to-emacs-lisp.html
  153. Emergency Elisp
    http://steve-yegge.blogspot.com/2008/01/emergency-elisp.html
  154. Lambda calculus
    https://en.wikipedia.org/wi­ki/Lambda_calculus
  155. John McCarthy's original LISP paper from 1959
    https://www.reddit.com/r/pro­gramming/comments/17lpz4/joh­n_mccarthys_original_lisp_pa­per_from_1959/
  156. Micro Manual LISP
    https://www.scribd.com/do­cument/54050141/Micro-Manual-LISP
  157. How Lisp Became God's Own Programming Language
    https://twobithistory.org/2018/10/14/lis­p.html
  158. History of Lisp
    http://jmc.stanford.edu/ar­ticles/lisp/lisp.pdf
  159. The Roots of Lisp
    http://languagelog.ldc.upen­n.edu/myl/llog/jmc.pdf
  160. The Racket Manifesto
    http://felleisen.org/matthi­as/manifesto/
  161. MIT replaces Scheme with Python
    https://www.johndcook.com/blog/2009/03/26/mit-replaces-scheme-with-python/
  162. Adventures in Advanced Symbolic Programming
    http://groups.csail.mit.e­du/mac/users/gjs/6.945/
  163. Why MIT Switched from Scheme to Python (2009)
    https://news.ycombinator.com/i­tem?id=14167453
  164. Starodávná stránka XLispu
    http://www.xlisp.org/
  165. AutoLISP
    https://en.wikipedia.org/wi­ki/AutoLISP
  166. Seriál PicoLisp: minimalistický a výkonný interpret Lispu
    https://www.root.cz/serialy/picolisp-minimalisticky-a-vykonny-interpret-lispu/
  167. Common Lisp
    https://common-lisp.net/
  168. Getting Going with Common Lisp
    https://cliki.net/Getting%20Started
  169. Online Tutorial (Common Lisp)
    https://cliki.net/online%20tutorial
  170. Guile Emacs
    https://www.emacswiki.org/e­macs/GuileEmacs
  171. Guile Emacs History
    https://www.emacswiki.org/e­macs/GuileEmacsHistory
  172. Guile is a programming language
    https://www.gnu.org/software/guile/
  173. MIT Scheme
    http://groups.csail.mit.e­du/mac/projects/scheme/
  174. SIOD: Scheme in One Defun
    http://people.delphiforum­s.com/gjc//siod.html
  175. CommonLispForEmacs
    https://www.emacswiki.org/e­macs/CommonLispForEmacs
  176. Elisp: print, princ, prin1, format, message
    http://ergoemacs.org/emac­s/elisp_printing.html
  177. Special Forms in Lisp
    http://www.nhplace.com/ken­t/Papers/Special-Forms.html
  178. Basic Building Blocks in LISP
    https://www.tutorialspoin­t.com/lisp/lisp_basic_syn­tax.htm
  179. Introduction to LISP – University of Pittsburgh
    https://people.cs.pitt.edu/~mi­los/courses/cs2740/Lectures/Lis­pTutorial.pdf
  180. Why don't people use LISP
    https://forums.freebsd.org/threads/why-dont-people-use-lisp.24572/
  181. Structured program theorem
    https://en.wikipedia.org/wi­ki/Structured_program_the­orem
  182. Clojure: API Documentation
    https://clojure.org/api/api
  183. Tutorial for the Common Lisp Loop Macro
    http://www.ai.sri.com/pkarp/loop.html
  184. Common Lisp's Loop Macro Examples for Beginners
    http://www.unixuser.org/~e­uske/doc/cl/loop.html
  185. A modern list api for Emacs. No 'cl required.
    https://github.com/magnars/dash.el
  186. The LOOP Facility
    http://www.lispworks.com/do­cumentation/HyperSpec/Body/06_a­.htm
  187. Clojure.org: Vars and the Global Environment
    http://clojure.org/Vars
  188. Clojure.org: Refs and Transactions
    http://clojure.org/Refs
  189. Clojure.org: Atoms
    http://clojure.org/Atoms
  190. Clojure.org: Agents as Asynchronous Actions
    http://clojure.org/agents
  191. Transient Data Structureshttp://clojure.or­g/transients
  192. Dynamic Languages Strike Back
    http://steve-yegge.blogspot.cz/2008/05/dynamic-languages-strike-back.html
  193. Scripting: Higher Level Programming for the 21st Century
    http://www.tcl.tk/doc/scripting.html
  194. Clojure (na Wikipedia EN)
    http://en.wikipedia.org/wiki/Clojure
  195. Clojure (na Wikipedia CS)
    http://cs.wikipedia.org/wiki/Clojure
  196. SICP (The Structure and Interpretation of Computer Programs)
    http://mitpress.mit.edu/sicp/
  197. Pure function
    http://en.wikipedia.org/wi­ki/Pure_function
  198. Funkcionální programování
    http://cs.wikipedia.org/wi­ki/Funkcionální_programová­ní
  199. Jazyky Hy a Clojure-py: moderní dialekty LISPu určené pro Python VM
    https://www.root.cz/clanky/jazyky-hy-a-clojure-py-moderni-dialekty-lispu-urcene-pro-python-vm/
  200. Pixie: lehký skriptovací jazyk s „kouzelnými“ schopnostmi
    https://www.root.cz/clanky/pixie-lehky-skriptovaci-jazyk-s-kouzelnymi-schopnostmi/
  201. Programovací jazyk Pixie: funkce ze základní knihovny a použití FFI
    https://www.root.cz/clanky/pro­gramovaci-jazyk-pixie-funkce-ze-zakladni-knihovny-a-pouziti-ffi/
  202. Stránka projektu Jython
    http://www.jython.org/
  203. Jython (Wikipedia)
    https://en.wikipedia.org/wiki/Jython
  204. Scripting for the Java Platform (Wikipedia)
    https://en.wikipedia.org/wi­ki/Scripting_for_the_Java_Plat­form
  205. JSR 223: Scripting for the JavaTM Platform
    https://jcp.org/en/jsr/detail?id=223
  206. List of JVM languages
    https://en.wikipedia.org/wi­ki/List_of_JVM_languages
  207. The JavaTM Virtual Machine Specification, Second Edition
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/VMSpec­TOC.doc.html
  208. The class File Format
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/Clas­sFile.doc.html
  209. javap – The Java Class File Disassembler
    http://docs.oracle.com/ja­vase/1.4.2/docs/tooldocs/win­dows/javap.html
  210. javap-java-1.6.0-openjdk(1) – Linux man page
    http://linux.die.net/man/1/javap-java-1.6.0-openjdk
  211. Using javap
    http://www.idevelopment.in­fo/data/Programming/java/mis­cellaneous_java/Using_javap­.html
  212. Examine class files with the javap command
    http://www.techrepublic.com/ar­ticle/examine-class-files-with-the-javap-command/5815354

Autor článku

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