Asynchronní programování v Clojure s využitím knihovny core.async

Pavel Tišnovský 2. 2. 2016

V komunitě vývojářů používajících jazyk Clojure se velké popularitě těší knihovna core.async, která do jazyka přidává podporu pro asynchronní kanály známé například z Go. Popíšeme si základní možnosti této knihovny.

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

14. Odkazy na Internetu

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

  1. Clojure 1: Úvod
    http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm/
  2. Clojure 2: Symboly, kolekce atd.
    http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-2-cast/
  3. 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/
  4. 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/
  5. 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/
  6. Clojure 6: Podpora pro paralelní programování
    http://www.root.cz/clanky/programovaci-jazyk-clojure-6-futures-nejsou-jen-financni-derivaty/
  7. Clojure 7: Další funkce pro paralelní programování
    http://www.root.cz/clanky/programovaci-jazyk-clojure-7-dalsi-podpurne-prostredky-pro-paralelni-programovani/
  8. 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/
  9. 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/
  10. Clojure 10: Kooperace mezi Clojure a Javou
    http://www.root.cz/clanky/programovaci-jazyk-clojure-10-kooperace-mezi-clojure-a-javou-pokracovani/
  11. Clojure 11: Generátorová notace seznamu/list comprehension
    http://www.root.cz/clanky/programovaci-jazyk-clojure-11-generatorova-notace-seznamu-list-comprehension/
  12. 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/
  13. 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/
  14. Clojure 14: Základy práce se systémem maker
    http://www.root.cz/clanky/programovaci-jazyk-clojure-14-zaklady-prace-se-systemem-maker/
  15. Clojure 15: Tvorba uživatelských maker
    http://www.root.cz/clanky/programovaci-jazyk-clojure-15-tvorba-uzivatelskych-maker/
  16. Clojure 16: Složitější uživatelská makra
    http://www.root.cz/clanky/programovaci-jazyk-clojure-16-slozitejsi-uzivatelska-makra/
  17. Clojure 17: Využití standardních maker v praxi
    http://www.root.cz/clanky/programovaci-jazyk-clojure-17-vyuziti-standardnich-maker-v-praxi/
  18. Clojure 18: Základní techniky optimalizace aplikací
    http://www.root.cz/clanky/programovaci-jazyk-clojure-18-zakladni-techniky-optimalizace-aplikaci/
  19. Clojure 19: Vývojová prostředí pro Clojure
    http://www.root.cz/clanky/programovaci-jazyk-clojure-19-vyvojova-prostredi-pro-clojure/
  20. 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/
  21. Clojure 21: ClojureScript aneb překlad Clojure do JS
    http://www.root.cz/clanky/programovaci-jazyk-clojure-21-clojurescript-aneb-preklad-clojure-do-javascriptu/
  22. Leiningen: nástroj pro správu projektů napsaných v Clojure
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure/
  23. 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/
  24. 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/
  25. 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/
  26. 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/
  27. 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/
  28. Programovací jazyk Clojure a databáze (1.část)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-databaze-1-cast/
  29. Pluginy pro Leiningen
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-pluginy-pro-leiningen/
  30. 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/
  31. 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/
  32. 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/
  33. 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/
  34. 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/
  35. Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure
    http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure/
  36. 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/
  37. 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/
  38. Programovací jazyk Clojure a práce s Gitem
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem/
  39. Programovací jazyk Clojure a práce s Gitem (2)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem-2/
  40. Programovací jazyk Clojure – triky při práci s řetězci
    http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-retezci/
  41. Programovací jazyk Clojure – triky při práci s kolekcemi
    http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-kolekcemi/
  42. Programovací jazyk Clojure – práce s mapami a množinami
    http://www.root.cz/clanky/programovaci-jazyk-clojure-prace-s-mapami-a-mnozinami/
  43. Programovací jazyk Clojure – základy zpracování XML
    http://www.root.cz/clanky/programovaci-jazyk-clojure-zaklady-zpracovani-xml/
  44. Programovací jazyk Clojure – testování s využitím knihovny Expectations
    http://www.root.cz/clanky/programovaci-jazyk-clojure-testovani-s-vyuzitim-knihovny-expectations/
  45. 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/
  46. Enlive – výkonný šablonovací systém pro jazyk Clojure
    http://www.root.cz/clanky/enlive-vykonny-sablonovaci-system-pro-jazyk-clojure/
  47. 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/
  48. Novinky v Clojure verze 1.8.0
    http://www.root.cz/clanky/novinky-v-clojure-verze-1–8–0/

