Hlavní navigace

Řídicí struktury využitelné v programovacím jazyku Clojure (dokončení)

19. 1. 2021
Doba čtení: 43 minut

Sdílet

 Autor: Clojure
Dnes se zaměříme na některá další makra ze standardní knihovny: cond->, cond->> a case. Poté si ukážeme velmi užitečné makro cond-table určené pro zápis rozhodovacích tabulek.

Obsah

1. Řídicí struktury využitelné v programovacím jazyku Clojure (dokončení)

2. Makro cond->

3. Vyhodnocování všech větví makrem cond->

4. Expanze makra cond->

5. Makro cond->>

6. Expanze makra cond->>

7. Jedno z nejsložitějších maker ze standardní knihovny: case

8. Větší množství konstant u jednotlivých větví v makru case

9. Kdy použít cond, condp a case?

10. Expanze makra case a forma vygenerovaného bajtkódu

11. Makra ze jmenného prostoru clojure.core.logic

12. Od „jednorozměrných“ rozhodovacích konstrukcí ke konstrukcím dvourozměrným

13. Makro cond-table

14. Příklady použití makra cond-table

15. Expanze makra cond-table

16. A na konci je speciální forma if

17. Příloha: zdrojové kódy výše popsaných maker

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

19. Odkazy na předchozí části seriálu o programovacím jazyku Clojure

20. Odkazy na Internetu

1. Řídicí struktury využitelné v programovacím jazyku Clojure (dokončení)

V dnešním článku dokončíme téma, kterému jsme se věnovali minule. Nejprve si popíšeme některá další (a nutno říci, že velmi často používaná) makra nabízená standardní knihovnou programovacího jazyka Clojure. Jedná se především o makra cond->, cond->> (což jsou varianty makra cond, které již dobře známe), dále pak o makro if-not a v neposlední řadě o interně dosti složité makro nazvané příhodně case. To však není zdaleka vše, protože existují i další makra určená pro řízení toku programu. Především se jedná o makra nabízená ve jmenném prostoru clojure.core.logic, zejména o makra nazvaná conde, condu a conda. Asi nebude velkým překvapením, že se opět jedná o varianty standardního makra cond.

Ještě zajímavější jsou však makra z vybraných nestandardních knihoven, zejména pak velmi povedené makro cond-table, jehož autorem je Daniel Gregoire. S využitím cond-table je možné vytvářet i složité rozhodovací tabulky (což jsou de facto dvourozměrné rozhodovací struktury, které možnosti jednorozměrných struktur dosti podstatným způsobem zobecňují). Později se zmíníme i o složitějších rozhodovacích strukturách nabízených například knihovnou special, better-cond, cond-let atd.

# Konstrukce Článek/kapitola Typ Stručný popis
1 if 1/2 speciální forma základní rozhodovací konstrukce, základ pro další makra
2 if-not   makro stejné jako speciální forma if, ovšem s negovanou podmínkou
3 if+do 1/3 dvě speciální formy použito ve chvíli, kdy je nutné do jedné větve či obou větví zapsat více výrazů
4 if-let 1/5 makro kombinace speciálních forem if a let
5 if-some 1/6 makro kombinace speciálních forem if (test na nil) a let
         
6 and 1/7 makro postupné vyhodnocování předaných výrazů až do chvíle, kdy se vrátí nil či false
7 or 1/7 makro postupné vyhodnocování předaných výrazů až do chvíle, kdy se vrátí true
         
8 when 1/4 makro vhodná náhrada za if s jedinou větví s více výrazy
9 when-not 1/4 makro vhodná náhrada za if-not s jedinou větví s více výrazy
10 when-let 1/5 makro kombinace speciálních forem when a let
11 when-some 1/6 makro kombinace speciálních forem when (test na nil) a let
12 when-first   makro použito při testu prvního prvku sekvence s následným zpracováním sekvence
         
13 cond 1/8 makro postupné testování podmínek, pokud je podmínka splněna, vrátí se hodnota příslušného výrazu
14 cond + :else 1/8 makro typické použití makra cond s větví :else nebo :default
15 condp 1/10 makro postupné dosazování testované hodnoty do zadaného výrazu, obdoba switch-case
         
16 conde 2/11 makro makro z knihovny clojure.core.logic (logické programování)
17 condu 2/11 makro makro z knihovny clojure.core.logic (logické programování)
18 conda 2/11 makro makro z knihovny clojure.core.logic (logické programování)
       
19 cond-> 2/2 makro odvozeno od cond a threading makra ->
20 cond->> 2/5 makro odvozeno od cond a threading makra ->
         
21 case 2/7 makro rozeskok na základě porovnání hodnoty výrazu s konstantami
         
22 cond-table 2/13 makro nová nestandardní konstrukce
Poznámka: v ukázkách zdrojových kódů jsou použity tyto typografické konvence:
součást zdrojového kódu (zapisovaná do smyčky REPL)
zvýraznění právě popisovaného makra
výsledná hodnota, popř. text vytištěný funkcemi print, println atd.

2. Makro cond->

První makro s nímž se v dnešním článku setkáme a které nám umožňuje zapsat rozvětvení na základě vyhodnocovaných podmínek, se jmenuje cond->. Jedná se o makro ze standardní knihovny (ta je automaticky dostupná), takže si můžeme ihned vypsat nápovědu k tomuto makru:

(doc cond->)
-------------------------
clojure.core/cond->
([expr & clauses])
Macro
  Takes an expression and a set of test/form pairs. Threads expr (via -&)
  through each form for which the corresponding test
  expression is true. Note that, unlike cond branching, cond-> threading does
  not short circuit after the first true test expression.

Toto na první pohled poněkud neobvyklé pojmenování nám naznačuje, že se bude jednat o konstrukci, která je založena na takzvaném threading makru. Samotné threading makro je přitom jednou z nejelegantnějších konstrukcí programovacího jazyka Clojure vůbec:

(doc ->)
 
-------------------------
clojure.core/->
([x & forms])
Macro
  Threads the expr through the forms. Inserts x as the
  second item in the first form, making a list of it if it is not a
  list already. If there are more forms, inserts the first form as the
  second item in second form, etc.

Toto makro nám umožňuje zapisovat sérii funkcí, mezi nimiž se předávají parametry podobně, jako je tomu v klasické koloně (pipe). Samotný zápis je přitom velmi stručný a obejde se bez řady závorek, které by jinak bylo nutné použít v případě NEpoužití threading makra:

(-> 1
    inc
    println)
 
2

Další jednoduchý příklad:

(-> (range 1 10)
    count
    println)
 
9

Volat je ovšem možné i funkce s větším množstvím parametrů, včetně metod:

(-> "Hello world."
    .toUpperCase
    (.replace "." "!")
    println)
 
HELLO WORLD!

Popř.:

(-> "Hello world."
    .toUpperCase
    (.replace "." "!")
    (.split " ")
    first
    println)
 
HELLO

Expanze tohoto makra vede k zápisu s velkým množstvím závorek tak typickým pro klasické LISPovské jazyky bez tohoto makra:

(macroexpand
  '(-> "Hello world."
      .toUpperCase
      (.replace "." "!")
      (.split " ")
      first
      println))

S výsledkem:

(println (first (.split (.replace (.toUpperCase "Hello world.") "." "!") " ")))

Vraťme se však k makru cond->, které je odvozené od makra cond. Jeho činnost je následující: postupně jsou testovány jednotlivé podmínky (sudé parametry) a pokud je podmínka splněna, je do výrazu za ní (liché parametry) dosazen první parametr makra, podobně jako je tomu u výše zmíněného threading makra (tedy na první pozici ve volané funkci či metodě).

V následujícím úryvku kódu se pokoušíme převést obsah řetězce na číselnou hodnotu, ovšem s testem, zda je vstupní hodnota skutečně řetězcem. S využitím makra cond-> se samotné x (tedy reference na převáděnou hodnotu) nemusí opakovat v podmínce a současně i v převodní funkci:

(let [x "42"] 
  (cond-> x 
    (string? x) (Integer.)))
 
42

Poněkud umělý příklad, který je na makru cond-> postaven, může testovat hodnotu parametru x a následně na základě hodnoty tohoto parametru provádí operace (inc x), (/ x 2) a (dec x):

(defn machina
  [x]
  (cond-> x 
    (odd? x)  inc
    (even? x) (/ 2)
    (zero? x) dec))

Výše uvedenou funkci si otestujeme na vstupních hodnotách od nuly do deseti:

(doseq [x (range 0 11)]
  (println x (machina x)))

S těmito výsledky:

0 -1
1 2
2 1
3 4
4 2
5 6
6 3
7 8
8 4
9 10
10 5
Poznámka: ve skutečnosti je ve funkci logická chyba, která se však navenek neprojeví, protože se týká vstupní hodnoty 0. Problém spočívá v tom, že pro nulu platí dvě podmínky even?zero? – viz další kapitolu.

3. Vyhodnocování všech větví makrem cond->

Na rozdíl od makra cond, které při splnění nějaké podmínky z celé sady již další podmínky netestuje, je tomu u makra cond-> odlišně, protože se postupně prochází všechny podmínky. To může vést k chybám, zejména tehdy, pokud se použije větev :else (symbol :else se totiž vždy vyhodnotí na pravdu). Pokud tedy předchozí příklad nepatrně upravíme:

