Hlavní navigace

Jazyk Joker: dialekt Clojure naprogramovaný v Go

Pavel Tišnovský

Dnes se seznámíme se základními vlastnostmi jazyka pojmenovaného Joker, který používá stejný zápis programů, jako populární Clojure. Na rozdíl od Clojure je však Joker naprogramován v Go a jeho interpret startuje prakticky okamžitě.

Doba čtení: 40 minut

Sdílet

11. Mapy

12. Množiny

13. Vliv použitého datového typu na funkci pro výpočet faktoriálu

14. Výpočet konstanty Pi Wallisovým součinem: použití reálných čísel a zlomků

15. Makro dotimes

16. Přímá rekurze

17. Tail rekurze

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

19. Literatura

20. Odkazy na Internetu

1. Programovací jazyk Joker: dialekt Clojure naprogramovaný v Go

V dnešní části seriálu o LISPovských programovacích jazycích se seznámíme se základními vlastnostmi programovacího jazyka pojmenovaného Joker [1]. Jedná se o jeden z jazyků, které se snaží používat syntaxi i sémantiku podobnou či v ideálním případě zcela shodnou s programovacím jazykem Clojure, který je (vedle některých dialektů Scheme a Common LISPu) dnes pravděpodobně nejpopulárnějším LISPovským programovacím jazykem, jenž se dokonce umisťuje i relativně vysoko v různých statistikách (na druhou stranu ovšem vypadl z první padesátky Tiobe indexu. Ovšem zatímco jazyk Clojure existuje ve třech variantách – pro JVM (překlad do bajtkódu), pro CLR (taktéž překlad do bajtkódu) a pro interpretry JavaScriptu (ClojureScript), je tomu v případě jazyka Joker jinak, protože Joker je vyvinut v programovacím jazyku Go a – alespoň prozatím – je implementován jako prostý interpret, který neprovádí překlad do bajtkódu ani do nativního kódu.

Tento přístup má své přednosti, ale pochopitelně i některé zápory. Mezi přednosti patří především velmi rychlý start interpretru, a to jak v porovnání se samotným Clojure (pro JVM), tak i například v porovnání s jazykem Racket, jemuž jsme se věnovali v předchozích částech tohoto seriálu ([2], [3], [4] a [5]) a k jehož popisu se ještě později vrátíme. Ovšem „pouhá“ interpretace kódu bez jeho (mezi)překladu a případných optimalizací má negativní vliv na celkový výkon aplikace. Projeví se to zejména u složitějších výpočtů, popř. u zpracování rozsáhlejších datových struktur, nicméně pro mnoho reálných aplikací by se nemuselo jednat o zásadní problém.

Další vlastnost jazyka Joker však může být užitečná i pro ty programátory, kteří používají originální jazyk Clojure. Joker totiž umožňuje spustit linter, který dokáže zkontrolovat korektnost zapsaného zdrojového kódu. A díky tomu, že se linter spouští velmi rychle (pod sekundu), lze tento nástroj použít i pro rychlou kontrolu zdrojových kódů aplikace psaných přímo v Clojure. Spuštění interpretru Clojure je totiž naproti tomu mnohem pomalejší a náročnější na systémové zdroje. Navíc je možné linter použít i pro rychlou kontrolu souborů uložených ve formátu EDN (Extensible Data Notation), který je taktéž založen na syntaxi programovacího jazyka Clojure. Bližší informace o tomto formátu, s nímž se ve světě Clojure relativně často setkáme, lze najít na stránce https://github.com/edn-format/edn.

Poznámka: Joker není prvním programovacím jazykem se syntaxí i sémantikou odvozenou od Clojure. Na stránkách Roota jsme se již seznámili s podobně koncipovanými jazyky Hy a Clojure-py [7], které jsou vytvořeny v Pythonu a dobře tak spolupracují s aplikacemi napsanými právě v tomto populárním jazyku.

2. Porovnání Jokeru s programovacím jazykem Clojure

Programovací jazyk Joker se v některých ohledech od jazyka Clojure odlišuje, což je však většinou pochopitelné, protože ne všechny vlastnosti JVM, popř. CLR je možné „emulovat“ v interpretru, který k těmto nástrojům nemá přístup. Některé vlastnosti Jokeru, například nabídka primitivních datových typů, je přímo ovlivněna vlastnostmi programovacího jazyka Go. Ovšem nejdůležitějším rozdílem (alespoň v současné verzi Jokeru) je neexistence podpory pro souběžný běh několika vláken, což je vlastnost podporovaná přímo v základech jazyka Clojure – viz například transakční paměť, agenti, pmap a pcalls, futures a promise atd. atd. Je to zajímavé, protože Joker má k dispozici všechny prostředky jazyka Go, především gorutiny a kanály, takže by alespoň nějakou podporu pro souběžné spouštění funkcí bylo možné naprogramovat. Možná se dočkáme až v některé vyšší verzi tohoto jazyka. S dalšími rozdíly mezi původním Clojure a Jokerem se postupně seznámíme v navazujícím textu.

Poznámka: jak jsme se již zmínili v úvodní kapitole, je interpret programovacího jazyka Joker naprogramován v jazyku Go. To mj. znamená, že Joker s Go (resp. s jeho ekosystémem) sdílí některé společné vlastnosti, například velmi snadnou instalaci na počítačích koncových uživatelů (interpret je dodáván jako jediný spustitelný soubor pouze s minimálními závislostmi na operačním systému) a rychlé spuštění, což je pro každý prakticky použitelný interpret důležitá vlastnost. Ovšem porovnáním samotného jazyka Go s Clojure se v tomto článku nebudeme zabývat. Pokud vás tato problematika zajímá, můžete si přečíst několik názorů na dané téma, které naleznete například na stránkách:
  1. Clojure vs Go – Clash of the Titans!
    https://our.status.im/clojure-vs-go/
  2. How do you see future of Clojure compared to Golang?
    https://www.reddit.com/r/Clo­jure/comments/5uftns/how_do_y­ou_see_future_of_clojure_com­pared_to/
  3. Choosing your future tech stack: Clojure vs Elixir vs Go
    https://smashingboxes.com/blog/cho­osing-your-future-tech-stack-clojure-vs-elixir-vs-go/
  4. Imagine you have to select a programming language in 2019
    https://medium.com/@yulia­oletskaya/imagine-you-have-to-select-a-programming-language-in-2019–162ddcbb6cf
Poznámka: ve skutečnosti se oblasti nasazení obou jazyků sice překrývají, ale v relativně malé oblasti, takže se většinou nejedná o přímé konkurenty.

3. Instalace interpretru programovacího jazyka Joker

Programovací jazyk Joker s poměrně velkou pravděpodobností nenaleznete v oficiálních repositářích vaší distribuce, takže je nutné instalaci provést odlišným způsobem. Archivy s binárním (spustitelným) souborem obsahujícím interpret jazyka Joker i jeho základní knihovny (vše v jednom binárním spustitelném souboru) jsou umístěny na adrese https://github.com/candid82/jo­ker/releases/tag/v0.12.7 (tabulka s archivy je zobrazena na konci stránky). Stažení archivu určeného pro 64bitovou platformu (x86–64) s Linuxem je tudíž velmi snadné:

$ wget https://github.com/candid82/joker/releases/download/v0.12.7/joker-0.12.7-linux-amd64.zip

Po rozbalení staženého archivu postačuje interpret (představovaný souborem joker) umístit do některého adresáře, na který ukazuje proměnná prostředí PATH (může se jednat například o /usr/local/bin, ~/bin atd. podle konkrétního uživatelského nastavení):

$ unzip joker-0.12.7-linux-amd64.zip
$ mv joker ~/bin

V případě, že preferujete vlastní překlad interpretru, musíte mít nainstalovány standardní nástroje programovacího jazyka Go dostupné přímo v repositáři vaší distribuce, popř. nabízené na stránce https://golang.org/dl/. Po instalaci jazyka Go je nutné provést nastavení proměnné prostředí GOPATH, což je problematika, kterou jsme se podrobněji věnovali v úvodním článku, který zahájil paralelně běžící seriál o Go. Samotný překlad se provede těmito pomocí kroků popsaných v následujících odstavcích.

Poznámka: pro úspěšný překlad projektu Joker je nezbytně nutné mít nainstalován jazyk Go verze 1.12 nebo 1.13. V některých Linuxových distribucích se stále nabízí Go 1.11 (možná dokonce i Go 1.10), ovšem tato verze programovacího jazyka Go neobsahuje ve svých standardních knihovnách všechny potřebné funkce a metody. Z tohoto důvodu nebude překlad úspěšný.

Samotný proces překladu Jokeru není příliš složitý ani zdlouhavý:

Před vlastním překladem nejdříve získáme zdrojové kódy, a to s využitím příkazu go get:

$ go get -d github.com/candid82/joker

Následně přejdeme do adresáře, který se předchozím příkazem vytvořil a naplnil zdrojovými kódy:

$ cd $GOPATH/src/github.com/candid82/joker

Překlad spustíme přes skript run.sh a standardním příkazem go install:

$ ./run.sh --version && go install

Výsledkem překladu by měl být spustitelný binární soubor o velikosti přibližně 15 MB. Jedná se o poměrně značnou velikost, což je způsobeno tím, že aplikace vytvořené v Go jsou překládány a linkovány se všemi knihovnami, včetně knihovny pro automatickou správu paměti (GC) apod.:

$ ls -l joker 
-rwxr-xr-x 1 tester tester 15248928 zář  3 06:53 joker

4. Spuštění interaktivní smyčky REPL

Po (doufejme že bezproblémovém a úspěšném) překladu a instalaci programovacího jazyka Joker si můžeme vyzkoušet spustit interaktivní smyčku REPL, kterou je tento jazyk – ostatně jako každý správný jazyk patřící do LISPovské rodiny – vybaven. V případě, že binární soubor s interpretrem leží v nějakém adresáři umístěném do proměnné prostředí PATH, je spuštění REPLu snadné a navíc prakticky okamžité:

$ ./joker
Welcome to joker v0.12.7. Use EOF (Ctrl-D) or SIGINT (Ctrl-C) to exit.

Alternativně (pokud byl překlad proveden uživatelem ze zdrojových kódů):

$ $GOPATH/bin/joker
Welcome to joker v0.12.7. Use EOF (Ctrl-D) or SIGINT (Ctrl-C) to exit.

Pod uvítací zprávou se zobrazí výzva (prompt), která jako by z oka vypadla výzvě známé z programovacího jazyka Clojure:

user=>
Poznámka: pokud máte na svém systému nainstalován i programovací jazyk Clojure, popř. doplněný o nástroj Leiningen, můžete si sami porovnat, jak rychlé je spuštění interaktivní smyčky REPL jazyka Joker a podobně koncipované interaktivní smyčky REPL, tentokrát pro jazyk Clojure:
$ lein repl

Ve druhém případě trvá spuštění REPLu cca pět sekund i na poměrně rychlém stroji s mikroprocesorem i7 taktovaným na 2,9 GHz, ovšem na pomalejších počítačích může spuštění interaktivní smyčky REPL trvat i více než deset sekund, což je (například pro rychlé otestování nějaké myšlenky) poměrně nepříjemné zdržení. Naproti tomu se interpret jazyka Joker spustí i na pomalejších počítačích takřka okamžitě, takže se skutečně jedná o výborný doplněk ke Clojure určený například k rychlému otestování nových nápadů apod.

5. Spuštění skriptu

Kromě přímého přechodu do interaktivní smyčky REPL je možné spustit nějaký skript naprogramovaný v jazyce Joker. Spuštění skriptu je snadné a přímočaré:

$ joker jméno_skriptu

Skripty vytvořené v jazyce Joker by měly mít koncovku .joker, ovšem bez problémů lze použít i koncovku .clj, která může být výhodnější; už jen z toho důvodu, že tuto koncovku rozpoznávají mnohé programátorské textové editory.

Poznámka: i z tohoto důvodu budou všechny dnešní zdrojové kódy demonstračních příkladů uloženy do souborů s koncovkou .clj. Tyto zdrojové soubory naleznete na adrese https://github.com/tisnik/lisp-families/tree/master/joker.

Příklad spuštění skriptu pro výpočet hodnoty Pi:

$ ./joker pi_1.clj 
 
1       4.000000
2       3.555556
4       3.413333
8       3.302394
16      3.230036
32      3.188127
64      3.165482
128     3.153699
256     3.147687
512     3.144650
1024    3.143124
2048    3.142359
4096    3.141976
8192    3.141784
16384   3.141689
32768   3.141641
65536   3.141617

6. Režim linteru

Jak jsme si již řekli v úvodní kapitole, je možné interpret programovacího jazyka Joker přepnout do režimu linteru, v němž se provádí kontrola zdrojového kódu (či dat), ovšem bez jeho spuštění. Základní varianta příkazu vypadá takto:

$ joker --lint jméno_skriptu

Ve skutečnosti je ještě možné přesně specifikovat dialekt určující, jaký zdrojový kód je očekáván na vstupu:

$ joker --lint --dialect dialekt jméno_skriptu

Mezi podporované dialekty patří:

# Volba –dialect Význam
1 clj zdrojový soubor určený pro Clojure
2 cljs zdrojový soubor určený pro ClojureScript
3 joker zdrojový soubor určený pro Joker
4 edn soubor ve formátu EDN (Extensible Data Notation)

Příklad výstupu linteru:

raster_renderer.clj:177:21: Parse warning: Wrong number of args (7) passed to graph-generator.raster-renderer/draw-line
raster_renderer.clj:178:21: Parse warning: Wrong number of args (7) passed to graph-generator.raster-renderer/draw-circle
raster_renderer.clj:179:21: Parse warning: Wrong number of args (7) passed to graph-generator.raster-renderer/draw-arc
raster_renderer.clj:180:21: Parse warning: Wrong number of args (7) passed to graph-generator.raster-renderer/draw-text
raster_renderer.clj:174:13: Parse warning: unused binding: i
raster_renderer.clj:303:13: Parse warning: unused binding: i
raster_renderer.clj:432:19: Parse warning: unused binding: bounds
raster_renderer.clj:771:11: Parse warning: unused binding: room-id
raster_renderer.clj:29:12: Parse warning: unused namespace graph-generator.db-interface
Poznámka: kupodivu se na tyto chyby dříve nepřišlo, mj. i proto, že se jednalo o netestovanou a nenasazovanou část zdrojového kódu. Interpret Clojure tyto kontroly bez dalších nástrojů neprovádí.

7. Základní jazykové konstrukce podporované Jokerem

V této kapitole se zmíníme o základních konceptech, na nichž je jazyk Joker postaven. Samotný popis jednotlivých konstrukcí bude poměrně stručný, a to z toho důvodu, že se Joker v mnoha ohledech podobá programovacímu jazyku Clojure, kterému jsme se na stránkách Rootu zevrubně věnovali.

Nejprve se ve stručnosti zmíníme o primitivních datových typech. Ty jsou v Jokeru podobné, jako je tomu v Go, ovšem konkrétní implementace (a někdy i chování) se může nepatrně odlišovat:

; hodnota nil použitelná i v logických výrazech
user=> nil
nil
 
; celočíselný typ int v Go
user=> 42
42
 
; je možné použít i hexadecimální zápis celých hodnot
user=> 0x2a
42
 
; Joker podporuje zlomky (typ ratio) s prakticky neomezeným rozsahem hodnot čitatele i jmenovatele
user=> 1/3
1/3
 
; a samozřejmě i čísla s plovoucí řádovou čárkou (typ float64)
user=> 3.1415
3.1415
 
; řetězce
user=> "foo bar baz"
"foo bar baz"
 
; funkce count pracuje korektně i s Unicode řetězci
user> (count "abcde")
5
user> (count "ěščřž")
5
; symboly
user=> :foo-bar-baz
:foo-bar-baz

Zapomenout nesmíme ani na celočíselný typ bez omezení rozsahu reprezentovatelných hodnot. Interně je tento typ implementován pomocí balíčku big.Int:

user=> 1N
1N

Nechybí ani numerický typ s plovoucí řádovou čárkou a prakticky nekonečným rozsahem, popř. přesností. Zde se používá postfixový znak M a interně je tento typ implementován pomocí balíčku big.Float:

user=> 1M
1M
 
user=> 1.5e100M
1.5e+100M

Dále si v této kapitole ukažme základní volání funkce:

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

Důležitá je i speciální forma nazvaná def. Ta se většinou používá k navázání libovolné hodnoty (například čísla, pravdivostní hodnoty, řetězce, seznamu a jak uvidíme dále, tak i funkce) na symbol. Méně časté je použití této speciální formy k pouhému vytvoření symbolu. V případě, že Joker vyhodnotí („spustí“) tuto speciální formu, dojde k vytvoření nové globální proměnné v aktuálně nastaveném jmenném prostoru (nejde tedy o skutečnou globální proměnnou, ale o proměnnou identifikovatelnou přes jmenný prostor – viz další text) a k inicializaci této proměnné. Pokud již globální proměnná stejného jména existuje, dojde k „přepisu“ její hodnoty. Ve skutečnosti však stará hodnota nemusí přestat existovat, protože může být navázána na další proměnné:

user=> (def x (range 10))
#'user/x

Vyhodnocení hodnoty navázané na symbol:

user=> x
(0 1 2 3 4 5 6 7 8 9)

Nepatrně složitější příklad:

user> (def x 6)
#'user/x
 
user> (def y 7)
#'user/y
  
user> (def answer (* x y))
#'user/answer
 
user> answer
42

Ke globální proměnné lze přiřadit i takzvaný dokumentační řetězec a ten následně získat makrem doc:

user> (def answer "Odpoved na otazku o ... vesmiru, zivote a vubec" 42)
#'user/answer
 
user> (doc answer)
-------------------------
user/answer
  Odpoved na otazku o ... vesmiru, zivote a vubec

8. Jmenné prostory

V předchozí kapitole jsme se poprvé explicitně zmínili o takzvaných jmenných prostorech. Jmenné prostory byly do programovacího jazyka Joker (resp. do Clojure a potom do Jokeru) přidány zejména z toho důvodu, že použití globálních symbolů je v reálných programech velmi nebezpečné a to zejména proto, že jiná část programu, která může být klidně vytvořena i jiným vývojářem, může nechtěně předeklarovat již existující globální symbol. Co je ještě horší – tato předeklarace nemusí nutně vést k okamžité chybě při práci s programem (ideálně při jeho testování), ale může se projevit až při určité shodě okolností – podle všeobecně platného zákona tedy ve chvíli, kdy se aplikace předvádí šéfovi či zákazníkovi :-). Připomeňme si, že mezi globální symboly patří i symboly představující jména funkcí, takže je asi představitelné, co by se stalo, kdyby nějaká importovaná knihovna náhodou obsahovala funkci pojmenovanou stejně, jako funkce vytvořená programátorem vyvíjené aplikace. Jmenné prostory proto představují jeden z možných způsobů, jak tento problém poměrně elegantně vyřešit (i když práce s nimi není vždy jednoduchá).