14. Odkazy na Internetu

  1. Communicating sequential processes
    https://en.wikipedia.org/wi­ki/Communicating_sequenti­al_processes
  2. Clojure core.async
    http://www.infoq.com/presen­tations/clojure-core-async
  3. core.async API Reference
    https://clojure.github.io/core.async/
  4. Clojure core.async Channels
    http://clojure.com/blog/2013/06/28/clo­jure-core-async-channels.html
  5. core.async examples
    https://github.com/clojure/co­re.async/blob/master/exam­ples/walkthrough.clj
  6. Timothy Baldridge – Core.Async
    https://www.youtube.com/wat­ch?v=enwIIGzhahw
  7. Designing Front End Applications with core.async
    http://go.cognitect.com/co­re_async_webinar_recording
  8. Mastering Concurrent Processes with core.async
    http://www.braveclojure.com/core-async/
  9. LispCast: Clojure core.async
    https://www.youtube.com/wat­ch?v=msv8Fvtd6YQ
  10. Julian Gamble – Applying the paradigms of core.async in ClojureScript
    https://www.youtube.com/wat­ch?v=JUrOebC5HmA
  11. Zip archiv s Clojure 1.8.0
    http://repo1.maven.org/ma­ven2/org/clojure/clojure/1­.8.0/clojure-1.8.0.zip
  12. Clojure 1.8 is now available
    http://clojure.org/news/2016/01/19/clo­jure18
  13. Changes to Clojure in Version 1.8
    https://github.com/clojure/clo­jure/blob/master/changes.md
  14. Socket Server REPL
    http://dev.clojure.org/dis­play/design/Socket+Server+REPL
  15. CLJ-1671: Clojure socket server
    http://dev.clojure.org/jira/browse/CLJ-1671
  16. CLJ-1449: Add clojure.string functions for portability to ClojureScript
    http://dev.clojure.org/jira/browse/CLJ-1449
  17. Launching a Socket Server
    http://clojure.org/referen­ce/repl_and_main#_launchin­g_a_socket_server
  18. API for clojure.string
    http://clojure.github.io/clo­jure/branch-master/clojure.string-api.html
  19. Clojars:
    https://clojars.org/
  20. Seznam knihoven na Clojars:
    https://clojars.org/projects
  21. Clojure Cookbook: Templating HTML with Enlive
    https://github.com/clojure-cookbook/clojure-cookbook/blob/master/07_webapps/7–11_enlive.asciidoc
  22. An Introduction to Enlive
    https://github.com/swannodette/enlive-tutorial/
  23. Enlive na GitHubu
    https://github.com/cgrand/enlive
  24. Expectations: příklady atd.
    http://jayfields.com/expectations/
  25. Expectations na GitHubu
    https://github.com/jaycfi­elds/expectations
  26. Lein-expectations na GitHubu
    https://github.com/gar3thjon3s/lein-expectations
  27. Testing Clojure With Expectations
    https://semaphoreci.com/blog/2014/09/23/tes­ting-clojure-with-expectations.html
  28. Clojure testing TDD/BDD libraries: clojure.test vs Midje vs Expectations vs Speclj
    https://www.reddit.com/r/Clo­jure/comments/1viilt/cloju­re_testing_tddbdd_librari­es_clojuretest_vs/
  29. Testing: One assertion per test
    http://blog.jayfields.com/2007/06/tes­ting-one-assertion-per-test.html
  30. Rewriting Your Test Suite in Clojure in 24 hours
    http://blog.circleci.com/rewriting-your-test-suite-in-clojure-in-24-hours/
  31. Clojure doc: zipper
    http://clojuredocs.org/clo­jure.zip/zipper
  32. Clojure doc: parse
    http://clojuredocs.org/clo­jure.xml/parse
  33. Clojure doc: xml-zip
    http://clojuredocs.org/clojure.zip/xml-zip
  34. Clojure doc: xml-seq
    http://clojuredocs.org/clo­jure.core/xml-seq
  35. Parsing XML in Clojure
    https://github.com/clojuredocs/guides
  36. Clojure Zipper Over Nested Vector
    https://vitalyper.wordpres­s.com/2010/11/23/clojure-zipper-over-nested-vector/
  37. Understanding Clojure's PersistentVector implementation
    http://blog.higher-order.net/2009/02/01/understanding-clojures-persistentvector-implementation
  38. Understanding Clojure's PersistentHashMap (deftwice…)
    http://blog.higher-order.net/2009/09/08/understanding-clojures-persistenthashmap-deftwice.html
  39. Assoc and Clojure's PersistentHashMap: part ii
    http://blog.higher-order.net/2010/08/16/assoc-and-clojures-persistenthashmap-part-ii.html
  40. Ideal Hashtrees (paper)
    http://lampwww.epfl.ch/pa­pers/idealhashtrees.pdf
  41. Clojure home page
    http://clojure.org/
  42. Clojure (downloads)
    http://clojure.org/downloads
  43. Clojure Sequences
    http://clojure.org/sequences
  44. Clojure Data Structures
    http://clojure.org/data_structures
  45. 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
  46. 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
  47. Clojure – Functional Programming for the JVM
    http://java.ociweb.com/mar­k/clojure/article.html
  48. Clojure quick reference
    http://faustus.webatu.com/clj-quick-ref.html
  49. 4Clojure
    http://www.4clojure.com/
  50. ClojureDoc (rozcestník s dokumentací jazyka Clojure)
    http://clojuredocs.org/
  51. Clojure (na Wikipedia EN)
    http://en.wikipedia.org/wiki/Clojure
  52. Clojure (na Wikipedia CS)
    http://cs.wikipedia.org/wiki/Clojure
  53. SICP (The Structure and Interpretation of Computer Programs)
    http://mitpress.mit.edu/sicp/
  54. Pure function
    http://en.wikipedia.org/wi­ki/Pure_function
  55. Funkcionální programování
    http://cs.wikipedia.org/wi­ki/Funkcionální_programová­ní
  56. Čistě funkcionální (datové struktury, jazyky, programování)
    http://cs.wikipedia.org/wi­ki/Čistě_funkcionální
  57. 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
  58. Clojure Macro Tutorial (Part II: The Compiler Strikes Back)
    http://www.learningclojure­.com/2010/09/clojure-macro-tutorial-part-ii-compiler.html
  59. Clojure Macro Tutorial (Part III: Syntax Quote)
    http://www.learningclojure­.com/2010/09/clojure-macro-tutorial-part-ii-syntax.html
  60. Tech behind Tech: Clojure Macros Simplified
    http://techbehindtech.com/2010/09/28/clo­jure-macros-simplified/
  61. Fatvat – Exploring functional programming: Clojure Macros
    http://www.fatvat.co.uk/2009/02/clo­jure-macros.html
  62. Eulerovo číslo
    http://cs.wikipedia.org/wi­ki/Eulerovo_číslo
  63. List comprehension
    http://en.wikipedia.org/wi­ki/List_comprehension
  64. List Comprehensions in Clojure
    http://asymmetrical-view.com/2008/11/18/list-comprehensions-in-clojure.html
  65. Clojure Programming Concepts: List Comprehension
    http://en.wikibooks.org/wi­ki/Clojure_Programming/Con­cepts#List_Comprehension
  66. Clojure core API: for macro
    http://clojure.github.com/clo­jure/clojure.core-api.html#clojure.core/for
  67. cirrus machina – The Clojure for macro
    http://www.cirrusmachina.com/blog/com­ment/the-clojure-for-macro/
  68. Riastradh's Lisp Style Rules
    http://mumble.net/~campbe­ll/scheme/style.txt
  69. Dynamic Languages Strike Back
    http://steve-yegge.blogspot.cz/2008/05/dynamic-languages-strike-back.html
  70. Scripting: Higher Level Programming for the 21st Century
    http://www.tcl.tk/doc/scripting.html
  71. Java Virtual Machine Support for Non-Java Languages
    http://docs.oracle.com/ja­vase/7/docs/technotes/gui­des/vm/multiple-language-support.html
  72. Třída java.lang.String
    http://docs.oracle.com/ja­vase/7/docs/api/java/lang/Strin­g.html
  73. Třída java.lang.StringBuffer
    http://docs.oracle.com/ja­vase/7/docs/api/java/lang/Strin­gBuffer.html
  74. Třída java.lang.StringBuilder
    http://docs.oracle.com/ja­vase/7/docs/api/java/lang/Strin­gBuilder.html
  75. StringBuffer versus String
    http://www.javaworld.com/ar­ticle/2076072/build-ci-sdlc/stringbuffer-versus-string.html
  76. Threading macro (dokumentace k jazyku Clojure)
    https://clojure.github.io/clo­jure/clojure.core-api.html#clojure.core/->
  77. Understanding the Clojure → macro
    http://blog.fogus.me/2009/09/04/un­derstanding-the-clojure-macro/
  78. clojure.inspector
    http://clojure.github.io/clo­jure/clojure.inspector-api.html
  79. The Clojure Toolbox
    http://www.clojure-toolbox.com/
  80. Unit Testing in Clojure
    http://nakkaya.com/2009/11/18/unit-testing-in-clojure/
  81. Testing in Clojure (Part-1: Unit testing)
    http://blog.knoldus.com/2014/03/22/tes­ting-in-clojure-part-1-unit-testing/
  82. API for clojure.test – Clojure v1.6 (stable)
    https://clojure.github.io/clo­jure/clojure.test-api.html
  83. Leiningen: úvodní stránka
    http://leiningen.org/
  84. Leiningen: Git repository
    https://github.com/techno­mancy/leiningen
  85. leiningen-win-installer
    http://leiningen-win-installer.djpowell.net/
  86. Clojure.org: Vars and the Global Environment
    http://clojure.org/Vars
  87. Clojure.org: Refs and Transactions
    http://clojure.org/Refs
  88. Clojure.org: Atoms
    http://clojure.org/Atoms
  89. Clojure.org: Agents as Asynchronous Actions
    http://clojure.org/agents
  90. Transient Data Structureshttp://clojure.or­g/transients