(defn machina-2
  [x]
  (cond-> x 
    (odd? x)  inc
    (even? x) (/ 2)
    :else     dec))

Získáme odlišné výsledky, což si ostatně můžeme velmi snadno ukázat:

(doseq [x (range 0 11)]
  (println x (machina x) (machina-2 x)))

Tabulka vypsaná předchozí smyčkou zobrazuje postupně vstupní hodnotu, výpočet provedený první funkcí machina a výpočet provedený funkcí upravenou machina-2:

0 -1 -1
1 2 1
2 1 0
3 4 3
4 2 1
5 6 5
6 3 2
7 8 7
8 4 3
9 10 9
10 5 4
Poznámka: vidíme, že se výsledky odlišují o jedničku, která byla ve větvi :else odečtena od všech výsledků.

Ještě lépe je toto chování patrné v případě, že se v jednotlivých větvích nachází funkce s vedlejším efektem, zde konkrétně funkce println:

(defn machina-3
  [x]
  (cond-> x 
    (odd? x)  (println "odd")
    (even? x) (println "even")
    :else     (println "zero")))

Zkusme si tuto funkci vyvolat postupně s hodnotami od 0 do 10:

(doseq [x (range 0 11)]
  (println x)
  (machina-3 x)
  (println))

Z vypsaných výsledků je patrné, že se vždy vyhodnotí i poslední větev:

0
0 even
nil zero
 
1
1 odd
nil zero
 
2
2 even
nil zero
 
3
3 odd
nil zero
 
4
4 even
nil zero
 
5
5 odd
nil zero
 
6
6 even
nil zero
 
7
7 odd
nil zero
 
8
8 even
nil zero
 
9
9 odd
nil zero
 
10
10 even
nil zero
Poznámka: funkce println v tomto případě ve skutečnosti vypíše i hodnotu parametru x, který je do println dosazen jako první parametr makrem cond->, popř. návratová hodnota z jednotlivých větví. Proč se vždy vypisuje nil uvidíme v navazující kapitole po expanzi makra.

Makro cond-> je tedy většinou vhodné použít pouze tehdy, pokud se množiny hodnot, pro které podmínky platí, nepřekrývají.

4. Expanze makra cond->

Otestovat si můžeme i expanzi makra cond->. Pro výpis expandovaného makra opět použijeme známou funkci macroexpand ze standardní knihovny programovacího jazyka Clojure:

(macroexpand
  '(cond-> x 
     (odd? x)  inc
     (even? x) (/ 2)
     (zero? x) inc))

Výsledkem by měl být následující kód (pomocná automaticky vygenerovaná proměnná může mít ovšem odlišné jméno):

(let* [G__10086 x
       G__10086 (if (odd? x)  (clojure.core/-> G__10086 inc) G__10086)
       G__10086 (if (even? x) (clojure.core/-> G__10086 (/ 2)) G__10086)]
  (if (zero? x) (clojure.core/-> G__10086 inc) G__10086))
Poznámka: povšimněte si, že se skutečně postupně vyhodnocují všechny větve a současně se interně používá threading makro ->. Navíc se postupně přepisují hodnoty předávané do větví (lokální symbol G__10086), což v předchozí kapitole vedlo k výpisu hodnot nil – návratových hodnot funkce println!

Příklad expanze ne plně funkčního kódu s větví else:

(macroexpand
  '(cond-> x 
     (odd? x)  inc
     (even? x) (/ 2)
     :else     inc))

Nyní bude expanze vypadat následovně:

(let* [G__10117 x
       G__10117 (if (odd? x) (clojure.core/-> G__10117 inc) G__10117)
       G__10117 (if (even? x) (clojure.core/-> G__10117 (/ 2)) G__10117)]
  (if :else (clojure.core/-> G__10117 inc) G__10117))

Extrémní příklad, v němž se provedou všechny větve a současně se mezivýsledek pětkrát zvětší o jedničku:

(cond-> 1
  :foo     inc
  :bar     inc
  :baz     inc
  :else    inc
  :default inc)
 
6

Bude expandován takto:

(macroexpand
  '(cond-> 1
    :foo     inc
    :bar     inc
    :baz     inc
    :else    inc
    :default inc))
 
(let*
  [G__11216 1
   G__11216 (if :foo (clojure.core/-> G__11216 inc) G__11216)
   G__11216 (if :bar (clojure.core/-> G__11216 inc) G__11216)
   G__11216 (if :baz (clojure.core/-> G__11216 inc) G__11216)
   G__11216 (if :else (clojure.core/-> G__11216 inc) G__11216)]
  (if :default (clojure.core/-> G__11216 inc) G__11216))
Poznámka: opět je patrné postupné přepisování, resp. přesněji řečeno redefinice lokálního symbolu G__11216.

5. Makro cond->>

Druhé dnes popisované makro určené pro implementaci rozvětvení na základě zadaných podmínek se jmenuje cond->>:

(doc cond->>)
 
-------------------------
clojure.core/cond->>
([expr & clauses])
Macro
  Takes an expression and a set of test/form pairs. Threads expr (via ->>)
  through each form for which the corresponding test expression
  is true.  Note that, unlike cond branching, cond->> threading does not short circuit
  after the first true test expression.

Opět se jedná o variantu makra cond, tentokrát ovšem zkombinovanou s alternativní formou threading makra ->>:

(doc ->>)
 
-------------------------
clojure.core/->>
([x & forms])
Macro
  Threads the expr through the forms. Inserts x as the
  last item in the first form, making a list of it if it is not a
  list already. If there are more forms, inserts the first form as the
  last item in second form, etc.
Poznámka: makro ->> dosazuje výsledek předchozí formy do posledního parametru následující formy:
(->> (range 1 11)
   (filter even?))
 
(2 4 6 8 10)

Součet všech sudých čísel vybraných z řady 0..10:

(->> (range 1 11)
  (filter even?)
  (reduce +))
 
30

Vraťme se k makru cond->>. Na rozdíl od výše popsaného makra cond-> se liší dosazování parametru do jednotlivých větví – parametr je dosazen na poslední místo výrazu ve větvi (ta je typicky představována voláním nějaké funkce) a nikoli na místo první. Pokud tedy například použijeme tuto funkci:

(defn machina-3
  [x]
  (cond->> x 
    (odd? x)  inc
    (even? x) (/ 2)))

bude se ve druhé větvi vyhodnocovat funkce (/ 2 x) a nikoli (/ x 2).

Výše definovanou funkci machina-3 si otestujeme na vstupních hodnotách od nuly do deseti:

(doseq [x (range 1 11)]
  (println x (machina-3 x)))

S výsledkem:

1 2
2 1
3 4
4 1/2
5 6
6 1/3
7 8
8 1/4
9 10
10 1/5
Poznámka: jen na okraj – povšimněte si, že programovací jazyk Clojure plně podporuje zlomky, tj. datový typ rational.

Nepatrně složitější příklad, který se snaží převést parametr na celé číslo:

(defn ->int
  [x]
  (cond->> x 
    (string? x)   Integer.
    (float?  x)   int
    (rational? x) int
    (integer? x)  identity))

Chování této funkce si můžeme snadno otestovat:

(println (->int 3.14))
3
 
(println (->int 10/3))
3
 
(println (->int 42))
42
 
(println (->int "1000"))
1000

6. Expanze makra cond->>

Způsob expanze makra cond->> si ukážeme na příkladu převodníku hodnoty typu řetězec, čísla s plovoucí řádovou čárkou, zlomku či celého čísla na hodnotu typu integer (tedy celé číslo). Expanzi si opět zobrazíme standardní funkcí macroexpand popř. macroexpand-1:

(macroexpand
  '(cond->> x 
    (string? x)   Integer.
    (float?  x)   int
    (rational? x) int
    (integer? x)  identity))

Výsledek expanze by měl v tomto případě vypadat následovně:

(let*
  [G__10237 x
   G__10237 (if (string? x) (clojure.core/->> G__10237 Integer.) G__10237)
   G__10237 (if (float? x) (clojure.core/->> G__10237 int) G__10237)
   G__10237 (if (rational? x) (clojure.core/->> G__10237 int) G__10237)]
  (if (integer? x) (clojure.core/->> G__10237 identity) G__10237))
Poznámka: povšimněte si, že se skutečně jednotlivá přiřazení provádí bez ohledu na to, zda byl předchozí test úspěšný nebo neúspěšný. To je největší rozdíl maker cond-> a cond->> oproti makru cond.

7. Jedno z nejsložitějších maker ze standardní knihovny: case

Interně nejsložitější makro ze standardní knihovny, které si dnes popíšeme, se jmenuje case:

(doc case)
 
-------------------------
clojure.core/case
([e & clauses])
Macro
  Takes an expression, and a set of clauses.
 
  Each clause can take the form of either:
 
  test-constant result-expr
 
  (test-constant1 ... test-constantN)  result-expr
 
  The test-constants are not evaluated. They must be compile-time
  literals, and need not be quoted.  If the expression is equal to a
  test-constant, the corresponding result-expr is returned. A single
  default expression can follow the clauses, and its value will be
  returned if no clause matches. If no default expression is provided
  and no clause matches, an IllegalArgumentException is thrown.
 
  Unlike cond and condp, case does a constant-time dispatch, the
  clauses are not considered sequentially.  All manner of constant
  expressions are acceptable in case, including numbers, strings,
  symbols, keywords, and (Clojure) composites thereof. Note that since
  lists are used to group multiple constants that map to the same
  expression, a vector can be used to match a list if needed. The
  test-constants need not be all of the same type.