My jsme se již vlastně s jedním jmenným prostorem setkali v textech vypisovaných smyčkou REPL, i když jsme si prozatím nevysvětlili, že se skutečně jedná o jmenný symbol. Při používání smyčky REPL je totiž jméno aktuálního jmenného prostoru vypisováno jako součást výzvy (prompt):

user=>

V programovacím jazyku Joker je možné vytvořit takřka libovolný počet jmenných prostorů a posléze se mezi těmito jmennými prostory přepínat, tj. lze zvolit, který jmenný prostor bude jmenným prostorem aktuálním. Pro tuto činnost se používá makro nazvané ns:

(ns název_jmenného_prostoru)

Podívejme se nyní na jednoduchý demonstrační příklad, v němž jsou vytvořeny dvě globální proměnné nazvané answer. Každé proměnné je přiřazena jiná hodnota a každá proměnná tudíž musí být uložena v jiném jmenném prostoru. Povšimněte si taktéž toho, jak se změní výzva (prompt) při přepnutí aktuálního jmenného prostoru:

; vytvoření globální proměnné umístěné ve jmenném prostoru "user"
user=> (def answer 42)
#'user/answer
 
; jméno proměnné se vyhodnotí na hodnotu proměnné
user=> answer
42
 
; vytvoření nového jmenného prostoru nazvaného "novy"
user=> (ns novy)
nil
 