Našli jste v článku chybu?
Vitalia.cz: Tohle je Břicháč Tom, co zhubnul 27 kg

Tohle je Břicháč Tom, co zhubnul 27 kg

Měšec.cz: Platíme NFC mobilem. Konečně to funguje!

Platíme NFC mobilem. Konečně to funguje!

120na80.cz: Jak se zbavit nadměrného pocení?

Jak se zbavit nadměrného pocení?

DigiZone.cz: Test Noxon A560+: kvalitka do vaší věže

Test Noxon A560+: kvalitka do vaší věže

DigiZone.cz: Televizory Sony 4K 2016 přicházejí..

Televizory Sony 4K 2016 přicházejí..

DigiZone.cz: Epson: 4K projektory s podporou HDR

Epson: 4K projektory s podporou HDR

Vitalia.cz: Klíšťata letos řádí, skvrna se udělá jen někomu

Klíšťata letos řádí, skvrna se udělá jen někomu

Lupa.cz: Největší torrentový web KickassTorrents padl

Největší torrentový web KickassTorrents padl

Podnikatel.cz: Účtenky v rámci EET? Klidně emailem

Účtenky v rámci EET? Klidně emailem

Vitalia.cz: V dTestu vyhrál levný opalovací krém

V dTestu vyhrál levný opalovací krém