Toto makro se do značné míry podobá céčkové či javovské konstrukci switch, protože výsledek zadaného výrazu je porovnáván s konstantami zapsanými před jednotlivými větvemi. To sice není tak obecné řešení, jako je tomu v případě maker cond a condp, ovšem výsledkem by měl být rychlejší běh, protože se interně zkonstruuje mapa s jednotlivými větvemi a výběr větve je tak proveden v téměř konstantním čase a nikoli postupným porovnáváním. Navíc existuje speciální režim expanze pro celočíselné hodnoty.

Poznámka: v předchozím odstavci bylo napsáno „téměř konstantní čas“. Ve skutečnosti záleží na tom, jak je v daném virtuálním stroji Javy (JVM) realizována instrukce bajtkódu tableswitch.

Podívejme se nyní na jednotlivé způsoby použití makra case pro vytvoření rozhodovací konstrukce. Nejprve převod celočíselných hodnot na řetězce (což by ovšem bylo rozumnější implementovat přes mapy!):

(defn say-number
  [x]
  (case x
    0 "nula"
    1 "jedna"
    2 "dva"))

Otestování funkce say-number:

(println (say-number 0))
nula
 
(println (say-number 1))
jedna
 
(println (say-number 2))
dva

Prozatím ovšem nemáme nijak zajištěnu funkčnost ve chvíli, kdy se předá hodnota neodpovídající žádné větvi:

(println (say-number 3))
 
Execution error (IllegalArgumentException) at user/say-number (REPL:3).
No matching clause: 3

To lze ovšem velmi snadno napravit uvedením takzvané default větve, resp. default výrazu (bez konstanty):

(defn say-number
  [x]
  (case x
    0 "nula"
    1 "jedna"
    2 "dva"
      "nezname cislo"))

Otestování nové podoby funkce say-number:

(println (say-number 0))
nula
 
(println (say-number 1))
jedna
 
(println (say-number 2))
dva
 
(println (say-number 3))
nezname cislo

Nejsme ovšem v žádném případě omezeni pouze na celočíselné konstanty

(defn string->number
  [x]
  (case x
    "nula"  0
    "jedna" 1
    "dva"   2
            -1))

Následuje otestování funkce string->number:

(println (string->number "nula"))
0
 
(println (string->number "jedna"))
1
 
(println (string->number "milion"))
-1

8. Větší množství konstant u jednotlivých větví v makru case

Jednu větev je možné provést (resp. přesněji řečeno vyhodnotit) pro více konstant, které ovšem musí být v tomto případě umístěny do závorek:

(defn string->number
  [x]
  (case x
    ("nic" "nula")  0
    "jedna"         1
    ("dva" "dve")   2
                   -1))
Poznámka: v Clojure obecně platí, že kulaté závorky znamenají vyhodnocení formy (zavolání funkce atd.), což ovšem nemusí platit u maker – a je tomu tak i v případě makra case.

Otestování předchozí funkce:

(println (string->number "nula"))
0
 
(println (string->number "dva"))
2
 
(println (string->number "dve"))
2

Konstantami mohou být v případě potřeby i vektory, mapy atd.

(defn say-vector
  [x]
  (case x
    []    "Empty vector"
    [1]   "Vector with just one item"
    [1 2] "Sequence of two numbers 1 and 2"
          "I don't know"))

Vyzkoušení funkce say-vector:

(say-vector [])
"Empty vector"
 
(say-vector [1 2])
"Sequence of two numbers 1 and 2"

9. Kdy použít cond, condp a case?

Nyní již známe celou trojici standardních maker pro rozeskoky – cond, condp a case. Makro cond je nejobecnější, protože každá podmínka může být zadána samostatným výrazem. Ovšem za tuto univerzálnost platíme menší čitelností. Pokud je nutné testy implementovat podobnými výrazy, je výhodnější použít condp a v případě, že se porovnává výsledek nějakého výrazu s konstantami, doporučuje se použít case, viz též Clojure Style Guide.

Makro cond je sice nejuniverzálnější, ale taktéž delší na zápis a i výpočetně složité:

(cond
  (= x 0) "Nula"
  (= x 1) "Jedna"
  (= x 2) "Dva"
  :else   "Nevim")

Makro condp umožňuje kratší zápis podmínky na jediném místě (první parametry makra):

(condp = x
  0 "Nula"
  1 "Jedna"
  2 "Dva"
    "Nevim")

Nejméně univerzální je makro case, ovšem na druhou stranu je nejrychlejší:

(case x
  0 "Nula"
  1 "Jedna"
  2 "Dva"
    "Nevim")

Rychlejší rozeskoky v případě použití makra case by mělo být možné i naměřit, pochopitelně ovšem za předpokladu, že důvěřujete mikrobenchmarkům (což je zrovna v případě JVM dosti problematické):

(defn f1
  [x]
  (cond
    (= x 0) "Nula"
    (= x 1) "Jedna"
    (= x 2) "Dva"
    :else   "Nevim"))
 
(defn f2
  [x]
  (condp = x
    0 "Nula"
    1 "Jedna"
    2 "Dva"
      "Nevim"))
 
(defn f3
  [x]
  (case x
    0 "Nula"
    1 "Jedna"
    2 "Dva"
      "Nevim"))
 
(time (doseq [_ (range 100000000)]
        (doseq [x (range 0 4)]
          (f1 x))))
"Elapsed time: 4252.930607 msecs"
 
(time (doseq [_ (range 100000000)]
        (doseq [x (range 0 4)]
          (f2 x))))
"Elapsed time: 4417.662463 msecs"
 
(time (doseq [_ (range 100000000)]
        (doseq [x (range 0 4)]
          (f3 x))))
"Elapsed time: 3924.937731 msecs"
Poznámka: na druhou stranu je vhodné poznamenat, že u programovacího jazyka typu Clojure (dynamicky typovaný vysokoúrovňový jazyk běžící nad JVM) je většinou mnohem důležitější sémantika zapisovaných operací (tj. i zde má makro case význam) nad ušetřeným strojovým časem.

10. Expanze makra case a forma vygenerovaného bajtkódu

V krátkosti se ještě zmiňme o způsobu expanze makra case. Ta se v jednom ohledu odlišuje od expanze ostatních maker, a to konkrétně v tom směru, že výsledkem expanze není kód založený na speciální formě if. Namísto toho se v expanzi setkáme s case*, což je metoda implementovaná v Javě, která slouží ke konstrukci rozeskoku na úrovni bajtkódu (viz též zdrojové kódy samotného Clojure). Ostatně se o tom můžeme velmi snadno přesvědčit:

(macroexpand
  '(case x
    0 "Nula"
    1 "Jedna"
    2 "Dva"
      "Nevim"))
 
(let*
 [G__11190 x]
 (case*
  G__11190
  0
  0
  "Nevim"
  {0 [0 "Nula"] 1 [1 "Jedna"] 2 [2 "Dva"]}
  :compact
  :int))

Abychom pochopili, jak celá technologie pracuje, musíme si připomenout, že Clojure pracuje tak, že každou zapsanou formu přeloží (po expanzi maker) do bajtkódu JVM a teprve poté je tato forma spuštěna. A právě pro překlad do bajtkódu se interně používá třída Compiler, která konkrétně interní symbol case* přeloží s využitím instrukce tableswitch určené pro implementaci konstrukce switch známé z Javy (resp. přesněji switch v případě, že jsou použita celá čísla nebo výčtový typ, nikoli řetězce). Touto problematikou jsme se již zabývali v článcích https://www.root.cz/clanky/pohled-pod-kapotu-jvm-9-cast-tajemstvi-instrukci-lookupswitch-a-tableswitch/#k04 a https://www.root.cz/clanky/pohled-pod-kapotu-jvm-9-cast-tajemstvi-instrukci-lookupswitch-a-tableswitch/#k07.

V praxi vypadá překlad funkce f3:

(defn f3
  [x]
  (case x
    0 "Nula"
    1 "Jedna"
    2 "Dva"
      "Nevim"))