; lze v tomto jmenném prostoru vyhodnotit (=najít) proměnnou answer?
novy=> answer
CompilerException java.lang.RuntimeException: Unable to resolve symbol: answer
in this context, compiling:(NO_SOURCE_PATH:0)
 
; vytvoření nové globální proměnné ve jmenném prostoru "novy"
novy=> (def answer "?")
#'novy/answer
 
; její hodnotu nyní můžeme získat (vyhodnotit), aniž by došlo k chybě
novy=> answer
"?"
 
; přepnutí jmenného prostoru
novy=> (ns user)
nil
 
; nyní je opět viditelná první globální proměnná se jménem answer
user=> answer
42

Ve skutečnosti však nejsou jednotlivé jmenné prostory od sebe izolovány, takže se můžeme odkazovat na symbol umístěný v jiném jmenném prostoru pomocí zápisu jmenný_prostor/symbol. Ostatně i kvůli podpoře tohoto způsobu zápisu není možné použít znak / ve jméně žádného symbolu (znaky * či – je však možné použít, což se taktéž často děje, protože – se používá pro oddělení jednotlivých slov v názvu symbolu a hvězdička je podle konvencí používána pro konstanty). Podívejme se nyní na způsob využití zápisu jmenný_prostor/symbol:

; přepnutí jmenného prostoru
user=> (ns user)
nil
 