Lupa.cz: Japonská invaze. Proč SoftBank kupuje ARM?

Japonská invaze. Proč SoftBank kupuje ARM?

Podnikatel.cz: Co vše musíte udělat, než vypukne EET?

Co vše musíte udělat, než vypukne EET?

Podnikatel.cz: Tahle praktika stála šmejdy přes milion

Tahle praktika stála šmejdy přes milion

Lupa.cz: Největší pitominy s logem “nyní smart a připojené”

Největší pitominy s logem “nyní smart a připojené”

Lupa.cz: IT scéna po brexitu: přijde exodus vývojářů?

IT scéna po brexitu: přijde exodus vývojářů?

Podnikatel.cz: Selhala pokladna k EET. Kdo zaplatí pokutu?

Selhala pokladna k EET. Kdo zaplatí pokutu?

120na80.cz: 7 překážek při odvykání kouření

7 překážek při odvykání kouření

Podnikatel.cz: Od baletu k požární ochraně. A jiné rarity

Od baletu k požární ochraně. A jiné rarity

Vitalia.cz: Cvičení tabata: na hubnutí i posilování?

Cvičení tabata: na hubnutí i posilování?

Lupa.cz: Tudy proudí váš hlas i data. V zákulisí CETINu

Tudy proudí váš hlas i data. V zákulisí CETINu