Obsah
1. Asynchronní programování v Clojure s využitím knihovny core.async
2. Princip, na němž je postaveno core.async: CSP
3. Cíle a prostředky použité v core.async
4. Kdy je vhodné použít core.async?
5. Go bloky a základní operace s kanály
6. První demonstrační příklad: producent a konzument
7. Druhý demonstrační příklad: modelování času pomocí timeout
8. Třetí demonstrační příklad: jednoduchý logger
9. Čtvrtý demonstrační příklad: větší množství producentů
10. Použití funkce alts! při obsluze většího množství kanálů
11. Pátý demonstrační příklad: použití funkce alts!
12. Repositář s demonstračními příklady
13. Odkazy na předchozí části tohoto seriálu
1. Asynchronní programování v Clojure s využitím knihovny core.async
Knihovny či nové jazykové konstrukce umožňující používání kanálů (či front) pro asynchronní komunikaci mezi různými částmi vyvíjených aplikací, se v posledních několika letech těší poměrně velké popularitě. Ta je způsobena dvěma faktory. První důvod spočívá ve snaze o zjednodušení návrhu (či porozumění) vyvíjené aplikace, zejména ve chvíli, kdy se v rámci jednoho programu předávají data (resp. objekty) mezi částmi, jejichž funkce může být dobře izolována od částí ostatních. Druhý důvod je poněkud prozaičtější – v některých situacích je nutné dosáhnout zvýšení efektivity aplikace (například zvýšit počet odpovědí, které může server vygenerovat za určitou časovou jednotku) a přitom není možné či vhodné využívat řešení založené na použití většího množství vláken spravovaných systémem. Naprosto typickým příkladem jsou virtuální stroje JavaScriptu, které povětšinou umožňují běh aplikace v jediném vláknu (což je ovšem s ohledem na „kvalitu“ některých programových kódů spíše výhodou…).
Některé programovací jazyky, zejména pak jazyk Go, obsahují prostředky sloužící pro zajištění asynchronní komunikace přímo v syntaxi (a samozřejmě též v sémantice) jazyka. Konkrétně v případě jazyka Go se jedná o takzvané „gorutiny“ představované klíčovým slovem go a taktéž o operace sloužící pro zápis či čtení dat z kanálů, které jsou představovány operátorem <- (ten má dva významy v závislosti na tom, zda je před operátorem uveden identifikátor představující kanál či nikoli). V programovacím jazyku Clojure však naproti tomu nebylo nutné zavádět speciální syntaxi a vlastně ani nebylo nutné zasahovat do překladače. Celá knihovna core.async je založena na několika běžných funkcích a hlavně pak na makrech, které manipulací s předaným programovým kódem dokážou z běžného příkazového bloku vytvořit blok zpracovávaný asynchronně (opět se zde ukazuje přednost homoikonických jazyků). Použít ji lze dokonce i v ClojureScriptu, tedy v implementaci jazyka Clojure postavené nad „jednovláknovým“ JavaScriptem.
2. Princip, na němž je postaveno core.async: CSP
Použití asynchronních kanálů a z nich odvozených asynchronních vstupně-výstupních operací („async I/O“), se mezi širší programátorskou komunitu rozšířilo pravděpodobně až s vývojem systému node.js. Ve skutečnosti je však realizovaný princip mnohem starší, protože sahá až do šedesátých a sedmdesátých let minulého století, kdy se postupně vyvíjely metody umožňující lepší využití strojového času tehdejších počítačů (lepším využitím se zde nutně nemyslí škálovatelnost!). Za strojový čas se tehdy samozřejmě platilo, což přeneseně platí i dnes. Poznatky vypracovávané v šedesátých a sedmdesátých letech byly formalizovány a následně shrnuty do teorie popsané v knize nazvané Communicating Sequential Processes, jejímž autorem je C.A.R Hoare. Kniha byla vydána v roce 1978 a kvůli její oblibě a známosti se mnohdy setkáme pouze se zkratkou CSP.
3. Cíle a prostředky použité v core.async
Hlavním cílem knihovny core.async je implementace technologie umožňující, aby různá nezávislá vlákna mezi sebou mohla komunikovat s využitím takzvaných kanálů (channels). Použití kanálů může vést ke zjednodušení aplikace, v níž komunikuje mnoho objektů. Typicky je umožněno snadné rozdělení výpočtů (či jak se dnes říká business logiky) od zobrazení výsledků, oddělení grafického uživatelského rozhraní od spouštění výpočtů atd., což je zajímavá alternativa k dnes převládající implementaci aplikace založené na přímém použití callback funkcí. Vlákna přitom mohou být vytvořena na úrovni systému, a to se všemi klady a zápory, které toto řešení přináší; či se může jednat o implementaci pseudovláken, s níž se setkáme zejména v ClojureScriptu. Knihovna core.async se snaží o využití svého thread poolu, tj. zásobníku vláken, které lze po doběhnutí určité operace použít znovu pro provedení jiné operace, což vede k efektivnějšímu využití systémových prostředků (s takto nízkoúrovňovými operacemi se však při použití knihovny většinou nesetkáte).
Knihovna core.async je založena na dvou základních konstrukcích. Jedná se o již zmíněné kanály, které lze v některých případech nakonfigurovat takovým způsobem, že fungují jako fronty či buffery (nejjednodušší kanál vlastnosti fronty nemá, proto jsou všechny operace nad ním blokující). Pro operaci s kanály je vytvořeno několik funkcí: vytvoření kanálu, zavření kanálu, zápis dat, čtení dat, čtení dat z libovolného kanálu, čtení dat s určením priority jednotlivých kanálů atd. Kanál lze použít i jako takzvané synchronizační primitivum a jak uvidíme dále, některé speciální kanály lze použít například i pro pouhé pozastavení výpočtu. Druhou konstrukcí jsou takzvané go bloky, které jsou interně implementovány dosti složitým makrem, které transformuje uživatelský kód do podoby stavového automatu.
4. Kdy je vhodné použít core.async?
Podobně, jako je tomu v případě dalších technologií, i u knihovny core.async může nastat situace, že její funkce budou v aplikacích nadužívány, minimálně do doby, než opadne prvotní nadšení z nové (velmi důležité) knihovny. Obecně platí, že nejvíce budou z výhod core.async těžit ty aplikace, v nichž by se běžně vytvářelo velké množství vláken, přičemž každé vlákno by mělo za úkol zpracovat data dodávaná z jiné části systému. Tato vlákna by tedy odpovídala takzvaným „workerům“. V případě, že každý worker musí čekat na data či například na dokončení nějaké I/O operace, znamená to, že jeho vlákno není efektivně vytíženo. Může to vypadat zhruba takto:
Vlákno 1........worker1........worker1........worker1........worker1 Vlákno 2........worker2........worker2........worker2........worker2 Vlákno 3........worker3........worker3........worker3........worker3 Vlákno 4........worker4........worker4........worker4........worker4
Použitím technologií dostupných v knihovně core.async je možné dosáhnout lepšího využití vláken, například takto:
Vlákno 1.worker1.worker4.worker1.worker3...worker1.worker3.worker2.worker4 Vlákno 2..worker2.worker3..worker1...worker4.worker2......worker1.worker3
5. Go bloky a základní operace s kanály
Pro předvedení základních možností knihovny core.async nám postačuje použít funkci chan, která vytvoří nový kanál, dvojici operací nazvaných „put“ a „take“ a již několikrát zmíněné go bloky představované makrem nazvaným go. Operace typu „put“, tj. vložení dat do kanálu, se zapisuje znaky >!, operace typu „take“ (přečtení/příjem dat z kanálu) se zapisuje pomocí znaků <!. Obě operace musí proběhnout v bloku go, což je v makru kontrolováno. V případě použití běžného kanálu bez bufferu je operace „put“ blokující (čeká se, až někdo jiný data z kanálu přijme) a operace „take“ je blokující ve chvíli, kdy je kanál prázdný (čeká se, až někdo jiný data do kanálu zapíše). Podívejme se na typický příklad typu producent–konzument:
+-----------+ +-----------+ | producent | +-----+ | konzument | | |... >! ... |kanál} ... <! ... | | | go block | +-----+ | go block | +-----------+ +-----------+
V interaktivní smyčce REPL lze producenta realizovat v go bloku, stejně tak i konzumenta:
(require '[clojure.core.async :refer :all]) ; vytvoření kanálu (def c (chan)) ; asynchronní spuštění producenta (go (>! c "Hello world!")) ; asynchronní spuštění konzumenta (go (println (<! c)))
Konzument může čekat libovolně dlouhou dobu do chvíle, než z kanálu získá data:
; asynchronní spuštění konzumenta (go (println (<! c))) ... ... ... čas na kafe ... ... ... ; asynchronní spuštění producenta (go (>! c "Hello world!"))
6. První demonstrační příklad: producent a konzument
V dnešním prvním demonstračním příkladu je implementován výše zmíněný (velmi jednoduchý) model typu producent–konzument. Kostru příkladu vytvoříte běžným způsobem, tj. příkazem:
lein new app async1
Následně je nutné upravit projektový soubor project.clj doplněním knihovny core.async. Pro jistotu taktéž změňte verzi Clojure z 1.6.0 na 1.7.0 (pochopitelně pouze tehdy, pokud je náhodou stále nastavena verze 1.6.0):
(defproject async1 "0.1.0-SNAPSHOT" :description "FIXME: write description" :url "http://example.com/FIXME" :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"} :dependencies [[org.clojure/clojure "1.7.0"] [org.clojure/core.async "0.2.374"]] :main ^:skip-aot async1.core :target-path "target/%s" :profiles {:uberjar {:aot :all}})
Vlastní program se vlastně příliš neliší od sekvence příkazů uvedených v předchozí kapitole. Pouze se vylepšil import knihovny core.async, protože potřebujeme použít jen čtyři symboly z této knihovny a současně si nepřepsat další užitečné funkce (v core.async je například deklarována funkce map apod.). Aby se simulovalo pomalé zapisování příkazů v REPL, používá se v příkladu uživatelská funkce wait ukazující, že bloky go jsou skutečně spuštěny asynchronně:
(ns async1.core (:gen-class)) ; nacteme vsechny potrebne funkce, makra a symboly z knihovny ; (schvalne se nenacitaji vsechny funkce, protoze by jejich jmena ; prepsala takove zakladni funkce, jako je map apod.) (require '[clojure.core.async :refer (go chan >! <!)]) (defn wait "Pozastaveni hlavniho vlakna - simulace interaktivni prace." [] (Thread/sleep 1000)) (defn -main "Tato funkce se spusti automaticky nastrojem Leiningen." [& args] (println "Start") ; vytvorime kanal (let [channel (chan)] ; poslani zpravy do kanaly (go block bude cekat na precteni) (go (>! channel "Hello world #1!")) (wait) ; precteni zpravy z kanalu (go (println (<! channel))) (wait) ; pokus o precteni zpravy z kanalu (ten je prazdny, takze se go block zastavi) (go (println (<! channel))) (wait) ; poslani zpravy do kanalu, na tuto zpravu se jiz netrpelive ceka (go (>! channel "Hello world #2!")) (wait)) (println "Finish"))
7. Druhý demonstrační příklad: modelování času pomocí timeout
Ve druhém demonstračním příkladu je vytvořen producent, který do kanálu postupně posílá sekvenci čísel 0 až 9, přičemž mezi čísly je určitý čekací interval simulující nějaký výpočet či I/O operaci. Producent vypadá následovně:
; kontinualni posilani zprav do kanalu v asynchronnim bloku (go (dotimes [i 10] (Thread/sleep 1000) (>! channel i)))
Konzument je realizován nekonečnou smyčkou čekající na data z kanálu. Pro go bloky je typické právě použití smyček, ovšem lze použít i go-loop atd.:
(go (while true (println (<! channel))))
Pojďme se nyní ukázal úplný zdrojový kód tohoto příkladu. Nejprve projektový soubor:
(defproject async2 "0.1.0-SNAPSHOT" :description "FIXME: write description" :url "http://example.com/FIXME" :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"} :dependencies [[org.clojure/clojure "1.7.0"] [org.clojure/core.async "0.2.374"]] :main ^:skip-aot async2.core :target-path "target/%s" :profiles {:uberjar {:aot :all}})
Následně i vlastní hlavní modul:
(ns async2.core (:gen-class)) ; nacteme vsechny potrebne funkce, makra a symboly z knihovny ; (schvalne se nenacitaji vsechny funkce, protoze by jejich jmena ; prepsala takove zakladni funkce, jako je map apod.) (require '[clojure.core.async :refer (go chan >! <!]) (defn wait "Pozastaveni hlavniho vlakna - simulace interaktivni prace." [] (Thread/sleep 15000)) (defn -main "Tato funkce se spusti automaticky nastrojem Leiningen." [& args] (println "Start") ; vytvorime kanal (let [channel (chan)] ; kontinualni posilani zprav do kanalu v asynchronnim bloku (go (dotimes [i 10] (Thread/sleep 1000) (>! channel i))) (println "1st go block started") ; kontinualni cteni zprav z kanalu v asynchronnim bloku (go (while true (println (<! channel)))) (println "2nd go block started")) ; chvili pockame, az se vypise cela sekvence 0 az 9 (wait) (println "Finish"))
Ve skutečnosti není použití (Thread/sleep 1000) příliš idiomatické. Mnohem lepší je použití speciálního kanálu vytvořeného zavoláním (timeout x). Tento kanál je po uplynutí určeného časového intervalu automaticky uzavřen. Co to znamená? Pokud z kanálu pouze čteme, jedná se o blokující operaci (to již víme), ovšem po uplynutí určeného času je kanál uzavřen a vrátí se hodnota nil. Čtením z kanálu typu timeout tedy můžeme elegantně realizovat efektivně implementované čekání. Upravený producent vypadá takto:
; kontinualni posilani zprav do kanalu v asynchronnim bloku (go (dotimes [i 10] ;(Thread/sleep 1000) (<! (timeout 1000)) (>! channel i)))
Upravený zdrojový kód má následující tvar (povšimněte si nového symbolu v require):
(ns async2.core (:gen-class)) ; nacteme vsechny potrebne funkce, makra a symboly z knihovny ; (schvalne se nenacitaji vsechny funkce, protoze by jejich jmena ; prepsala takove zakladni funkce, jako je map apod.) (require '[clojure.core.async :refer (go chan >! <! timeout)]) (defn wait "Pozastaveni hlavniho vlakna - simulace interaktivni prace." [] (Thread/sleep 15000)) (defn -main "Tato funkce se spusti automaticky nastrojem Leiningen." [& args] (println "Start") ; vytvorime kanal (let [channel (chan)] ; kontinualni posilani zprav do kanalu v asynchronnim bloku (go (dotimes [i 10] ;(Thread/sleep 1000) (<! (timeout 1000)) (>! channel i))) (println "1st go block started") ; kontinualni cteni zprav z kanalu v asynchronnim bloku (go (while true (println (<! channel)))) (println "2nd go block started")) ; chvili pockame, az se vypise cela sekvence 0 az 9 (wait) (println "Finish"))
8. Třetí demonstrační příklad: jednoduchý logger
Jedno z možných použití kanálů spočívá v implementaci jednoduchého loggeru, který může být volán z libovolného počtu vláken, synchronně či asynchronně. Navíc je možné zvětšit kapacitu kanálu používaného loggerem a změnit tak jeho chování (kanál se bude chovat jako fronta či buffer), což je vhodné zejména tehdy, pokud je I/O operace spojená s logováním časově náročnější (zápis do databáze, zápis na dedikovaný stroj atd.). V našem případě logger pouze zapisuje data přečtená z kanálu na standardní výstup, to je však možné snadno změnit:
(ns async3.core (:gen-class)) ; nacteme vsechny potrebne funkce, makra a symboly z knihovny ; (schvalne se nenacitaji vsechny funkce, protoze by jejich jmena ; prepsala takove zakladni funkce, jako je map apod.) (require '[clojure.core.async :refer (go chan >! <!)]) (def logger (chan)) (defn wait "Pozastaveni hlavniho vlakna - simulace interaktivni prace." [] (Thread/sleep 5000)) (defn start-logger "Spusteni loggeru." [] (go (while true ;(Thread/sleep 1000) ; zkuste odkomentovat ; vytisteni vsech dat prectenych z kanalu (println (<! logger))))) (defn log "Zalogovani zpravy - poslani do kanalu." [message] (go (>! logger message)) nil) (defn -main "Tato funkce se spusti automaticky nastrojem Leiningen." [& args] (println "Start") (start-logger) (log "Hello") (dotimes [i 20] (future (log (str "Thread #" i)))) (log "world") ; chvili pockame, az se vypise cela sekvence 0 az 9 (wait) ; dokonceni vsech dalsich vlaken atd. (shutdown-agents) (println "Finish"))
Povšimněte si použití dvaceti různých vláken pro zápis do kanálu. Makrem future se spustí blok v samostatném vláknu:
(dotimes [i 20] (future (log (str "Thread #" i))))
Na konci příkladu je nyní nutné použít volání:
(shutdown-agents)
zajišťující ukončení všech vláken vytvořených agenty či voláním future.
9. Čtvrtý demonstrační příklad: větší množství producentů
Kanál může být využíván více producenty (i konzumenty), takže se původní schéma:
+-----------+ +-----------+ | producent | +-----+ | konzument | | |... >! ... |kanál} ... <! ... | | | go block | +-----+ | go block | +-----------+ +-----------+
Může změnit na poněkud složitější schéma:
+-----------+ | producent | | #1 |... >!......... | go block | : +-----------+ : : +-----------+ : +-----------+ | producent | +-----+ | konzument | | #2 |... >! ... |kanál} ... <! ... | | | go block | +-----+ | go block | +-----------+ : +-----------+ : +-----------+ : | producent | : | #3 |... >!........: | go block | +-----------+
Toto uspořádání je samozřejmě podporováno, o čemž nás přesvědčí další programový kód s trojicí producentů a jediným konzumentem:
(ns async4.core (:gen-class)) ; nacteme vsechny potrebne funkce, makra a symboly z knihovny ; (schvalne se nenacitaji vsechny funkce, protoze by jejich jmena ; prepsala takove zakladni funkce, jako je map apod.) (require '[clojure.core.async :refer (go chan >! <! timeout)]) (defn wait "Pozastaveni hlavniho vlakna - simulace interaktivni prace." [] (Thread/sleep 10000)) (defn -main "Tato funkce se spusti automaticky nastrojem Leiningen." [& args] (println "Start") ; vytvorime kanal (let [channel (chan)] ; kontinualni posilani zprav do kanalu v trojici asynchronnich bloku (go (while true (<! (timeout 500)) (>! channel "first"))) (go (while true (<! (timeout 1000)) (>! channel "second"))) (go (while true (<! (timeout 2000)) (>! channel "third"))) (println "producers started") ; kontinualni cteni zprav z kanalu v asynchronnim bloku (go (while true (println (<! channel)))) (println "consumer started")) ; chvili pockame, az se vypise cela sekvence 0 az 9 (wait) (println "Finish") (System/exit 0))
Příklad výstupu, z něhož je patrné, s jakou frekvencí jednotliví producenti zapisují zprávy do kanálu:
Start producers started consumer started first second first first third second first first second first first third second first first second first first third second first first second first first third second first first second first first Finish
10. Použití funkce alts! při obsluze většího množství kanálů
Použití většího množství producentů jsme si již ukázali v předchozím příkladu. Nyní si řekněme, jak může jediný konzument načítat data z většího množství kanálů. To je možné, ovšem namísto operace <! se použije funkce alts!, které se předá vektor kanálů. Funkce alts! ve výchozím nastavení náhodně vybere kanál, ze kterého bude číst. Náhodný výběr je zde důležitý, protože pokud by se kanály neustále vybíraly podle uvedeného pořadí, mohlo by docházet k efektu, který je znám pod termínem „vyhladovění“ (starvation) u těch kanálů, které se ve vektoru nachází na posledních pozicích. Kromě dvou již uvedených schémat tedy ještě přibývá třetí schéma + všechny jeho varianty:
+-----------+ | producent | +------+ | #1 |... >!.....|kanál1}....... | go block | +------+ : +-----------+ : : +-----------+ : +-----------+ | producent | +------+ : | konzument | | #2 |... >! ... |kanál2} ... alts! ... | | | go block | +------+ : | go block | +-----------+ : +-----------+ : +-----------+ : | producent | +------+ : | #3 |... >!.....|kanál3}......: | go block | +------+ +-----------+
11. Pátý demonstrační příklad: použití funkce alts!
Funkce alts! je použita v pátém demonstračním příkladu. Povšimněte si, že návratovou hodnotou této funkce je dvojice, protože je nutné nějakým způsobem vrátit jak referenci na kanál, ze kterého se čte, tak i vlastní přečtenou hodnotu. My referenci na kanál použijeme ve výstupu pro určení tisknutého prefixu:
(ns async5.core (:gen-class)) ; nacteme vsechny potrebne funkce, makra a symboly z knihovny ; (schvalne se nenacitaji vsechny funkce, protoze by jejich jmena ; prepsala takove zakladni funkce, jako je map apod.) (require '[clojure.core.async :refer (go chan >! <! timeout alts!)]) (defn wait "Pozastaveni hlavniho vlakna - simulace interaktivni prace." [] (Thread/sleep 10000)) (defn -main "Tato funkce se spusti automaticky nastrojem Leiningen." [& args] (println "Start") ; vytvorime kanaly (let [channel1 (chan) channel2 (chan) channel3 (chan)] ; kontinualni posilani zprav do trech kanalu v trojici asynchronnich bloku (go (doseq [i (range)] (<! (timeout 500)) (>! channel1 i))) (go (doseq [i (range)] (<! (timeout 1000)) (>! channel2 i))) (go (doseq [i (range)] (<! (timeout 2000)) (>! channel3 i))) (println "producers started") ; kontinualni cteni zprav z kanalu v asynchronnim bloku (go (while true (let [[item channel] (alts! [channel1 channel2 channel3])] (condp = channel channel1 (println "channel #1: " item) channel2 (println "channel #2: " item) channel3 (println "channel #3: " item))))) (println "consumer started")) ; chvili pockame, az se vypise cela sekvence 0 az 9 (wait) (println "Finish") (System/exit 0))
Příklad výstupu:
Start producers started consumer started channel #1: 0 channel #2: 0 channel #1: 1 channel #1: 2 channel #3: 0 channel #2: 1 channel #1: 3 channel #1: 4 channel #2: 2 channel #1: 5 channel #1: 6 channel #3: 1 channel #2: 3 channel #1: 7 channel #1: 8 channel #2: 4 channel #1: 9 channel #1: 10 channel #3: 2 channel #2: 5 channel #1: 11 channel #1: 12 channel #2: 6 channel #1: 13 channel #1: 14 channel #3: 3 channel #2: 7 channel #1: 15
12. Repositář s demonstračními příklady
Všech pět demonstračních příkladů, které jsme si v dnešním článku popsali, bylo uloženo do Git repositáře dostupného na adrese https://github.com/tisnik/clojure-examples. V tabulce zobrazené pod tímto odstavcem naleznete na zdrojové kódy demonstračních příkladů přímé odkazy:
13. Odkazy na předchozí části tohoto seriálu
- 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/ - 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/ - Programovací jazyk Clojure – práce s mapami a množinami
http://www.root.cz/clanky/programovaci-jazyk-clojure-prace-s-mapami-a-mnozinami/ - Programovací jazyk Clojure – základy zpracování XML
http://www.root.cz/clanky/programovaci-jazyk-clojure-zaklady-zpracovani-xml/ - Programovací jazyk Clojure – testování s využitím knihovny Expectations
http://www.root.cz/clanky/programovaci-jazyk-clojure-testovani-s-vyuzitim-knihovny-expectations/ - Programovací jazyk Clojure – některé užitečné triky použitelné (nejenom) v testech
http://www.root.cz/clanky/programovaci-jazyk-clojure-nektere-uzitecne-triky-pouzitelne-nejenom-v-testech/ - Enlive – výkonný šablonovací systém pro jazyk Clojure
http://www.root.cz/clanky/enlive-vykonny-sablonovaci-system-pro-jazyk-clojure/ - Nástroj Leiningen a programovací jazyk Clojure: tvorba vlastních knihoven pro veřejný repositář Clojars
http://www.root.cz/clanky/nastroj-leiningen-a-programovaci-jazyk-clojure-tvorba-vlastnich-knihoven-pro-verejny-repositar-clojars/ - Novinky v Clojure verze 1.8.0
http://www.root.cz/clanky/novinky-v-clojure-verze-1–8–0/
14. Odkazy na Internetu
- Communicating sequential processes
https://en.wikipedia.org/wiki/Communicating_sequential_processes - Clojure core.async
http://www.infoq.com/presentations/clojure-core-async - core.async API Reference
https://clojure.github.io/core.async/ - Clojure core.async Channels
http://clojure.com/blog/2013/06/28/clojure-core-async-channels.html - core.async examples
https://github.com/clojure/core.async/blob/master/examples/walkthrough.clj - Timothy Baldridge – Core.Async
https://www.youtube.com/watch?v=enwIIGzhahw - Designing Front End Applications with core.async
http://go.cognitect.com/core_async_webinar_recording - Mastering Concurrent Processes with core.async
http://www.braveclojure.com/core-async/ - LispCast: Clojure core.async
https://www.youtube.com/watch?v=msv8Fvtd6YQ - Julian Gamble – Applying the paradigms of core.async in ClojureScript
https://www.youtube.com/watch?v=JUrOebC5HmA - Zip archiv s Clojure 1.8.0
http://repo1.maven.org/maven2/org/clojure/clojure/1.8.0/clojure-1.8.0.zip - Clojure 1.8 is now available
http://clojure.org/news/2016/01/19/clojure18 - Changes to Clojure in Version 1.8
https://github.com/clojure/clojure/blob/master/changes.md - Socket Server REPL
http://dev.clojure.org/display/design/Socket+Server+REPL - CLJ-1671: Clojure socket server
http://dev.clojure.org/jira/browse/CLJ-1671 - CLJ-1449: Add clojure.string functions for portability to ClojureScript
http://dev.clojure.org/jira/browse/CLJ-1449 - Launching a Socket Server
http://clojure.org/reference/repl_and_main#_launching_a_socket_server - API for clojure.string
http://clojure.github.io/clojure/branch-master/clojure.string-api.html - Clojars:
https://clojars.org/ - Seznam knihoven na Clojars:
https://clojars.org/projects - Clojure Cookbook: Templating HTML with Enlive
https://github.com/clojure-cookbook/clojure-cookbook/blob/master/07_webapps/7–11_enlive.asciidoc - An Introduction to Enlive
https://github.com/swannodette/enlive-tutorial/ - Enlive na GitHubu
https://github.com/cgrand/enlive - Expectations: příklady atd.
http://jayfields.com/expectations/ - Expectations na GitHubu
https://github.com/jaycfields/expectations - Lein-expectations na GitHubu
https://github.com/gar3thjon3s/lein-expectations - Testing Clojure With Expectations
https://semaphoreci.com/blog/2014/09/23/testing-clojure-with-expectations.html - Clojure testing TDD/BDD libraries: clojure.test vs Midje vs Expectations vs Speclj
https://www.reddit.com/r/Clojure/comments/1viilt/clojure_testing_tddbdd_libraries_clojuretest_vs/ - Testing: One assertion per test
http://blog.jayfields.com/2007/06/testing-one-assertion-per-test.html - Rewriting Your Test Suite in Clojure in 24 hours
http://blog.circleci.com/rewriting-your-test-suite-in-clojure-in-24-hours/ - Clojure doc: zipper
http://clojuredocs.org/clojure.zip/zipper - Clojure doc: parse
http://clojuredocs.org/clojure.xml/parse - Clojure doc: xml-zip
http://clojuredocs.org/clojure.zip/xml-zip - Clojure doc: xml-seq
http://clojuredocs.org/clojure.core/xml-seq - Parsing XML in Clojure
https://github.com/clojuredocs/guides - Clojure Zipper Over Nested Vector
https://vitalyper.wordpress.com/2010/11/23/clojure-zipper-over-nested-vector/ - 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.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