; proměnná z aktuálního jmenného prostoru
user=> answer
42
 
; proměnná z jiného jmenného prostoru
user=> novy/answer
"?"
 
; přepnutí jmenného prostoru
user=> (ns novy)
nil
 
; proměnná z aktuálního jmenného prostoru
novy=> answer
"?"
 
; proměnná z aktuálního jmenného prostoru
novy=> novy/answer
"?"
 
; proměnná z jiného jmenného prostoru
novy=> user/answer
42

9. Práce se seznamy

Programovací jazyk Joker podporuje všechny základní strukturované datové typy, které známe z jazyka Clojure. Jedná se o seznamy, vektory, mapy i množiny:

# Typ kolekce Zápis konstruktoru
1 Seznam '(prvky)
2 Vektor [prvky]
3 Mapa {dvojice klíč-hodnota}
4 Množina #{unikátní prvky}

Základním strukturovaným datovým typem každého LISPovského programovacího jazyka jsou nepochybně seznamy (list). Jejich zpracování je v jazyku Joker podobné jako v Clojure – prvky seznamů se zapisují do kulatých závorek, což je ovšem stejná syntaxe, jako zápis volání funkce. Aby se seznam nevyhodnocoval jako funkce (tedy aby první prvek seznamu nebyl chápán jako jméno funkce), je nutné před jeho deklaraci zapsat znak ' (apostrof) nebo použít speciální formu quote.