následovně (zvýrazněna je instrukce tableswitch:

public static java.lang.Object invokeStatic(java.lang.Object x);
   0  aload_0 [x]
   1  aconst_null
   2  astore_0 [x]
   3  astore_1 [G__5991]
   4  aload_1 [G__5991]
   5  instanceof java.lang.Number [13]
   8  ifeq 89
  11  aload_1 [G__5991]
  12  checkcast java.lang.Number [13]
  15  invokevirtual java.lang.Number.intValue() : int [17]
  18  tableswitch default: 89
        case 0: 44
        case 1: 59
        case 2: 74
  44  aload_1 [G__5991]
  45  getstatic user$f3.const__0 : java.lang.Object [21]
  48  invokestatic clojure.lang.Util.equiv(java.lang.Object, java.lang.Object) : boolean [27]
  51  ifeq 89
  54  ldc <String "Nula"> [29]
  56  goto 91
  59  aload_1 [G__5991]
  60  getstatic user$f3.const__1 : java.lang.Object [32]
  63  invokestatic clojure.lang.Util.equiv(java.lang.Object, java.lang.Object) : boolean [27]
  66  ifeq 89
  69  ldc <String "Jedna"> [34]
  71  goto 91
  74  aload_1 [G__5991]
  75  getstatic user$f3.const__2 : java.lang.Object [37]
  78  invokestatic clojure.lang.Util.equiv(java.lang.Object, java.lang.Object) : boolean [27]
  81  ifeq 89
  84  ldc <String "Dva"> [39]
  86  goto 91
  89  ldc <String "Nevim"> [41]
  91  areturn
Poznámka: pro získání bajtkódu lze použít buď nástroj javap dodávaný společně s JVM, nebo – což je jednodušší – knihovnu nazvanou no.disassemble, která je dostupná na adrese https://github.com/gtrak/no­.disassemble. Popis této knihovny přesahuje rozsah dnešního článku, ovšem zmíníme se o ní později v samostatném textu.

11. Makra ze jmenného prostoru clojure.core.logic

V této kapitole se ve stručnosti zmíníme o makrech, která jsou definována ve jmenném prostoru clojure.core.logic. V tomto jmenném prostoru nalezneme knihovnu, která do programovacího jazyka Clojure přináší konstrukce známé z Prologu, které byly popsány například v The Reasoned Schemer (viz též: The Reasoned Schemer, Second Edition: https://mitpress.mit.edu/bo­oks/reasoned-schemer-second-edition). Načtení funkcí, maker a dalších symbolů z tohoto jmenného prostoru si musíme explicitně vyžádat, například použitím require či use:

(use 'clojure.core.logic)

Mezi makra, která se zde používají pro vyhodnocení podmínek, patří condu, conda a conde. Jejich podrobnějším popisem se budeme zabývat v samostatném článku o logickém programování v Clojure.

12. Od „jednorozměrných“ rozhodovacích konstrukcí ke konstrukcím dvourozměrným

Existuje poměrně mnoho algoritmů postavených na takzvaných rozhodovacích tabulkách. Tyto tabulky mají podobu skutečných tabulek, v nichž se v hlavičkách sloupců i řádků zapisují podmínky a do buněk tabulky pak vypočtená hodnota popř. akce, která se má provést (tedy volání nějaké funkce). V běžných programovacích jazycích většinou nelze tyto rozhodovací tabulky zapisovat přímo, takže se setkáme buď s využitím map (popř. map obsahujících další mapy), vnořených konstrukcí switch apod. To ovšem není ani přehledné ani to neodpovídá implementovanému algoritmu (rozhodovací tabulka totiž může být součástí zadání). V programovacím jazyku Clojure však díky jeho makrosystému a homoikonicitě existuje minimálně jedno elegantní řešení, a to konkrétně ve formě makra nazvaného příznačně cond-table, jehož autorem je Daniel Gregoire.

Pokud je například nutné implementovat tabulku, která na základě dvou dvojic predikátů určí, která další funkce se má zavolat:

+------------++--------------+------------+
|            || in-progress? | final-run? |
+------------++--------------+------------+
| succeeded? || succeed      | succeed    |
|    failed? || retry        | terminate  |
+------------++--------------+------------+

Je možné tuto rozhodovací tabulku zapsat do zdrojového kódu zcela přímočarým způsobem:

(cond-table
  :|             in-progress?   final-run?
  :| succeeded?  (succeed)      (succeed)
  :|    failed?  (retry)        (terminate))

Taktéž někdy můžeme chtít pouze vrátit určitou hodnotu na základě podmínek a nevolat žádné funkce:

+------------++-----------------+----------------+--------------------+
|            || operace=login   | operace=logout | operace=index-page |
+------------++-----------------+----------------+--------------------+
| status=200 || :index-page     | :logout-page   | :index-page        |
| status=401 || :not-authorized | :invalid-state | :invalid-state     |
| status=404 || :not-found      | :not-found     | :not-found)        |
+------------++-----------------+----------------+--------------------+

I tuto tabulku lze do zdrojového kódu zapsat přímočaře:

(cond-table
    :|                   (= oper :login)   (= oper :logout)  (= oper :index-page)
    :|   (= status 200)  :index-page       :logout-page      :index-page
    :|   (= status 401)  :not-authorized   :invalid-state    :invalid-state
    :|   (= status 404)  :not-found        :not-found        :not-found)
Poznámka: v mnoha případech vede zařazení tohoto makra k výraznému zjednodušení i zpřehlednění výsledného kódu.

13. Makro cond-table

Makro cond-table sice není příliš rozsáhlé, ovšem je strukturováno velmi zajímavě. Nejdříve je (v expandovaném kódu) zkontrolována tabulka zapsaná programátorem a následně se z této tabulky sestaví sekvence forem cond (a ty jsou v další expanzi převedeny na speciální formu if). Úplný zdrojový kód tohoto makra vypadá následovně:

(defmacro cond-table
  "Produce a `cond` expression from a tabular representation of its clauses".
 
  [& items]
  (let [rows (validate-cond-table items)
        rights (first rows)  ;; get right-hand conditions
        rights (if (and (symbol? (first rights))
                        (every? (partial = \_) (name (first rights))))
                 (next rights)
                 rights)
        op-omitted? (= (count (second rows))
                       (inc (count rights)))
        [op rights] (if op-omitted?
                      ['and rights]
                      [(first rights) (next rights)])]
    (cons 'cond
          (mapcat
           (fn [[left-condition & exprs :as row]]
             (mapcat
              (fn [right-condition expr]
                ;; `cond` test/expr pair:
                (list (list op left-condition right-condition) expr))
              rights exprs))
           (next rows)))))

Při expanzi makra se ve výsledném kódu nejdříve provede kontrola (nebo řekněme validace) zapsané tabulky. To zajišťuje pomocná funkce nazvaná příznačně validate-cond-table:

(defn validate-cond-table
  "Validate the arguments passed to the `cond-table` macro and return
  the data of the table rows."
  [items]
  (let [rs (into []
                 (comp (partition-by (partial = :|))
                       (partition-all 2))
                 items)
        _ (when-not (every? #(= '(:|) (first %)) rs)
            (throw (IllegalArgumentException. "Each row in cond-table must begin with the keyword :|")))
        rows (map second rs) ;; remove :| syntax
        header-count (count (first rows))
        next-row-counts (into #{} (map count) (next rows))
        next-rows-same-count? (= 1 (count next-row-counts))
        ;; First row with blank first cell, for default `and` behavior
        default-header-validates? (= (inc header-count) (first next-row-counts))
        ;; First row with custom op in first cell
        op-header-validates? (= header-count (first next-row-counts))
        ;; All rows after the first must be same length and first row is either
        ;; the same length (because a custom op was supplied) or has one item
        ;; fewer (default of `and` is being leveraged).
        _ (when-not (and next-rows-same-count?
                         (or default-header-validates?
                             op-header-validates?))
            (throw (IllegalArgumentException. "Every row after the first in cond-table must start with a predicate and include an expression for each cell in the table.")))]
    rows))

14. Příklady použití makra cond-table

Celá vyjadřovací síla nabízená makrem cond-table vynikne až ve chvíli, kdy toto makro použijeme v reálných příkladech. Například se můžeme rozhodnout na základě predikátů even?, odd?, zero? a not-zero? nad tím, jaká hodnota se má vrátit z funkce nazvané choose. Nejdříve ovšem musíme definovat predikát not-zero?, který není součástí standardní knihovny programovacího jazyka Clojure:

(defn not-zero?
  [n]
  (not (zero? n)))

nebo dáváte-li přednost threading makru:

(defn not-zero?
  [n]
  (-> n zero? not))

Funkce choose s rozhodovací tabulkou bude vypadat následovně:

(defn choose
  [x]
  (cond-table
    :|                 (even? x)   (odd? x)
    :|      (zero? x)  :a           :b        
    :|  (not-zero? x)  :c           :d))

Základní otestování funkčnosti:

(println (choose 0))
a
 
(println (choose 1))
d
 
(println (choose 2))
c
 
(println (choose 3))
d
 
(println (choose 4))
c
Poznámka: stav :b pochopitelně nemůže v praxi nastat, ale to bývá u mnoha rozhodovacích tabulek běžné.

Praktičtější může být funkce použitá například v testu webové aplikace, která na základě aktuální operace a stavového kódu vráceného z webové aplikace rozhodne o tom, jaká stránka se má očekávat v dalším kroku. Jedná se tedy vlastně o implementaci stavového automatu založeného na vstupní hodnotě a na předchozím stavu:

(defn next-operation
  [oper status]
  (cond-table
    :|                   (= oper :login)   (= oper :logout)  (= oper :index-page)
    :|   (= status 200)  :index-page       :logout-page      :index-page
    :|   (= status 401)  :not-authorized   :invalid-state    :invalid-state
    :|   (= status 404)  :not-found        :not-found        :not-found))

Opět se podívejme, jak tato funkce pracuje, projdeme ovšem celý stavový prostor:

(doseq [operation [:login :logout :index-page]]
  (doseq [status [200 401 404]]
    (println operation status (next-operation operation status))))

S výsledkem:

:login 200 :index-page
:login 401 :not-authorized
:login 404 :not-found
:logout 200 :logout-page
:logout 401 :invalid-state
:logout 404 :not-found
:index-page 200 :index-page
:index-page 401 :invalid-state
:index-page 404 :not-found
Poznámka: rozhodovací tabulku je pochopitelně možné libovolným způsobem rozšiřovat, ovšem stále se musíme držet její 2D struktury.

15. Expanze makra cond-table

Makro cond-table se expanduje na formu obsahující další makro, konkrétně cond. Podmínky ze sloupců a řádků jsou spojeny s využitím logické spojky and, i když cond-table podporuje specifikaci libovolné jiné funkce (což se ale prakticky pravděpodobně nebude příliš často používat). Podívejme se nyní na konkrétní způsob expanze u tabulky s 2×2 podmínkami:

(macroexpand-1
  '(cond-table
     :|                 (even? x)   (odd? x)
     :|      (zero? x)  :a           :b        
     :|  (not-zero? x)  :c           :d))

Výsledek expanze vypadá následovně – je jasně patrné spojení jednotlivých podmínek i to, že jsou vyčerpány všechny možné kombinace podmínek:

(cond
 (and (zero? x) (even? x))     :a
 (and (zero? x) (odd? x))      :b
 (and (not-zero? x) (even? x)) :c
 (and (not-zero? x) (odd? x))  :d)

Pochopitelně si expandované makro můžeme zobrazit i pro rozsáhlejší tabulku, zde konkrétně s 3×3 podmínkami, tedy s celkem devíti možnými kombinacemi:

(macroexpand-1
  '(cond-table
     :|                  (= oper :login)   (= oper :logout)  (= oper :index-page)
     :|   (= status 200) :index-page       :logout-page      :index-page
     :|   (= status 401) :not-authorized   :invalid-state    :invalid-state
     :|   (= status 404) :not-found        :not-found        :not-found))

Expandovaný kód vypadá následovně:

(cond
 (and (= status 200) (= oper :login))       :index-page
 (and (= status 200) (= oper :logout))      :logout-page
 (and (= status 200) (= oper :index-page))  :index-page
 (and (= status 401) (= oper :login))       :not-authorized
 (and (= status 401) (= oper :logout))      :invalid-state
 (and (= status 401) (= oper :index-page))  :invalid-state
 (and (= status 404) (= oper :login))       :not-found
 (and (= status 404) (= oper :logout))      :not-found
 (and (= status 404) (= oper :index-page))  :not-found)
Poznámka: oba výsledky byly ručně upraveny pro lepší čitelnost.

16. A na konci je speciální forma if

Po úplné expanzi maker cond-table a cond musíme dojít k výrazu, v němž se bude vyskytovat speciální forma if, protože se jedná o jedinou podporovanou nízkoúrovňovou rozhodovací konstrukci programovacího jazyka Clojure (na rozdíl od Scheme, v němž jsou k dispozici i speciální formy and, or, when, unless a dokonce i cond – což jsou v Clojure „pouze“ makra). Je to ostatně patrné i při plné expanzi makra cond-table s 2×2 podmínkami:

(macroexpand-all
  '(cond-table
     :|                 (even? x)   (odd? x)
     :|      (zero? x)  :a           :b        
     :|  (not-zero? x)  :c           :d))

V expandovaném kódu nalezneme skutečně if, několik predikátů a interní symbol let*:

(if
  (let* [and__5514__auto__ (zero? x)]
        (if and__5514__auto__ (even? x) and__5514__auto__))
  :a
  (if
    (let* [and__5514__auto__ (zero? x)]
          (if and__5514__auto__ (odd? x) and__5514__auto__))
    :b
    (if
      (let* [and__5514__auto__ (not-zero? x)]
            (if and__5514__auto__ (even? x) and__5514__auto__))
      :c
      (if
        (let* [and__5514__auto__ (not-zero? x)]
              (if and__5514__auto__ (odd? x) and__5514__auto__))
        :d
        nil))))

Totéž platí pro rozhodovací tabulku s 3×3 podmínkami:

(macroexpand-all
  '(cond-table
     :|                  (= oper :login)   (= oper :logout)  (= oper :index-page)
     :|   (= status 200) :index-page       :logout-page      :index-page
     :|   (= status 401) :not-authorized   :invalid-state    :invalid-state
     :|   (= status 404) :not-found        :not-found        :not-found))

S expanzí na kód:

(if
 (let*
  [and__5514__auto__ (= status 200)]
  (if and__5514__auto__ (= oper :login) and__5514__auto__))
 :index-page
 (if
  (let*
   [and__5514__auto__ (= status 200)]
   (if and__5514__auto__ (= oper :logout) and__5514__auto__))
  :logout-page
  (if
   (let*
    [and__5514__auto__ (= status 200)]
    (if and__5514__auto__ (= oper :index-page) and__5514__auto__))
   :index-page
   (if
    (let*
     [and__5514__auto__ (= status 401)]
     (if and__5514__auto__ (= oper :login) and__5514__auto__))
    :not-authorized
    (if
     (let*
      [and__5514__auto__ (= status 401)]
      (if and__5514__auto__ (= oper :logout) and__5514__auto__))
     :invalid-state
     (if
      (let*
       [and__5514__auto__ (= status 401)]
       (if and__5514__auto__ (= oper :index-page) and__5514__auto__))
      :invalid-state
      (if
       (let*
        [and__5514__auto__ (= status 404)]
        (if and__5514__auto__ (= oper :login) and__5514__auto__))
       :not-found
       (if
        (let*
         [and__5514__auto__ (= status 404)]
         (if and__5514__auto__ (= oper :logout) and__5514__auto__))
        :not-found
        (if
         (let*
          [and__5514__auto__ (= status 404)]
          (if and__5514__auto__ (= oper :index-page) and__5514__auto__))
         :not-found
         nil)))))))))
Poznámka: i z tohoto kódu je jasně patrné, jak mohou být rozhodovací tabulky přehledné a krátké v porovnání s běžným strukturovaným kódem.

17. Příloha: zdrojové kódy výše popsaných maker

V této příloze jsou vypsány podoby všech výše popsaných maker ze standardní knihovny programovacího jazyka Clojure. Tyto kódy jsou velmi dobrým studijním materiálem pro tvorbu vlastních maker:

Root obecny

(source if-not)
 
(defmacro if-not
  "Evaluates test. If logical false, evaluates and returns then expr,
  otherwise else expr, if supplied, else nil."
  {:added "1.0"}
  ([test then] `(if-not ~test ~then nil))
  ([test then else]
   `(if (not ~test) ~then ~else)))
(source if-let)
 
(defmacro if-let
  "bindings => binding-form test
 
  If test is true, evaluates then with binding-form bound to the value of
  test, if not, yields else"
  {:added "1.0"}
  ([bindings then]
   `(if-let ~bindings ~then nil))
  ([bindings then else & oldform]
   (assert-args
     (vector? bindings) "a vector for its binding"
     (nil? oldform) "1 or 2 forms after binding vector"
     (= 2 (count bindings)) "exactly 2 forms in binding vector")
   (let [form (bindings 0) tst (bindings 1)]
     `(let [temp# ~tst]
        (if temp#
          (let [~form temp#]
            ~then)
          ~else)))))
(source if-some)
 
(defmacro if-some
  "bindings => binding-form test
 
   If test is not nil, evaluates then with binding-form bound to the
   value of test, if not, yields else"
  {:added "1.6"}
  ([bindings then]
   `(if-some ~bindings ~then nil))
  ([bindings then else & oldform]
   (assert-args
     (vector? bindings) "a vector for its binding"
     (nil? oldform) "1 or 2 forms after binding vector"
     (= 2 (count bindings)) "exactly 2 forms in binding vector")
   (let [form (bindings 0) tst (bindings 1)]
     `(let [temp# ~tst]
        (if (nil? temp#)
          ~else
          (let [~form temp#]
            ~then))))))
(source and)
 
(defmacro and
  "Evaluates exprs one at a time, from left to right. If a form
  returns logical false (nil or false), and returns that value and
  doesn't evaluate any of the other expressions, otherwise it returns
  the value of the last expr. (and) returns true."
  {:added "1.0"}
  ([] true)
  ([x] x)
  ([x & next]
   `(let [and# ~x]
      (if and# (and ~@next) and#))))
(source or)
 
(defmacro or
  "Evaluates exprs one at a time, from left to right. If a form
  returns a logical true value, or returns that value and doesn't
  evaluate any of the other expressions, otherwise it returns the
  value of the last expression. (or) returns nil."
  {:added "1.0"}
  ([] nil)
  ([x] x)
  ([x & next]
      `(let [or# ~x]
         (if or# or# (or ~@next)))))
(source when)
 
(defmacro when
  "Evaluates test. If logical true, evaluates body in an implicit do."
  {:added "1.0"}
  [test & body]
  (list 'if test (cons 'do body)))
(source when-not)
 
(defmacro when-not
  "Evaluates test. If logical false, evaluates body in an implicit do."
  {:added "1.0"}
  [test & body]
    (list 'if test nil (cons 'do body)))
(source when-let)
 
(defmacro when-let
  "bindings => binding-form test
 
  When test is true, evaluates body with binding-form bound to the value of test"
  {:added "1.0"}
  [bindings & body]
  (assert-args
     (vector? bindings) "a vector for its binding"
     (= 2 (count bindings)) "exactly 2 forms in binding vector")
   (let [form (bindings 0) tst (bindings 1)]
    `(let [temp# ~tst]
       (when temp#
         (let [~form temp#]
           ~@body)))))
(source when-some)
 
(defmacro when-some
  "bindings => binding-form test
 
   When test is not nil, evaluates body with binding-form bound to the
   value of test"
  {:added "1.6"}
  [bindings & body]
  (assert-args
     (vector? bindings) "a vector for its binding"
     (= 2 (count bindings)) "exactly 2 forms in binding vector")
   (let [form (bindings 0) tst (bindings 1)]
    `(let [temp# ~tst]
       (if (nil? temp#)
         nil
         (let [~form temp#]
           ~@body)))))
(source when-first)
 
(defmacro when-first
  "bindings => x xs
 
  Roughly the same as (when (seq xs) (let [x (first xs)] body)) but xs is evaluated only once"
  {:added "1.0"}
  [bindings & body]
  (assert-args
     (vector? bindings) "a vector for its binding"
     (= 2 (count bindings)) "exactly 2 forms in binding vector")
  (let [[x xs] bindings]
    `(when-let [xs# (seq ~xs)]
       (let [~x (first xs#)]
           ~@body))))
(source cond)
 
(defmacro cond
  "Takes a set of test/expr pairs. It evaluates each test one at a
  time.  If a test returns logical true, cond evaluates and returns
  the value of the corresponding expr and doesn't evaluate any of the
  other tests or exprs. (cond) returns nil."
  {:added "1.0"}
  [& clauses]
    (when clauses
      (list 'if (first clauses)
            (if (next clauses)
                (second clauses)
                (throw (IllegalArgumentException.
                         "cond requires an even number of forms")))
            (cons 'clojure.core/cond (next (next clauses))))))
(source condp)
 
(defmacro condp
  "Takes a binary predicate, an expression, and a set of clauses.
  Each clause can take the form of either:
 
  Each clause can take the form of either:
 
  test-expr result-expr
 
  test-expr :>> result-fn
 
  Note :>> is an ordinary keyword.
 
  For each clause, (pred test-expr expr) is evaluated. If it returns
  logical true, the clause is a match. If a binary clause matches, the
  result-expr is returned, if a ternary clause matches, its result-fn,
  which must be a unary function, is called with the result of the
  predicate as its argument, the result of that call being the return
  value of condp. A single default expression can follow the clauses,
  and its value will be returned if no clause matches. If no default
  expression is provided and no clause matches, an
  IllegalArgumentException is thrown."
 
  {:added "1.0"}
 
  [pred expr & clauses]
  (let [gpred (gensym "pred__")
        gexpr (gensym "expr__")
        emit (fn emit [pred expr args]
               (let [[[a b c :as clause] more]
                       (split-at (if (= :>> (second args)) 3 2) args)
                       n (count clause)]
                 (cond
                  (= 0 n) `(throw (IllegalArgumentException. (str "No matching clause: " ~expr)))
                  (= 1 n) a
                  (= 2 n) `(if (~pred ~a ~expr)
                             ~b
                             ~(emit pred expr more))
                  :else `(if-let [p# (~pred ~a ~expr)]
                           (~c p#)
                           ~(emit pred expr more)))))]
    `(let [~gpred ~pred
           ~gexpr ~expr]
       ~(emit gpred gexpr clauses))))
(source conde)
 
(defmacro conde
  "Logical disjunction of the clauses. The first goal in
  a clause is considered the head of that clause. Interleaves the
  execution of the clauses."
  [& clauses]
  (let [a (gensym "a")]
    `(fn [~a]
       (-inc
        (mplus* ~@(bind-conde-clauses a clauses))))))
(source condu)
(defmacro condu
 
  "Committed choice. Once the head (first goal) of a clause
  has succeeded, remaining goals of the clause will only
  be run once. Non-relational."
  [& clauses]
  (let [a (gensym "a")]
    `(fn [~a]
       (ifu* ~@(map (cond-clauses a) clauses)))))
(source conda)
(defmacro conda
 
  "Soft cut. Once the head of a clause has succeeded
  all other clauses will be ignored. Non-relational."
  [& clauses]
  (let [a (gensym "a")]
    `(fn [~a]
       (ifa* ~@(map (cond-clauses a) clauses)))))
(source cond->)
(defmacro cond->
 
  "Takes an expression and a set of test/form pairs. Threads expr (via ->)
  through each form for which the corresponding test
  expression is true. Note that, unlike cond branching, cond-> threading does
  not short circuit after the first true test expression."
  {:added "1.5"}
  [expr & clauses]
  (assert (even? (count clauses)))
  (let [g (gensym)
        steps (map (fn [[test step]] `(if ~test (-> ~g ~step) ~g))
                   (partition 2 clauses))]
    `(let [~g ~expr
           ~@(interleave (repeat g) (butlast steps))]
       ~(if (empty? steps)
          g
          (last steps)))))
(source cond->>)
(defmacro cond->>
 
  "Takes an expression and a set of test/form pairs. Threads expr (via ->>)
  through each form for which the corresponding test expression
  is true.  Note that, unlike cond branching, cond->> threading does not short circuit
  after the first true test expression."
  {:added "1.5"}
  [expr & clauses]
  (assert (even? (count clauses)))
  (let [g (gensym)
        steps (map (fn [[test step]] `(if ~test (->> ~g ~step) ~g))
                   (partition 2 clauses))]
    `(let [~g ~expr
           ~@(interleave (repeat g) (butlast steps))]
       ~(if (empty? steps)
          g
          (last steps)))))
(source case)
 
(defmacro case
  "Takes an expression, and a set of clauses.
 
  Each clause can take the form of either:
 
  test-constant result-expr
 
  (test-constant1 ... test-constantN)  result-expr
 
  The test-constants are not evaluated. They must be compile-time
  literals, and need not be quoted.  If the expression is equal to a
  test-constant, the corresponding result-expr is returned. A single
  default expression can follow the clauses, and its value will be
  returned if no clause matches. If no default expression is provided
  and no clause matches, an IllegalArgumentException is thrown.
 
  Unlike cond and condp, case does a constant-time dispatch, the
  clauses are not considered sequentially.  All manner of constant
  expressions are acceptable in case, including numbers, strings,
  symbols, keywords, and (Clojure) composites thereof. Note that since
  lists are used to group multiple constants that map to the same
  expression, a vector can be used to match a list if needed. The
  test-constants need not be all of the same type."
  {:added "1.2"}
 
  [e & clauses]
  (let [ge (with-meta (gensym) {:tag Object})
        default (if (odd? (count clauses))
                  (last clauses)
                  `(throw (IllegalArgumentException. (str "No matching clause: " ~ge))))]
    (if (> 2 (count clauses))
      `(let [~ge ~e] ~default)
      (let [pairs (partition 2 clauses)
            assoc-test (fn assoc-test [m test expr]
                         (if (contains? m test)
                           (throw (IllegalArgumentException. (str "Duplicate case test constant: " test)))
                           (assoc m test expr)))
            pairs (reduce1
                       (fn [m [test expr]]
                         (if (seq? test)
                           (reduce1 #(assoc-test %1 %2 expr) m test)
                           (assoc-test m test expr)))
                       {} pairs)
            tests (keys pairs)
            thens (vals pairs)
            mode (cond
                   (every? #(and (integer? %) (<= Integer/MIN_VALUE % Integer/MAX_VALUE)) tests)
                   :ints
                   (every? keyword? tests)
                   :identity
                   :else :hashes)]
        (condp = mode
          :ints
          (let [[shift mask imap switch-type] (prep-ints tests thens)]
            `(let [~ge ~e] (case* ~ge ~shift ~mask ~default ~imap ~switch-type :int)))
          :hashes
          (let [[shift mask imap switch-type skip-check] (prep-hashes ge default tests thens)]
            `(let [~ge ~e] (case* ~ge ~shift ~mask ~default ~imap ~switch-type :hash-equiv ~skip-check)))
          :identity
          (let [[shift mask imap switch-type skip-check] (prep-hashes ge default tests thens)]
            `(let [~ge ~e] (case* ~ge ~shift ~mask ~default ~imap ~switch-type :hash-identity ~skip-check))))))))

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

Ukázkové části kódu s rozhodovacími konstrukcemi, které byly popsány v rámci předchozích kapitol, byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/clojure-examples/. Konkrétně se jedná o projekt conditions (https://github.com/tisnik/clojure-examples/tree/master/conditions), jehož zdrojový kód (jediný soubor) je možné postupně kopírovat do interaktivního prostředí představovaného smyčkou REPL. Příklad je pochopitelně možné spustit i v jeho plné podobě příkazem:

lein run

To ovšem do určité míry postrádá výukový charakter.

19. Odkazy na předchozí části seriálu o programovacím jazyku Clojure

  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. Programovací jazyk Clojure – triky při práci s řetězci
    http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-retezci/
  17. Programovací jazyk Clojure – triky při práci s kolekcemi
    http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-kolekcemi/
  18. Programovací jazyk Clojure – práce s mapami a množinami
    http://www.root.cz/clanky/programovaci-jazyk-clojure-prace-s-mapami-a-mnozinami/
  19. Programovací jazyk Clojure – základy zpracování XML
    http://www.root.cz/clanky/programovaci-jazyk-clojure-zaklady-zpracovani-xml/
  20. Programovací jazyk Clojure – testování s využitím knihovny Expectations
    http://www.root.cz/clanky/programovaci-jazyk-clojure-testovani-s-vyuzitim-knihovny-expectations/
  21. 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/
  22. Enlive – výkonný šablonovací systém pro jazyk Clojure
    http://www.root.cz/clanky/enlive-vykonny-sablonovaci-system-pro-jazyk-clojure/
  23. 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/
  24. Novinky v Clojure verze 1.8.0
    http://www.root.cz/clanky/novinky-v-clojure-verze-1–8–0/
  25. Asynchronní programování v Clojure s využitím knihovny core.async
    http://www.root.cz/clanky/asynchronni-programovani-v-clojure-s-vyuzitim-knihovny-core-async/
  26. Asynchronní programování v Clojure s využitím knihovny core.async (pokračování)
    http://www.root.cz/clanky/asynchronni-programovani-v-clojure-s-vyuzitim-knihovny-core-async-pokracovani/
  27. Asynchronní programování v Clojure s využitím knihovny core.async (dokončení)
    http://www.root.cz/clanky/asynchronni-programovani-v-clojure-s-vyuzitim-knihovny-core-async-dokonceni/
  28. Vytváříme IRC bota v programovacím jazyce Clojure
    http://www.root.cz/clanky/vytvarime-irc-bota-v-programovacim-jazyce-clojure/
  29. Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
    https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/
  30. Multimetody v Clojure aneb polymorfismus bez použití OOP
    https://www.root.cz/clanky/multimetody-v-clojure-aneb-polymorfismus-bez-pouziti-oop/
  31. Práce s externími Java archivy v programovacím jazyku Clojure
    https://www.root.cz/clanky/prace-s-externimi-java-archivy-v-programovacim-jazyku-clojure/
  32. Clojure 16: Složitější uživatelská makra
    http://www.root.cz/clanky/programovaci-jazyk-clojure-16-slozitejsi-uzivatelska-makra/
  33. Clojure 17: Využití standardních maker v praxi
    http://www.root.cz/clanky/programovaci-jazyk-clojure-17-vyuziti-standardnich-maker-v-praxi/
  34. Clojure 18: Základní techniky optimalizace aplikací
    http://www.root.cz/clanky/programovaci-jazyk-clojure-18-zakladni-techniky-optimalizace-aplikaci/
  35. Clojure 19: Vývojová prostředí pro Clojure
    http://www.root.cz/clanky/programovaci-jazyk-clojure-19-vyvojova-prostredi-pro-clojure/
  36. 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/
  37. Clojure 21: ClojureScript aneb překlad Clojure do JS
    http://www.root.cz/clanky/programovaci-jazyk-clojure-21-clojurescript-aneb-preklad-clojure-do-javascriptu/
  38. Leiningen: nástroj pro správu projektů napsaných v Clojure
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure/
  39. 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/
  40. 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/
  41. 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/
  42. 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/
  43. 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/
  44. Programovací jazyk Clojure a databáze (1.část)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-databaze-1-cast/
  45. Pluginy pro Leiningen
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-pluginy-pro-leiningen/
  46. 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/
  47. 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/
  48. 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/
  49. 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/
  50. Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure
    http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure/
  51. Seesaw: knihovna pro snadnou tvorbu GUI v azyce Clojure (2)
    http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure-2/
  52. 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/
  53. Programovací jazyk Clojure a práce s Gitem
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem/
  54. Programovací jazyk Clojure a práce s Gitem (2)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem-2/
  55. 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/
  56. Pixie: lehký skriptovací jazyk s „kouzelnými“ schopnostmi
    https://www.root.cz/clanky/pixie-lehky-skriptovaci-jazyk-s-kouzelnymi-schopnostmi/
  57. Programovací jazyk Pixie: funkce ze základní knihovny a použití FFI
    https://www.root.cz/clanky/pro­gramovaci-jazyk-pixie-funkce-ze-zakladni-knihovny-a-pouziti-ffi/
  58. Novinky v Clojure verze 1.9.0
    https://www.root.cz/clanky/novinky-v-clojure-verze-1–9–0/
  59. Validace dat s využitím knihovny spec v Clojure 1.9.0
    https://www.root.cz/clanky/validace-dat-s-vyuzitim-knihovny-spec-v-clojure-1–9–0/
  60. Použití jazyka Gherkin při tvorbě testovacích scénářů pro aplikace psané v Clojure
    https://www.root.cz/clanky/pouziti-jazyka-gherkin-pri-tvorbe-testovacich-scenaru-pro-aplikace-psane-v-nbsp-clojure/
  61. Použití jazyka Gherkin při tvorbě testovacích scénářů pro aplikace psané v Clojure (2)
    https://www.root.cz/clanky/pouziti-jazyka-gherkin-pri-tvorbe-testovacich-scenaru-pro-aplikace-psane-v-nbsp-clojure-2/
  62. Incanter: prostředí pro statistické výpočty s grafickým výstupem založené na Clojure
    https://www.root.cz/clanky/incanter-prostredi-pro-statisticke-vypocty-s-grafickym-vystupem-zalozene-na-clojure/
  63. Incanter: operace s maticemi
    https://www.root.cz/clanky/incanter-operace-s-maticemi/
  64. Interpret programovacího jazyka Clojure integrovaný do Jupyter Notebooku
    https://www.root.cz/clanky/interpret-programovaciho-jazyka-clojure-integrovany-do-jupyter-notebooku/
  65. Babashka: interpret Clojure určený pro rychlé spouštění utilit z příkazového řádku
    https://www.root.cz/clanky/babashka-interpret-clojure-urceny-pro-rychle-spousteni-utilit-z-prikazoveho-radku/
  66. Pokročilý streaming založený na Apache Kafce, jazyku Clojure a knihovně Jackdaw
    https://www.root.cz/clanky/pokrocily-streaming-zalozeny-na-apache-kafce-jazyku-clojure-a-knihovne-jackdaw/
  67. Pokročilý streaming založený na Apache Kafce, jazyku Clojure a knihovně Jackdaw (2. část)
    https://www.root.cz/clanky/pokrocily-streaming-zalozeny-na-apache-kafce-jazyku-clojure-a-knihovne-jackdaw-2-cast/
  68. Pokročilý streaming založený na projektu Apache Kafka, jazyku Clojure a knihovně Jackdaw (streamy a kolony)
    https://www.root.cz/clanky/pokrocily-streaming-zalozeny-na-projektu-apache-kafka-jazyku-clojure-a-knihovne-jackdaw-streamy-a-kolony/
  69. Řídicí struktury využitelné v programovacím jazyku Clojure
    https://www.root.cz/clanky/ridici-struktury-vyuzitelne-v-programovacim-jazyku-clojure/

20. Odkazy na Internetu

  1. The Clojure Style Guide
    https://github.com/bbatsov/clojure-style-guide
  2. Rozhodovací tabulky a stromy
    https://wikisofia.cz/wiki/Roz­hodovac%C3%AD_tabulky_a_stro­my
  3. Special (Conditions)
    https://github.com/clojureman/special
  4. better-cond
    https://cljdoc.org/d/better-cond/better-cond/2.1.0/doc/readme
  5. A micro-library around the useful cond-let macro
    https://cljdoc.org/d/com.wal­martlabs/cond-let/1.0.0/doc/readme
  6. An adaption of the Racket cond macro for Clojure
    https://cljdoc.org/d/cond-plus/cond-plus/1.0.1/doc/readme
  7. Makro cond-table
    https://github.com/semperos/ran­kle/blob/master/src/com/sem­peros/rankle/util.clj
  8. cond v jazyku Racket
    https://docs.racket-lang.org/reference/if.html?q=con­d#%28form._%28%28lib._rac­ket%2Fprivate%2Fletstx-scheme..rkt%29._cond%29%29
  9. Learn Clojure – Flow Control
    https://clojure.org/guides/learn/flow
  10. clojure.core.logic
    https://clojuredocs.org/clo­jure.core.logic
  11. ETL Batch Processing With Kafka?
    https://medium.com/swlh/etl-batch-processing-with-kafka-7f66f843e20d
  12. ETL with Kafka
    https://blog.codecentric.de/en/2018/03/e­tl-kafka/
  13. Building ETL Pipelines with Clojure and Transducers
    https://www.grammarly.com/blog/en­gineering/building-etl-pipelines-with-clojure-and-transducers/
  14. pipeline (možné použít pro ETL)
    https://clojuredocs.org/clo­jure.core.async/pipeline
  15. On Track with Apache Kafka – Building a Streaming ETL Solution with Rail Data
    https://www.confluent.io/blog/build-streaming-etl-solutions-with-kafka-and-rail-data/
  16. Kafka – Understanding Offset Commits
    https://www.logicbig.com/tu­torials/misc/kafka/commit­ting-offsets.html
  17. fundingcircle/jackdaw (na Clojars)
    https://clojars.org/fundin­gcircle/jackdaw/versions/0­.7.6
  18. Dokumentace ke knihovně jackdaw
    https://cljdoc.org/d/fundin­gcircle/jackdaw/0.7.6/doc/re­adme
  19. Jackdaw AdminClient API
    https://cljdoc.org/d/fundin­gcircle/jackdaw/0.7.6/doc/jac­kdaw-adminclient-api
  20. Jackdaw Client API
    https://cljdoc.org/d/fundin­gcircle/jackdaw/0.7.6/doc/jac­kdaw-client-api
  21. Kafka.clj
    https://github.com/helins-io/kafka.clj
  22. Použití nástroje Apache Kafka v aplikacích založených na mikroslužbách
    https://www.root.cz/clanky/pouziti-nastroje-apache-kafka-v-aplikacich-zalozenych-na-mikrosluzbach/
  23. Apache Kafka: distribuovaná streamovací platforma
    https://www.root.cz/clanky/apache-kafka-distribuovana-streamovaci-platforma/
  24. Real-Time Payments with Clojure and Apache Kafka (podcast)
    https://www.evidentsystem­s.com/news/confluent-podcast-about-apache-kafka/
  25. Kafka and Clojure – Immutable event streams
    https://practicalli.github.io/kafka-and-clojure/
  26. Kafka Streams, the Clojure way
    https://blog.davemartin.me/posts/kafka-streams-the-clojure-way/
  27. dvlopt.kafka na GitHubu
    https://github.com/helins-io/kafka.clj
  28. kafka-streams-the-clojure-way na GitHubu
    https://github.com/DaveWM/kafka-streams-the-clojure-way
  29. babashka: A Clojure babushka for the grey areas of Bash
    https://github.com/borkdude/babashka
  30. Babashka and the Small Clojure Interpreter @ ClojureD 2020 (slajdy)
    https://speakerdeck.com/bor­kdude/babashka-and-the-small-clojure-interpreter-at-clojured-2020
  31. Babashka: ukázky použití
    https://github.com/borkdu­de/babashka/blob/master/doc/e­xamples.md
  32. clojureD 2020: „Babashka and Small Clojure Interpreter: Clojure in new contexts“ by Michiel Borkent
    https://www.youtube.com/watch?v=Nw8aN-nrdEk&t=5s
  33. Meetup #124 Babashka, implementing an nREPL server & game engines with Clojure
    https://www.youtube.com/wat­ch?v=0YmZYnwyHHc
  34. The Last Programming Language (shrnutí vývoje programovacích jazyků)
    https://www.youtube.com/watch?v=P2yr-3F6PQo
  35. Shebang (Unix): Wikipedia EN
    https://en.wikipedia.org/wi­ki/Shebang_(Unix)
  36. Shebang (Unix): Wikipedia CZ
    https://cs.wikipedia.org/wi­ki/Shebang_(Unix)
  37. How to create Clojure notebooks in Jupyter
    https://s01blog.wordpress­.com/2017/12/10/how-to-create-clojure-notebooks-in-jupyter/
  38. Dokumentace k nástroji Conda
    https://docs.conda.io/en/latest/
  39. Notebook interface
    https://en.wikipedia.org/wi­ki/Notebook_interface
  40. Jypyter: open source, interactive data science and scientific computing across over 40 programming languages
    https://jupyter.org/
  41. Calysto Scheme
    https://github.com/Calysto/ca­lysto_scheme
  42. scheme.py (základ projektu Calysto Scheme)
    https://github.com/Calysto/ca­lysto_scheme/blob/master/ca­lysto_scheme/scheme.py
  43. Humane test output for clojure.test
    https://github.com/pjstadig/humane-test-output
  44. iota
    https://github.com/juxt/iota
  45. 5 Differences between clojure.spec and Schema
    https://lispcast.com/clojure.spec-vs-schema/
  46. Schema: Clojure(Script) library for declarative data description and validation
    https://github.com/plumatic/schema
  47. Zip archiv s Clojure 1.9.0
    http://repo1.maven.org/ma­ven2/org/clojure/clojure/1­.9.0/clojure-1.9.0.zip
  48. Clojure 1.9 is now available
    https://clojure.org/news/2017/12/08/clo­jure19
  49. Deps and CLI Guide
    https://clojure.org/guides/dep­s_and_cli
  50. Changes to Clojure in Version 1.9
    https://github.com/clojure/clo­jure/blob/master/changes.md
  51. clojure.spec – Rationale and Overview
    https://clojure.org/about/spec
  52. Zip archiv s Clojure 1.8.0
    http://repo1.maven.org/ma­ven2/org/clojure/clojure/1­.8.0/clojure-1.8.0.zip
  53. Clojure 1.8 is now available
    http://clojure.org/news/2016/01/19/clo­jure18
  54. Socket Server REPL
    http://dev.clojure.org/dis­play/design/Socket+Server+REPL
  55. CLJ-1671: Clojure socket server
    http://dev.clojure.org/jira/browse/CLJ-1671
  56. CLJ-1449: Add clojure.string functions for portability to ClojureScript
    http://dev.clojure.org/jira/browse/CLJ-1449
  57. Launching a Socket Server
    http://clojure.org/referen­ce/repl_and_main#_launchin­g_a_socket_server
  58. API for clojure.string
    http://clojure.github.io/clo­jure/branch-master/clojure.string-api.html
  59. Clojars:
    https://clojars.org/
  60. Seznam knihoven na Clojars:
    https://clojars.org/projects
  61. Clojure Cookbook: Templating HTML with Enlive
    https://github.com/clojure-cookbook/clojure-cookbook/blob/master/07_webapps/7–11_enlive.asciidoc
  62. An Introduction to Enlive
    https://github.com/swannodette/enlive-tutorial/
  63. Enlive na GitHubu
    https://github.com/cgrand/enlive
  64. Expectations: příklady atd.
    http://jayfields.com/expectations/
  65. Expectations na GitHubu
    https://github.com/jaycfi­elds/expectations
  66. Lein-expectations na GitHubu
    https://github.com/gar3thjon3s/lein-expectations
  67. Testing Clojure With Expectations
    https://semaphoreci.com/blog/2014/09/23/tes­ting-clojure-with-expectations.html
  68. 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/
  69. Testing: One assertion per test
    http://blog.jayfields.com/2007/06/tes­ting-one-assertion-per-test.html
  70. Rewriting Your Test Suite in Clojure in 24 hours
    http://blog.circleci.com/rewriting-your-test-suite-in-clojure-in-24-hours/
  71. Clojure doc: zipper
    http://clojuredocs.org/clo­jure.zip/zipper
  72. Clojure doc: parse
    http://clojuredocs.org/clo­jure.xml/parse
  73. Clojure doc: xml-zip
    http://clojuredocs.org/clojure.zip/xml-zip
  74. Clojure doc: xml-seq
    http://clojuredocs.org/clo­jure.core/xml-seq
  75. Parsing XML in Clojure
    https://github.com/clojuredocs/guides
  76. Clojure Zipper Over Nested Vector
    https://vitalyper.wordpres­s.com/2010/11/23/clojure-zipper-over-nested-vector/
  77. Understanding Clojure's PersistentVector implementation
    http://blog.higher-order.net/2009/02/01/understanding-clojures-persistentvector-implementation
  78. Understanding Clojure's PersistentHashMap (deftwice…)
    http://blog.higher-order.net/2009/09/08/understanding-clojures-persistenthashmap-deftwice.html
  79. Assoc and Clojure's PersistentHashMap: part ii
    http://blog.higher-order.net/2010/08/16/assoc-and-clojures-persistenthashmap-part-ii.html
  80. Ideal Hashtrees (paper)
    http://lampwww.epfl.ch/pa­pers/idealhashtrees.pdf
  81. 4Clojure
    http://www.4clojure.com/
  82. ClojureDoc (rozcestník s dokumentací jazyka Clojure)
    http://clojuredocs.org/
  83. Clojure (na Wikipedia EN)
    http://en.wikipedia.org/wiki/Clojure
  84. Clojure (na Wikipedia CS)
    http://cs.wikipedia.org/wiki/Clojure
  85. SICP (The Structure and Interpretation of Computer Programs)
    http://mitpress.mit.edu/sicp/
  86. Pure function
    http://en.wikipedia.org/wi­ki/Pure_function
  87. Funkcionální programování
    http://cs.wikipedia.org/wi­ki/Funkcionální_programová­ní
  88. Čistě funkcionální (datové struktury, jazyky, programování)
    http://cs.wikipedia.org/wi­ki/Čistě_funkcionální
  89. 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
  90. Clojure Macro Tutorial (Part II: The Compiler Strikes Back)
    http://www.learningclojure­.com/2010/09/clojure-macro-tutorial-part-ii-compiler.html
  91. Clojure Macro Tutorial (Part III: Syntax Quote)
    http://www.learningclojure­.com/2010/09/clojure-macro-tutorial-part-ii-syntax.html
  92. Tech behind Tech: Clojure Macros Simplified
    http://techbehindtech.com/2010/09/28/clo­jure-macros-simplified/
  93. Fatvat – Exploring functional programming: Clojure Macros
    http://www.fatvat.co.uk/2009/02/clo­jure-macros.html

Autor článku

Pavel Tišnovský vystudoval VUT FIT a v současné době pracuje ve společnosti Red Hat, kde vyvíjí nástroje pro OpenShift.io.