Obsah
1. Programovací jazyk Clojure – práce s mapami a množinami
3. Implementace clojure.lang.PersistentHashMap
5. ArrayNode a BitmapIndexedNode
6. Operace s mapami: assoc a dissoc
7. Vyhledávání prvků, získání klíčů a hodnot
9. Práce s množinami v jazyce Clojure
11. Odkazy na předchozí části tohoto seriálu
1. Programovací jazyk Clojure – práce s mapami a množinami
Ve dvacáté první části seriálu o programovacím jazyku Clojure i o knihovnách, které jsou pro tento jazyk dostupné, jsme se zaměřili na popis práce se seznamy a vektory. Jedná se o základní strukturované datové typy, které jsou v Clojure používané ve všech programech. Díky homoikonicitě se navíc seznamy a vektory používají i přímo pro zápis algoritmů – každá definice, funkce či makro jsou reprezentovány stromem sestrojeným z vnořených seznamů a vektorů (vektory jsou použity například pro deklaraci parametrů, najdeme je ve speciální formě let, v makru for atd.). Ostatně právě tato vlastnost jazyka Clojure se – stejně jako u LISPu – velmi často využívá při tvorbě maker, protože se stromovou reprezentací funkce je možné poměrně snadným způsobem manipulovat.
Nesmíme však zapomenout ani na zbylé dva strukturované datové typy. Jedná se o mapy (maps) a množiny (sets). Především mapy jsou velmi důležité v mnoha aplikacích naprogramovaných v Clojure, neboť se s jejich využitím mohou vytvářet struktury/záznamy (structs/records), hodnotové objekty atd. Mapy se velmi často používají pro předávání pojmenovaných parametrů do volaných funkcí či maker atd. Navíc jsou mapy zajímavé i z toho hlediska jejich implementace.
2. Typy map v Clojure
Nejprve se budeme zabývat mapami. Ty existují ve dvou variantách, jejichž základní vlastnosti shrnuje tabulka zobrazená pod tímto odstavcem:
# | Vlastnost | Hash map | Sorted map |
---|---|---|---|
1 | konstruktor | (hash-map …) | (sorted-map …) |
2 | „literál“ | {klíč hodnota klíč hodnota…} | × |
2 | složitost přístupu k prvkům | O(log32N) | O(N) |
3 | složitost (count) | O(1) | O(1) |
4 | setříděné prvky | ne | ano |
5 | podpora (seq) | ano | ano |
6 | podpora (rseq) | ne | ano |
6 | klíče nil | ano | ano |
7 | hodnoty nil | ano | ano |
3. Implementace clojure.lang.PersistentHashMap
Hash mapy se v praxi používají (minimálně v případě programovacího jazyka Clojure) mnohem častěji než mapy, v nichž jsou prvky setříděny na základě hodnoty svého klíče. Proto si podrobněji popíšeme interní strukturu hash mapy. Jedná se o neměnitelnou (immutable) a perzistentní (persistent) datovou strukturu, což znamená, že do jednou vytvořené mapy již nelze přidávat další prvky (dvojice klíč-hodnota) ani žádné prvky ubírat. Na druhou stranu však mapy mohou sdílet svoji interní strukturu, takže přidání nového prvku lze efektivně zařídit vytvořením nové mapy s přidaným prvkem (což zajišťuje funkce nazvaná assoc, kterou si popíšeme níže). Hash mapy jsou interně uloženy podobným způsobem jako minule popsané vektory, tj. s využitím stromu, jehož každý uzel může obsahovat až 32 odkazů na další poduzly. Takové stromy jsou typicky velmi nízké a tím pádem je i jejich prohledání rychlé – složitost je O(log32N), tj. „prakticky konstantní“.
4. INode
Interně je hash mapa tvořena stromem obsahujícím jako své uzly objekty objekty s rozhraním INode (tj. jedná se o instance tříd implementujících toto rozhraní). Samotná struktura INode se postupně vyvíjí a mění. V současné verzi jazyka Clojure (1.6.0) vypadá následovně:
static interface INode extends Serializable { INode assoc(int shift, int hash, Object key, Object val, Box addedLeaf); INode without(int shift, int hash, Object key); IMapEntry find(int shift, int hash, Object key); Object find(int shift, int hash, Object key, Object notFound); ISeq nodeSeq(); INode assoc(AtomicReference<Thread> edit, int shift, int hash, Object key, Object val, Box addedLeaf); INode without(AtomicReference<Thread> edit, int shift, int hash, Object key, Box removedLeaf); public Object kvreduce(IFn f, Object init); Object fold(IFn combinef, IFn reducef, IFn fjtask, IFn fjfork, IFn fjjoin); Iterator iterator(IFn f); }
V minulosti existovalo pět tříd implementujících rozhraní INode, v moderním Clojure jsou to již jen tři třídy: ArrayNode, BitmapIndexedNode a HashCollisionNode.
5. ArrayNode a BitmapIndexedNode
Třída ArrayNode interně obsahuje pole INodu a hodnotu obsahující počet aktivních prvků v poli. Důležité je, že zde není uložen přímo klíč a hodnota prvku, jen reference na INode popř. hodnota null. Operace assoc (přidání prvku se strukturálním sdílením) znamená buď pouhé přidání nového prvku nebo klonování pole s následným přidáním (klonují se ovšem pouze reference).
Mnohem zajímavější je třída BitmapIndexedMode. Zde je interně použito pole Object[] o kapacitě 32 prvků. Význam jednotlivých položek se může měnit. Sudé prvky obsahují buď hodnotu null nebo klíč, liché prvky buď hodnotu (navázanou na klíč) nebo referenci na další INode (což je rekurzivně opět ArrayNode či BitmapIndexedMode):
- Když pole[2*i]==null pak pole[2*i+1] je INode
- Když pole[2*i]!=null pak pole[2*i] je klíč a pole[2*i+1] je hodnota
To znamená, že zmíněné 32prvkové pole může maximálně obsahovat 16 prvků klíč+hodnota, 32 odkazů na poduzly typu INode nebo mix obou typů záznamů.
Ve skutečnosti se pracuje s klíčem nepřímo přes jeho hash kód zjištěný funkcí hash (jde o 32bitovou hodnotu).
6. Operace s mapami: assoc a dissoc
Mezi dvě základní operace, které je možné provádět s mapami, patří operace reprezentované funkcemi nazvanými assoc a dissoc. První z těchto funkcí přidá k již existující mapě novou dvojici klíč-hodnota (či několik dvojic klíč-hodnota) a vrátí novou mapu rozšířenou o tyto prvky (mapy jsou totiž neměnné datové typy, podobně jako seznamy a vektory). Druhá funkce – dissoc – plní opačnou roli, protože z nějaké mapy odstraní dvojici/e klíč-hodnota pro zadaný klíč či několik klíčů (jediným voláním dissoc je tedy možné odstranit více prvků). I tato funkce nemění původní mapu, ale vrací namísto toho novou datovou strukturu, která ovšem s původní mapou většinu prvků sdílí. Ukažme si chování obou zmíněných funkcí assoc a dissoc na jednoduchém příkladu:
Nejprve vytvoříme mapu, jejímiž klíči jsou symboly a hodnotami řetězce (zejména použití symbolů pro klíče je v Clojure idiomatické):
user=> (def client {:id 42 #_=> :name "Sheldon" #_=> :surname "Cooper" #_=> :real-name "Jim Parsons"}) #'user/client
Zobrazíme obsah této mapy (povšimněte si, že prvky jsou zobrazeny náhodně, protože se jedná o hashmapu:
user=> client {:name "Sheldon", :surname "Cooper", :id 42, :real-name "Jim Parsons"}
Funkcí assoc vytvoříme novou mapu, do níž bude přidán další prvek:
user=> (assoc client :iq 187) {:name "Sheldon", :surname "Cooper", :iq 187, :id 42, :real-name "Jim Parsons"}
Původní mapa zůstala nezměněna:
user=> client {:name "Sheldon", :surname "Cooper", :id 42, :real-name "Jim Parsons"}
Funkcí dissoc vytvoříme mapu s odstraněným jedním prvkem či dvěma prvky:
user=> (dissoc client :surname) {:name "Sheldon", :id 42, :real-name "Jim Parsons"} user=> (dissoc client :name :surname) {:id 42, :real-name "Jim Parsons"}
Opět se můžeme přesvědčit o tom, že původní mapa zůstala nezměněna:
user=> client {:name "Sheldon", :surname "Cooper", :id 42, :real-name "Jim Parsons"}
Poznámka: minule jsme se seznámili s takzvanými dočasnými (tranzientními) datovými typy. I pro mapy existuje tranzientní ekvivalent, pro nějž jsou definovány funkce assoc! a dissoc! (s vykřičníkem na konci). Výsledné hodnoty těchto funkcí jsou opět tranzientními mapami, které je možné v případě potřeby navázat na symbol původní tranzientní mapy.
7. Vyhledávání prvků, získání klíčů a hodnot
Mezi další důležité operace, které se s mapami většinou provádí, patří zjištění, zda mapa obsahuje prvek (tj. klíč-hodnota). Pro tento účel nám Clojure nabízí hned několik funkcí. Opět si tyto funkce nejlépe odzkoušíme na jednoduchých příkladech.
Funkce budou otestovány na mapě, jejímiž klíči jsou římské číslice a hodnotami odpovídající číslice arabské:
user=> (def numbers {"I" 1 "II" 2 "III" 3 "IV" 4 "V" 5}) #'user/numbers
Zobrazíme obsah této mapy (povšimněte si, že prvky jsou zobrazeny náhodně, protože se jedná o hashmapu:
user=> (println numbers) {III 3, II 2, V 5, I 1, IV 4} nil
První funkcí je predikát contains?, kde otazník naznačuje, že se vždy vrací pravdivostní hodnota true či false v závislosti na tom, zda v mapě existuje prvek s daným klíčem:
user=> (contains? numbers "I") true user=> (contains? numbers 1) false user=> (contains? numbers "xyzzy") false
Druhá důležitá funkce je get, která vrací hodnotu navázanou na zadaný klíč popř. nil, pokud klíč nebyl nalezen. Pokud se do mapy ukládají i hodnoty nil, nezbývá většinou nic jiného, než kombinace get+contains?:
user=> (get numbers "V") 5 user=> (get numbers "xyzzy") nil
Funkce find je podobná funkci get, ale namísto hodnoty navázané na klíč vrací dvojici klíč+hodnota:
user=> (find numbers "V") ["V" 5] user=> (find numbers "xyzzy") nil user=> (type (find numbers "V")) clojure.lang.MapEntry
Získat je možné všechny klíče, všechny hodnoty či provést selekci na základě sekvence klíčů:
user=> (keys numbers) ("III" "II" "V" "I" "IV") user=> (vals numbers) (3 2 5 1 4) user=> (select-keys numbers ["I" "IV" "V"]) {"V" 5, "IV" 4, "I" 1} user=> (select-keys numbers ["I" "IV" "neznamy"]) {"IV" 4, "I" 1}
Výběr jednoho prvku je možné provést i tím, že se zapíše volání (klíč mapa), což je v Clojure idiomatické:
user=> (def numbers1 {:I 1 :II 2 :III 3 :IV 4 :V 5 :VI 6}) #'user/numbers1 user=> (:III numbers1) 3
Podívejme se na praktičtější příklad používající mapu namísto struktury/záznamu a dvojí způsob získání jedné hodnoty na základě klíče:
user=> (def client {:id 42 #_=> :name "Sheldon" #_=> :surname "Cooper" #_=> :real-name "Jim Parsons"}) #'user/client user=> client {:name "Sheldon", :surname "Cooper", :id 42, :real-name "Jim Parsons"} user=> (get client :name) "Sheldon" user=> (:name client) "Sheldon"
8. Operace merge a zipmap
Posledními dvěma důležitými operacemi, s nimiž se lze v reálných aplikacích velmi často setkat, jsou operace reprezentované funkcemi merge a zipmap. Funkce nazvaná merge slouží ke spojení dvou či většího množství map. Nejprve opět použijeme známou strukturu obsahující základní informace o Sheldonovi:
user=> (def client {:id 42 #_=> :name "Sheldon" #_=> :surname "Cooper" #_=> :real-name "Jim Parsons"}) #'user/client user=> client {:name "Sheldon", :surname "Cooper", :id 42, :real-name "Jim Parsons"}
Nyní zavoláme funkci merge, jejímž výsledkem bude nová mapa:
user=> (merge client {:iq 187 :degrees ["Ph.D." "Sc.D."]}) {:name "Sheldon", :surname "Cooper", :iq 187, :id 42, :degrees ["Ph.D." "Sc.D."], :real-name "Jim Parsons"}
Původní mapa zůstala nezměněna:
user=> client {:name "Sheldon", :surname "Cooper", :id 42, :real-name "Jim Parsons"}
Lépe bude vidět význam funkce merge u map se shodnými klíči. Při spojování v takovém případě „vyhraje“ hodnota uložená v poslední mapě vstupující do merge:
user=> (def numbers1 {"I" 1 "II" 2 "III" 3 "IV" 4 "V" 5 "VI" 6}) #'user/numbers1 user=> (def numbers2 {"VI" "sest" "VII" 7 "VIII" 8 "IX" 9 "X" 9}) #'user/numbers2 user=> (merge numbers1 numbers2) {"III" 3, "VIII" 8, "II" 2, "V" 5, "VII" 7, "X" 9, "VI" "sest", "IX" 9, "I" 1, "IV" 4}
Ovšem:
user=> (merge numbers2 numbers1) {"III" 3, "VIII" 8, "II" 2, "V" 5, "VII" 7, "X" 9, "VI" 6, "IX" 9, "I" 1, "IV" 4}
Velmi užitečná je v praxi funkce nazvaná zipmap, která umožňuje zkombinovat dvě sekvence do jediné mapy. První sekvence obsahuje klíče, druhá sekvence hodnoty. Můžeme tedy psát například:
user=> (zipmap ["I" "II" "III" "IV" "V" "VI"] #_=> [1 2 3 4 5 6]) {"VI" 6, "V" 5, "IV" 4, "III" 3, "II" 2, "I" 1}
Pravověrný Clojure programátor by ovšem napsal (se stejným výsledkem):
user=> (zipmap ["I" "II" "III" "IV" "V" "VI"] #_=> (range 1 7)) {"VI" 6, "V" 5, "IV" 4, "III" 3, "II" 2, "I" 1}
Pozor ovšem na to, že zipmap nejde jednoduše použít s nekonečnými lazy sekvencemi. Toto není dobrý nápad:
(zipmap (range) (repeat 42))
(Stačí ovšem, aby první sekvence byla konečná).
9. Práce s množinami v jazyce Clojure
Poslední důležitou datovou strukturou, se kterou se dříve či později musí seznámit jakýkoli programátor používající jazyk Clojure, jsou množiny (sets), které jsou charakteristické tím, že každý prvek obsahují maximálně jednou (na rozdíl od seznamů a vektorů). Podobně jako mapy, i množiny existují ve dvou podobách: s nesetříděnými prvky (hash set) a setříděnými prvky (sorted set). Pro vytvoření prvního typu množiny je možné použít buď „literál“ s formou zápisu #{prvek1 prvek2 prvek3} nebo funkci-konstruktor nazvanou hash-set. Pro vytvoření druhého prvku množiny se používá výhradně konstruktoru představovaného funkcí sorted-set. Nejdůležitějšími množinovými operacemi je sjednocení, průnik, rozdíl (viz též další kapitolu) a test, zda je jedna množina podmnožinou či nadmnožinou jiné množiny. V Clojure je ovšem množiny možné využít podobně jako další typy sekvencí:
user=> (def colors #{:red :green :blue :yellow :magenta :cyan}) #'user/colors user=> (count colors) 6 user=> (:red colors) :red user=> (:xxx colors) nil user=> (get colors :red) :red user=> (get colors :xxx) nil user=> (contains? colors :red) true user=> (contains? colors :xxx) false
Fungovat bude i přiřazení kódu (indexu) každé barvě:
user=> (zipmap (range) colors) {5 :magenta, 4 :blue, 3 :red, 2 :cyan, 1 :green, 0 :yellow}
10. Množinové operace
Podívejme se nyní na vybrané operace (funkce), které je možné aplikovat na množiny. Nejprve vytvoříme trojici množin, z nichž první reprezentuje množinu s prvky černá a bílá, druhá množina obsahuje prvky červená, zelená, modrá a třetí množina obsahuje prvky azurová, fialová, modrá:
user=> (def bw #{:white :black}) #'user/bw user=> (def rgb #{:red :green :blue}) #'user/rgb user=> (def cmy #{:cyan :magenta :yellow}) #'user/cmy
Obsah všech tří množin si vypíšeme:
user=> bw #{:white :black} user=> rgb #{:green :red :blue} user=> cmy #{:yellow :cyan :magenta}
S využitím operace union vytvoříme dvě nové množiny nazvané „cmyk“ a „palette“. První z těchto množin bude obsahovat prvky azurová, fialová, modrá, druhá množina pak všech osm prvků představujících základní barvy ZX Spectra či textových režimů PC:
user=> (def cmyk (clojure.set/union cmy #{:black})) #'user/cmyk user=> cmyk #{:yellow :cyan :magenta :black} user=> (def palette (clojure.set/union bw cmy rgb)) #'user/palette user=> palette #{:white :yellow :green :cyan :red :blue :magenta :black}
Komu se nechce počet barev palety počítat, může použít:
user=> (count palette) 8
Nyní otestujeme operaci intersection (průnik):
user=> (clojure.set/intersection cmyk rgb) #{} user=> (clojure.set/intersection cmyk bw) #{:black}
Na závěr zjistíme, zda je některá množina podmnožinou či nadmnožinou jiné množiny:
user=> (clojure.set/subset? rgb palette) true user=> (clojure.set/subset? rgb cmyk) false user=> (clojure.set/subset? cmy cmyk) true user=> (clojure.set/superset? cmy cmyk) false user=> (clojure.set/superset? cmyk cmy) true
11. Odkazy na předchozí části tohoto seriálu
- Leiningen: nástroj pro správu projektů napsaných v Clojure
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (2)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-2/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (3)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-3/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (4)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-4/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (5)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-5/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (6)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-6/ - Programovací jazyk Clojure a databáze (1.část)
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-databaze-1-cast/ - Pluginy pro Leiningen
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-pluginy-pro-leiningen/ - Programovací jazyk Clojure a knihovny pro práci s vektory a maticemi
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-knihovny-pro-praci-s-vektory-a-maticemi/ - Programovací jazyk Clojure a knihovny pro práci s vektory a maticemi (2)
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-knihovny-pro-praci-s-vektory-a-maticemi-2/ - Programovací jazyk Clojure: syntéza procedurálních textur s využitím knihovny Clisk
http://www.root.cz/clanky/programovaci-jazyk-clojure-synteza-proceduralnich-textur-s-vyuzitim-knihovny-clisk/ - Programovací jazyk Clojure: syntéza procedurálních textur s využitím knihovny Clisk (2)
http://www.root.cz/clanky/programovaci-jazyk-clojure-synteza-proceduralnich-textur-s-vyuzitim-knihovny-clisk-2/ - Programovací jazyk Clojure: syntéza procedurálních textur s využitím knihovny Clisk (dokončení)
http://www.root.cz/clanky/programovaci-jazyk-clojure-synteza-proceduralnich-textur-s-vyuzitim-knihovny-clisk-dokonceni/ - Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure
http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure/ - Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure (2)
http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure-2/ - Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure (3)
http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure-3/ - Programovací jazyk Clojure a práce s Gitem
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem/ - Programovací jazyk Clojure a práce s Gitem (2)
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem-2/ - Programovací jazyk Clojure – triky při práci s řetězci
http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-retezci/ - Programovací jazyk Clojure – triky při práci s kolekcemi
http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-kolekcemi/
12. Odkazy na Internetu
- Understanding Clojure's PersistentVector implementation
http://blog.higher-order.net/2009/02/01/understanding-clojures-persistentvector-implementation - Understanding Clojure's PersistentHashMap (deftwice…)
http://blog.higher-order.net/2009/09/08/understanding-clojures-persistenthashmap-deftwice.html - Assoc and Clojure's PersistentHashMap: part ii
http://blog.higher-order.net/2010/08/16/assoc-and-clojures-persistenthashmap-part-ii.html - Ideal Hashtrees (paper)
http://lampwww.epfl.ch/papers/idealhashtrees.pdf - Clojure home page
http://clojure.org/ - Clojure (downloads)
http://clojure.org/downloads - Clojure Sequences
http://clojure.org/sequences - Clojure Data Structures
http://clojure.org/data_structures - The Structure and Interpretation of Computer Programs: 2.2.1 Representing Sequences
http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-15.html#%_sec2.2.1 - The Structure and Interpretation of Computer Programs: 3.3.1 Mutable List Structure
http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-22.html#%_sec3.3.1 - Clojure – Functional Programming for the JVM
http://java.ociweb.com/mark/clojure/article.html - Clojure quick reference
http://faustus.webatu.com/clj-quick-ref.html - 4Clojure
http://www.4clojure.com/ - ClojureDoc (rozcestník s dokumentací jazyka Clojure)
http://clojuredocs.org/ - Clojure (na Wikipedia EN)
http://en.wikipedia.org/wiki/Clojure - Clojure (na Wikipedia CS)
http://cs.wikipedia.org/wiki/Clojure - SICP (The Structure and Interpretation of Computer Programs)
http://mitpress.mit.edu/sicp/ - Pure function
http://en.wikipedia.org/wiki/Pure_function - Funkcionální programování
http://cs.wikipedia.org/wiki/Funkcionální_programování - Čistě funkcionální (datové struktury, jazyky, programování)
http://cs.wikipedia.org/wiki/Čistě_funkcionální - Clojure Macro Tutorial (Part I, Getting the Compiler to Write Your Code For You)
http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-i-getting.html - Clojure Macro Tutorial (Part II: The Compiler Strikes Back)
http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-ii-compiler.html - Clojure Macro Tutorial (Part III: Syntax Quote)
http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-ii-syntax.html - Tech behind Tech: Clojure Macros Simplified
http://techbehindtech.com/2010/09/28/clojure-macros-simplified/ - Fatvat – Exploring functional programming: Clojure Macros
http://www.fatvat.co.uk/2009/02/clojure-macros.html - Eulerovo číslo
http://cs.wikipedia.org/wiki/Eulerovo_číslo - List comprehension
http://en.wikipedia.org/wiki/List_comprehension - List Comprehensions in Clojure
http://asymmetrical-view.com/2008/11/18/list-comprehensions-in-clojure.html - Clojure Programming Concepts: List Comprehension
http://en.wikibooks.org/wiki/Clojure_Programming/Concepts#List_Comprehension - Clojure core API: for macro
http://clojure.github.com/clojure/clojure.core-api.html#clojure.core/for - cirrus machina – The Clojure for macro
http://www.cirrusmachina.com/blog/comment/the-clojure-for-macro/ - Riastradh's Lisp Style Rules
http://mumble.net/~campbell/scheme/style.txt - Dynamic Languages Strike Back
http://steve-yegge.blogspot.cz/2008/05/dynamic-languages-strike-back.html - Scripting: Higher Level Programming for the 21st Century
http://www.tcl.tk/doc/scripting.html - Java Virtual Machine Support for Non-Java Languages
http://docs.oracle.com/javase/7/docs/technotes/guides/vm/multiple-language-support.html - Třída java.lang.String
http://docs.oracle.com/javase/7/docs/api/java/lang/String.html - Třída java.lang.StringBuffer
http://docs.oracle.com/javase/7/docs/api/java/lang/StringBuffer.html - Třída java.lang.StringBuilder
http://docs.oracle.com/javase/7/docs/api/java/lang/StringBuilder.html - StringBuffer versus String
http://www.javaworld.com/article/2076072/build-ci-sdlc/stringbuffer-versus-string.html - Threading macro (dokumentace k jazyku Clojure)
https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/-> - Understanding the Clojure → macro
http://blog.fogus.me/2009/09/04/understanding-the-clojure-macro/ - clojure.inspector
http://clojure.github.io/clojure/clojure.inspector-api.html - The Clojure Toolbox
http://www.clojure-toolbox.com/ - Unit Testing in Clojure
http://nakkaya.com/2009/11/18/unit-testing-in-clojure/ - Testing in Clojure (Part-1: Unit testing)
http://blog.knoldus.com/2014/03/22/testing-in-clojure-part-1-unit-testing/ - API for clojure.test – Clojure v1.6 (stable)
https://clojure.github.io/clojure/clojure.test-api.html - Leiningen: úvodní stránka
http://leiningen.org/ - Leiningen: Git repository
https://github.com/technomancy/leiningen - leiningen-win-installer
http://leiningen-win-installer.djpowell.net/ - Clojure 1: Úvod
http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm/ - Clojure 2: Symboly, kolekce atd.
http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-2-cast/ - Clojure 3: Funkcionální programování
http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-3-cast-funkcionalni-programovani/ - Clojure 4: Kolekce, sekvence a lazy sekvence
http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-4-cast-kolekce-sekvence-a-lazy-sekvence/ - Clojure 5: Sekvence, lazy sekvence a paralelní programy
http://www.root.cz/clanky/clojure-a-bezpecne-aplikace-pro-jvm-sekvence-lazy-sekvence-a-paralelni-programy/ - Clojure 6: Podpora pro paralelní programování
http://www.root.cz/clanky/programovaci-jazyk-clojure-6-futures-nejsou-jen-financni-derivaty/ - Clojure 7: Další funkce pro paralelní programování
http://www.root.cz/clanky/programovaci-jazyk-clojure-7-dalsi-podpurne-prostredky-pro-paralelni-programovani/ - Clojure 8: Identity, stavy, neměnné hodnoty a reference
http://www.root.cz/clanky/programovaci-jazyk-clojure-8-identity-stavy-nemenne-hodnoty-a-referencni-typy/ - Clojure 9: Validátory, pozorovatelé a kooperace s Javou
http://www.root.cz/clanky/programovaci-jazyk-clojure-9-validatory-pozorovatele-a-kooperace-mezi-clojure-a-javou/ - Clojure 10: Kooperace mezi Clojure a Javou
http://www.root.cz/clanky/programovaci-jazyk-clojure-10-kooperace-mezi-clojure-a-javou-pokracovani/ - Clojure 11: Generátorová notace seznamu/list comprehension
http://www.root.cz/clanky/programovaci-jazyk-clojure-11-generatorova-notace-seznamu-list-comprehension/ - Clojure 12: Překlad programů z Clojure do bajtkódu JVM I:
http://www.root.cz/clanky/programovaci-jazyk-clojure-12-preklad-programu-z-clojure-do-bajtkodu-jvm/ - Clojure 13: Překlad programů z Clojure do bajtkódu JVM II:
http://www.root.cz/clanky/programovaci-jazyk-clojure-13-preklad-programu-z-clojure-do-bajtkodu-jvm-pokracovani/ - Clojure 14: Základy práce se systémem maker
http://www.root.cz/clanky/programovaci-jazyk-clojure-14-zaklady-prace-se-systemem-maker/ - Clojure 15: Tvorba uživatelských maker
http://www.root.cz/clanky/programovaci-jazyk-clojure-15-tvorba-uzivatelskych-maker/ - Clojure 16: Složitější uživatelská makra
http://www.root.cz/clanky/programovaci-jazyk-clojure-16-slozitejsi-uzivatelska-makra/ - Clojure 17: Využití standardních maker v praxi
http://www.root.cz/clanky/programovaci-jazyk-clojure-17-vyuziti-standardnich-maker-v-praxi/ - Clojure 18: Základní techniky optimalizace aplikací
http://www.root.cz/clanky/programovaci-jazyk-clojure-18-zakladni-techniky-optimalizace-aplikaci/ - Clojure 19: Vývojová prostředí pro Clojure
http://www.root.cz/clanky/programovaci-jazyk-clojure-19-vyvojova-prostredi-pro-clojure/ - Clojure 20: Vývojová prostředí pro Clojure (Vimu s REPL)
http://www.root.cz/clanky/programovaci-jazyk-clojure-20-vyvojova-prostredi-pro-clojure-integrace-vimu-s-repl/ - Clojure 21: ClojureScript aneb překlad Clojure do JS
http://www.root.cz/clanky/programovaci-jazyk-clojure-21-clojurescript-aneb-preklad-clojure-do-javascriptu/ - Clojure.org: Vars and the Global Environment
http://clojure.org/Vars - Clojure.org: Refs and Transactions
http://clojure.org/Refs - Clojure.org: Atoms
http://clojure.org/Atoms - Clojure.org: Agents as Asynchronous Actions
http://clojure.org/agents - Transient Data Structureshttp://clojure.org/transients