Pochopitelně je podporován i koncept prázdného seznamu, který se zapisuje ve formě prázdného páru kulatých závorek:

; prázdný seznam
user=> '()
()
 
; prázdný seznam
user=> (quote ())
()

Seznam obsahující čtveřici numerických hodnot se vytvoří následujícím způsobem:

; seznam čísel
user=> '(1 2 3 4)
(1 2 3 4)
 
; seznam čísel
user=> (quote (1 2 3 4))
(1 2 3 4)

Seznam obsahující trojici řetězců vytvoříme naprosto stejným postupem:

; seznam řetězců
user=> '("prvni" "druhy" "treti")
("prvni" "druhy" "treti")

Poměrně často se setkáme s použitím keywords, které jsou určeny pro reprezentaci neměnných a současně i unikátních hodnot (unikátních v rámci celého programu, popř. jmenných prostorů):

; seznam "keywords"
user=> '(:prvni :druhy :treti)
(:prvni :druhy :treti)
 
user=> (quote (:foo :bar :baz))
(:foo :bar :baz)

Dále je pochopitelně možné do seznamů ukládat proměnné, jejichž hodnota však není vyhodnocována. To je ostatně logické, protože není vyhodnocen ani samotný seznam a pravidla pro vyhodnocení jsou v LISPovských jazycích jednoduchá (až na makra, která naopak takové vyhodnocení umožňují):

user=> (def positionX 1)
#'user/positionX
 
user=> (def positionY 2)
#'user/positionY
 
user=> (def positionZ 3)
#'user/positionZ

Vytvoření seznamu, v němž jsou uloženy proměnné:

; seznam s proměnnými
user=> '(positionX positionY positionZ)
(positionX positionY positionZ)

Ukažme si ještě několik příkladů vnořených seznamů:

; vnořené seznamy
user=> '( '(:x :y) '(:z :w) )
((quote (:x :y)) (quote (:z :w)))

Díky tomu, že se nevyhodnocuje už vnější seznam, můžeme použít i následující zápis (který však není ekvivalentní s předchozím zápisem – výsledkem je odlišná datová struktura):

; vnořené seznamy
user=> '( '(:x :y) '(:z :w) )
((:x :y) (:z :w))

Délka seznamu:

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

Zploštění seznamu je další relativně často používaná operace:

user> (flatten '( (:x :y) (:z :w) ))
(:x :y :z :w)

10. Vektory

Další důležitou datovou strukturou, která je převzata z programovacího jazyka Clojure, jsou vektory (vectors). Prvky vektorů se zapisují do hranatých závorek a vzhledem k tomu, že se jedná o zápis odlišný od volání funkce (na rozdíl od seznamů), není nutné před vektor psát ani apostrof ani speciální formu quote. Prázdný vektor se zapíše takto:

; prázdný vektor
user=> []
[]

Vektor se čtveřicí celočíselných hodnot můžeme zapsat následovně:

; vektor čísel
user=> [1 2 3 4]
[1 2 3 4]

Pochopitelně nám nic nebrání si vytvořit vektor z řetězců

; vektor řetězců
user=> ["prvni" "druhy" "treti"]
["prvni" "druhy" "treti"]

Vektor obsahující „keywords“:

; vektor "keywords"
user=> [:prvni :druhy :treti]
[:prvni :druhy :treti]

A pochopitelně i vektor proměnných:

user=> (def positionX 1)
#'user/positionX
 
user=> (def positionY 2)
#'user/positionY
 
user=> (def positionZ 3)
#'user/positionZ
 
; vektor proměnných
user=> [positionX positionY positionZ]
[1 2 3]
Poznámka: povšimněte si, že se proměnné vyhodnotily na svoje hodnoty.

Další často vyžadované operace se seznamy:

; zploštění vektorů (výsledkem je seznam!)
user> (flatten [[:x :y] [:z :w]])
(:x :y :z :w)
 
; přístup k prvku vektoru
user> (nth [1 2 3 4] 2)
3
 
; neexistující prvek
user> (nth [1 2 3 4] 10)
<joker.core>:693:25: Eval error: Index 10 is out of bounds [0..3]
Stacktrace:
  global </gcrepl>/gc:26:1
  core/nth </gcjoker.core>/gc:693:25
 
; přístup k prvku vektoru bez rizika vzniku chyb
user> (get [1 2 3 4] 2)
3
 
; neexistující prvek
user> (get [1 2 3 4] 10)
nil

11. Mapy

Mapa, popř. též asociativní pole, se v reálných aplikacích používá poměrně často a existuje pro ni i speciální konstrukce spočívající v použití složených závorek, do nichž se zapíšou dvojice klíč-hodnota:

; prázdná mapa
user=> {}
{}

Mapa s řetězci:

; mapování typu string-string
user=> {"prvni" "first" "druhy" "second" "treti" "third"}
{"prvni" "first", "druhy" "second", "treti" "third"}

Lze použít i čárky, které zajistí větší čitelnost (je zřejmé, kde končí která dvojice:)

; mapování typu string-string
user=> {"prvni" "first", "druhy" "second", "treti" "third"}
{"prvni" "first", "druhy" "second", "treti" "third"}

Dále je vhodné si uvědomit, že prvky mapy jsou nejdříve vyhodnoceny:

; mapa s vyhodnocením proměnných
user=> {"X" positionX "y" positionY "z" positionZ}
{"X" 1, "y" 2, "z" 3}

Převod vektorů/seznamů klíčů a vektorů/seznamů hodnot na mapu:

; stejný počet klíčů i hodnot
user> (zipmap [1 2 3 4] [5 6 7 8])
{1 5, 2 6, 3 7, 4 8}
 
; větší počet hodnot
user> (zipmap [1 2 3 4] [5 6 7 8 9 0])
{1 5, 2 6, 3 7, 4 8}

Přístup k hodnotám uloženým v mapě:

user> (def slova {"prvni" "first", "druhy" "second", "treti" "third"})
#'user/slova
 
user> (get slova "prvni")
"first"
 
user> (get slova "foobar")
nil

Test na existenci prvku (podle klíče):

user> (def slova {"prvni" "first", "druhy" "second", "treti" "third"})
#'user/slova
 
user> (contains? slova "prvni")
true
 
user> (contains? slova "foobar")
false

Výběr většího množství prvků:

user> (select-keys slova ["prvni" "treti"])
{"prvni" "first", "treti" "third"}

12. Množiny

V jazyku Joker lze pochopitelně pracovat i s množinami. Ty se vytváří konstruktorem #{}:

; Množina
user=> #{"prvni" "druhy" "treti"}
#{"prvni" "druhy" "treti"}
Poznámka: ve skutečnosti nemusí být prvky v množině vypsány ve stejném pořadí, jak do ní byly vloženy, interně se totiž používá hešovací tabulka).

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

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

user=> #{1 1 3}
Read error: Duplicate set element 1

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

user=> #{"nesmi" "mit" "dva" "stejne" "prvky" "skutecne" "nesmi"}
Read error: Duplicate set element nesmi

Joker poctivě zkontroluje i ekvivalenci seznamů atd.:

user=> #{ '(:stejny :seznam) '(:stejny :seznam) }
Read error: Duplicate set element (quote (:stejny :seznam))
Poznámka: zde můžeme vidět odlišnost od jazyka Clojure – chybové zprávy Jokeru jsou většinou kratší a současně i specifičtější, než je tomu v Clojure:
user=> #{1 1 3}
IllegalArgumentException Duplicate key: 1  clojure.lang.PersistentHashSet.create
WithCheck (PersistentHashSet.java:68)

13. Vliv použitého datového typu na funkci pro výpočet faktoriálu

V sedmé kapitole jsme se mj. zmínili o tom, že v jazyku Joker existuje hned několik datových typů určených pro reprezentaci numerických hodnot. Nyní si ukážeme, jaký vliv má zvolený datový typ na některé výpočty. Připomeňme si deklaraci funkce pro výpočet faktoriálu. Její zkrácená varianta zapsatelná na jediný řádek využívá funkci vyššího řádu reduce a generátor sekvence range. Všechny výpočty přitom probíhají s využitím datového typu „celé číslo“, který má ovšem omezený rozsah hodnot, což se projeví na výpočtu, přesněji řečeno na jeho výsledcích (interně se používá Go typ int):

user=> (defn factorial [n] (reduce * (range 1 (inc n))))
#'user/factorial

Výpočet faktoriálu si můžeme ověřit například tak, že se pokusíme vypočítat výsledky pro hodnoty 0! až 29!. V oboru celých čísel ovšem výsledky nevypadají příliš korektně:

user=> (doseq [n (range 0 30)] (println n (factorial n)))
0 1
1 1
2 2
3 6
4 24
5 120
6 720
7 5040
8 40320
9 362880
10 3628800
11 39916800
12 479001600
13 6227020800
14 87178291200
15 1307674368000
16 20922789888000
17 355687428096000
18 6402373705728000
19 121645100408832000
20 2432902008176640000
21 -4249290049419214848
22 -1250660718674968576
23 8128291617894825984
24 -7835185981329244160
25 7034535277573963776
26 -1569523520172457984
27 -5483646897237262336
28 -5968160532966932480
29 -7055958792655077376

Lepšího výsledku dosáhneme při použití čísel s plovoucí řádovou čárkou (interně typ float64):

user> (defn factorial [n] (reduce * (range 1.0 (inc n))))
#'user/factorial
 
user> (doseq [n (range 0 30)] (println n (factorial n)))
0 1
1 1
2 2
3 6
4 24
5 120
6 720
7 5040
8 40320
9 362880
10 3.6288e+06
11 3.99168e+07
12 4.790016e+08
13 6.2270208e+09
14 8.71782912e+10
15 1.307674368e+12
16 2.0922789888e+13
17 3.55687428096e+14
18 6.402373705728e+15
19 1.21645100408832e+17
20 2.43290200817664e+18
21 5.109094217170944e+19
22 1.1240007277776077e+21
23 2.585201673888498e+22
24 6.204484017332394e+23
25 1.5511210043330986e+25
26 4.0329146112660565e+26
27 1.0888869450418352e+28
28 3.0488834461171384e+29
29 8.841761993739701e+30

Tyto výsledky vypadají lépe, ovšem například 1000! přesáhne rozsah typu float64:

user> (factorial 1000)
+Inf

Třetí varianta používá celá čísla s „nekonečným“ rozsahem:

user> (defn factorial [n] (reduce * (range 1N (inc n))))

Jedná se o nejlepší datový typ téměř přesně stvořený pro výpočet faktoriálů:

user> (doseq [n (range 0 30)] (println n (factorial n)))
0 1
1 1N
2 2N
3 6N
4 24N
5 120N
6 720N
7 5040N
8 40320N
9 362880N
10 3628800N
11 39916800N
12 479001600N
13 6227020800N
14 87178291200N
15 1307674368000N
16 20922789888000N
17 355687428096000N
18 6402373705728000N
19 121645100408832000N
20 2432902008176640000N
21 51090942171709440000N
22 1124000727777607680000N
23 25852016738884976640000N
24 620448401733239439360000N
25 15511210043330985984000000N
26 403291461126605635584000000N
27 10888869450418352160768000000N
28 304888344611713860501504000000N
29 8841761993739701954543616000000N

Problém nenastane ani při výpočtu 1000!:

#'user/factorial
user> (factorial 1000)
4023872600770937735437024339230039857193748642107146325437999
1042993851239862902059204420848696940480047998861019719605863
1666872994808558901323829669944590997424504087073759918823627
...
...
...
2301353580818400969963725242305608559037006242712434169090041
5369010593398383577793941097002775347200000000000000000000000
0000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000N

14. Výpočet konstanty Pi Wallisovým součinem: použití reálných čísel a zlomků

Podívejme se nyní na použití různých datových typů na jednoduchém algoritmu určeném pro výpočet hodnoty čísla Pi s využitím takzvaného Wallisova součinu (Wallis product, viz též https://en.wikipedia.org/wi­ki/Wallis_product). Výpočet používající hodnoty s plovoucí řádovou čárkou může být implementován například takto (používáme zde programovou smyčku, i když by bylo možné algoritmus přepsat za pomoci rekurze – což je méně efektivní):

(defn compute-pi
    [n]
    (loop [pi 4.0
           i  3]
           (if (< i (+ n 2))
               (recur (* pi (/ (- i 1) i) (/ (+ i 1) i))
                      (+ i 2))
               pi)))
 
 
(loop [n 1]
    (print n "\t")
    (println (compute-pi n))
    (if (< n 500000)
        (recur (* n 2))))

Výsledky budou sice vypočteny rychle, ovšem přesnost se od určitého bodu již nebude zvyšovat:

1       4.000000
2       3.555556
4       3.413333
8       3.302394
16      3.230036
32      3.188127
64      3.165482
128     3.153699
256     3.147687
512     3.144650
1024    3.143124
2048    3.142359
4096    3.141976
8192    3.141784
16384   3.141689
32768   3.141641
65536   3.141617
131072  3.141605
262144  3.141599
524288  3.141596

Druhá varianta používá čísla s teoreticky neomezenou přesností:

(defn compute-pi
    [n]
    (loop [pi 4M
           i  3]
           (if (< i (+ n 2))
               (recur (* pi (/ (- i 1) i) (/ (+ i 1) i))
                      (+ i 2))
               pi)))
 
 
(loop [n 1]
    (print n "\t")
    (println (compute-pi n))
    (if (< n 500000)
        (recur (* n 2))))

Výpočet je již znatelně pomalejší, ovšem Pi lze (s dostatkem času a paměti) vypočítat s libovolnou přesností, i když používáme jeden z nejpomaleji konvergujících algoritmů:

1       4M
2       3.5555555555555551608095912443887965835546460817611862740078511498087454612005M
4       3.41333333333333301753656188439991388456296898300406018291771319944995746157833M
8       3.30239355001259692280920194400154772374586363706317216774086896671698557673493M
16      3.23003646641171644532889395147155628591824505919549219334948928591485866419726M
32      3.1881271694471385210138122971756460360357166286716599658017344451317225348676M
64      3.16548206003479301000359223272779662079847690689602323017497171003960000199875M
128     3.15369884909579662233696379914525164935458647351778549138013266158467482689623M
256     3.14768689955642097012774122086854615055254871426894364462136545058514574740573M
512     3.1446501625172043342721016716151822642187838865539328374058640031469372655216M
1024    3.14312401702818716513564658579506676971691498322295763444751506627862492954596M
2048    3.14235898912177474909453737557243618376555954108506806271913844464157411813212M
4096    3.14197598500560203352568445241891251993592823431462623506515693992611545990133M
8192    3.14178436023473709091057936359290673473145653612318588673863895561787882156406M
16384   3.14168851714957705626885770501460708755455187703107092416262443647651781577763M
32768   3.1416405879293422812335507015910306209939040223297184216375211890918980802193M
65536   3.14161662139946575975135338281062749093860415881901622734854996519879152066546M
131072  3.1416046376545461304633895559846886819121577906409706156493279815083072754397M
262144  3.14159864566195543783459196307309124809158749182342203241630155983916009242113M
524288  3.14159564963570920196181065052181394692878789940139554703274845924206554511736M

Použít můžeme i zlomky, které jsou na konci převáděny na reálná čísla:

(defn compute-pi
    [n]
    (loop [pi 4/1
           i  3]
           (if (< i (+ n 2))
               (recur (* pi (/ (- i 1) i) (/ (+ i 1) i))
                      (+ i 2))
               pi)))
 
 
(loop [n 1]
    (print n "\t")
    (println (double (compute-pi n)))
    (if (< n 500000)
        (recur (* n 2))))

Výpočet je v tomto případě nejpomalejší:

1       4.000000
2       3.555556
4       3.413333
8       3.302394
16      3.230036
32      3.188127
64      3.165482
128     3.153699
256     3.147687
512     3.144650
1024    3.143124
2048    3.142359
4096    3.141976
8192    3.141784
16384   3.141689

15. Makro dotimes

V některých programech může být poměrně užitečné makro nazvané jednoduše a přitom příhodně dotimes, které dokáže nějaký výraz (formu) opakovat n krát. Přitom toto makro může v každé iteraci (opakování) nastavit zvolenou lokální proměnnou na aktuální hodnotu počitadla, přičemž se hodnota počitadla v první iteraci vždy nastavuje na nulu a v poslední iteraci dosahuje zadaného počtu opakování-1. Vzdáleně tedy můžeme toto makro považovat za ekvivalent programové smyčky for i in range(n): v programovacím jazyku Python či ekvivalent k počítané smyčce for (int i = 0; i<n; i++) známé z céčka (zde bez možnosti mít lokální proměnnou jako počitadlo), C++, Javy atd. Vzhledem k tomu, že se předpokládá, že forma – tělo smyčky – předaná makru dotimes bude mít nějaký vedlejší efekt, nejedná se sice o čistě funkcionální přístup, nicméně makro dotimes může být skutečně velmi užitečné.

V jednoduchém demonstračním příkladu, který si ukážeme, se na standardní výstup vypisuje převrácená hodnota celých čísel od 0 do 9. Vedlejším efektem je v tomto případě samotný výpis na standardní výstup:

user=> (dotimes [i 10] (println (/ 1.0 i)))
+Inf
1.000000
0.500000
0.333333
0.250000
0.200000
0.166667
0.142857
0.125000
0.111111
nil

Poznámka: poslední vypsané nil je návratovou hodnotou samotného makra dotimes, nikoli výsledek poslední iterace)

Podívejme se nyní na poněkud složitější příklad, který by se v imperativních programovacích jazycích většinou řešil s využitím dvojice do sebe vnořených počítaných programových smyček. Mějme za úkol vypsat tabulku malé násobilky, tj. všechny výsledky vzniklé vynásobením dvojic celých čísel od 1 do 10. Tento algoritmus je možné velmi snadno realizovat právě s využitím makra dotimes, například následujícím one-linerem:

(dotimes [i 10] (dotimes [j 10] (print (* (+ i 1) (+ j 1)) "\t")) (println))

Pro větší přehlednost si můžeme výše uvedený one-liner přepsat na správně odsazený program, z něhož je patrné, že se skutečně jedná o ekvivalent dvou do sebe zanořených programových smyček:

(dotimes [i 10]
    (dotimes [j 10]
        (print (* (inc i) (inc j)) "\t"))
    (println))

A zde je již výsledek práce tohoto programu (poslední nil je opět návratovou hodnotou makra dotimes):

1     2     3     4     5     6     7     8     9     10
2     4     6     8     10    12    14    16    18    20
3     6     9     12    15    18    21    24    27    30
4     8     12    16    20    24    28    32    36    40
5     10    15    20    25    30    35    40    45    50
6     12    18    24    30    36    42    48    54    60
7     14    21    28    35    42    49    56    63    70
8     16    24    32    40    48    56    64    72    80
9     18    27    36    45    54    63    72    81    90
10    20    30    40    50    60    70    80    90    100
nil

16. Přímá rekurze

Všechny funkcionální jazyky podporují přímou rekurzi, takže se podívejme na její zápis. Typický školní příklad pro výpočet faktoriálu můžeme zapsat následovně:

(defn fact
    [n]
    (if (<= n 1)
        1
        (* n (fact (- n 1)))))

Vcelku snadno však může nastat situace, kdy se zcela zaplní paměť s návratovými body i parametry rekurzivně volané funkce:

user> (fact 1000000000)
runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow

V takovém případě celý interpret zhavaruje.

17. Tail rekurze

Důvod, proč předchozí volání funkce fact skončilo s chybou, spočívá v tom, že došlo k přeplnění zásobníku při rekurzivním volání. Na zásobník se totiž musí ukládat parametry předávané volané funkci a taktéž body návratu (zjednodušeně řečeno návratové adresy). Aby k přetečení zásobníku nedocházelo, můžeme naši funkci fact upravit tak, aby se využívalo takzvané tail rekurze. Velmi zjednodušeně řečeno je tail rekurze použita tehdy, pokud je posledním příkazem nějaké funkce příkaz pro rekurzivní volání té samé funkce. V tomto případě se nemusí na zásobník nic ukládat a namísto toho se prostě provede skok. V Jokeru se však musí tail rekurze zapsat explicitně, což má své přednosti i zápory (podle mě převažují přednosti, protože již ze zápisu programu je zcela zřejmé, kdy k tail rekurzi skutečně dojde).

Explicitní zápis rekurze spočívá ve využití speciální formy recur, která se zapíše přesně do místa, kde má k tail rekurzi (=skoku) dojít:

(defn fact
    ([n]
     (fact n 1))
    ([n acc]
     (if (<= n 1)
         acc
         (recur (dec n) (* acc n)))))
Poznámka: v tomto příkladu používáme ještě jednu důležitou vlastnost Jokeru – funkci s více aritami, resp. přesněji řečeno s větším množstvím variant, které se od sebe odlišují počtem parametrů.

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 01_data_types.clj základní datové typy https://github.com/tisnik/lisp-families/blob/master/joker/01_da­ta_types.clj
2 02_collections.clj kolekce a další strukturované datové typy https://github.com/tisnik/lisp-families/blob/master/joker/02_co­llections.clj
3 03_arithmetic_functions.clj test překladu aritmetických výrazů https://github.com/tisnik/lisp-families/blob/master/joker/03_a­rithmetic_functions.clj
4 04_relop.clj test překladu relačních výrazů https://github.com/tisnik/lisp-families/blob/master/joker/04_re­lop.clj
5 05_boolean_op.clj test překladu booleovských výrazů https://github.com/tisnik/lisp-families/blob/master/joker/05_bo­olean_op.clj
6 06_factorial1.clj výpočet faktoriálu v oboru int https://github.com/tisnik/lisp-families/blob/master/joker/06_fac­torial1.clj
7 07_factorial2.clj výpočet faktoriálu v oboru float32 https://github.com/tisnik/lisp-families/blob/master/joker/07_fac­torial2.clj
8 08_factorial3.clj výpočet faktoriálu v oboru big.Int https://github.com/tisnik/lisp-families/blob/master/joker/08_fac­torial3.clj
9 09_pi1.clj výpočet konstanty Pi, první varianta https://github.com/tisnik/lisp-families/blob/master/joker/09_pi.clj
10 10_pi2.clj výpočet konstanty Pi, druhá varianta https://github.com/tisnik/lisp-families/blob/master/joker/10_pi.clj
11 11_pi3.clj výpočet konstanty Pi, třetí varianta https://github.com/tisnik/lisp-families/blob/master/joker/11_pi.clj
12 12_dotimes.clj makro dotimes https://github.com/tisnik/lisp-families/blob/master/joker/12_do­times.clj

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
  24. Graham Hutton
    A tutorial on the universality andexpressiveness of fold
    http://www.cs.nott.ac.uk/~pszgmh/fol­d.pdf

20. Odkazy na Internetu

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