Hlavní navigace

Řídicí struktury využitelné v programovacím jazyku Clojure

12. 1. 2021
Doba čtení: 39 minut

Sdílet

Článek se věnuje popisu řídicích struktur v jazyku Clojure. Zatímco ve většině běžných jazyků existuje jen omezené množství takových struktur (podmínky, rozvětvení, cykly), v Clojure najdeme makrosystém.

Obsah

1. Řídicí struktury využitelné v programovacím jazyku Clojure

2. Speciální forma if

3. Použití speciální formy if s oběma větvemi a problematika více volaných funkcí ve větvích

4. Makra when a when-not

5. Kombinace speciálních forem if a let

6. Makra if-some a when-some

7. Použití maker and a or pro řízení toku programu

8. Rozhodovací konstrukce založená na makru cond

9. Plná expanze makra cond

10. Sekvence testů prováděná makrem condp

11. Makro condp a složitější výrazy

12. Aplikace funkce namísto vyhodnocení výrazu ve větvi

13. Ukázka expanze makra condp

14. Obsah navazujícího článku

15. Dodatek č.1: makra a speciální formy

16. Dodatek č.2: expanze maker

17. Dodatek č.3: speciální formy jazyka Clojure, porovnání se Scheme

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

Programovací jazyk Clojure nabízí programátorům několik základních řídicích struktur, které jsou představovány takzvanými speciálními formami (ty se vyhodnocují jinak, než běžné funkce). Jedná se především o formy if, loop a recur. Ovšem díky makrosystému, tedy možnosti tvorby uživatelských maker, můžeme v aplikacích psaných v Clojure použít mnohem více řídicích struktur, které jsou interně postaveny právě na výše zmíněné trojici (celé Clojure je tedy stavebnicí s malým množstvím základních prvků). V následující tabulce jsou vypsány některé z nich (prozatím se soustředíme na rozhodování a rozvětvení):

# Konstrukce Kapitola Typ Stručný popis
1 if 2 speciální forma základní rozhodovací konstrukce, základ pro další makra
2 if+do 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ů
3 if-let 5 makro kombinace speciálních forem if a let
4 if-some 6 makro kombinace speciálních forem if (test na nil) a let
         
5 and 7 makro postupné vyhodnocování předaných výrazů až do chvíle, kdy se vrátí nil či false
6 or 7 makro postupné vyhodnocování předaných výrazů až do chvíle, kdy se vrátí true
         
7 when 4 makro vhodná náhrada za if s jedinou větví s více výrazy
8 when-not 4 makro vhodná náhrada za if-not s jedinou větví s více výrazy
9 when-let 5 makro kombinace speciálních forem when a let
10 when-some 6 makro kombinace speciálních forem when (test na nil) a let
11 when-first   makro použito při testu prvního prvku sekvence s následným zpracováním sekvence
         
12 cond 8 makro postupné testování podmínek, pokud je podmínka splněna, vrátí se hodnota příslušného výrazu
13 cond + :else 8 makro typické použití makra cond s větví :else nebo :default
14 condp 10 makro postupné dosazování testované hodnoty do zadaného výrazu, obdoba switch-case
15 conde   makro makro z knihovny clojure.core.logic, bude popsáno příště
16 condu   makro makro z knihovny clojure.core.logic, bude popsáno příště
17 conda   makro makro z knihovny clojure.core.logic, bude popsáno příště
       
18 cond->   makro odvozeno od cond, bude popsáno příště
19 cond->>   makro odvozeno od cond, bude popsáno příště
         
20 case   makro makro, které bude popsáno příště
         
21 cond-table   makro nová nestandardní konstrukce se zdrojovým kódem zmíněným na konci navazujícího článku
Poznámka: při studiu výše zmíněných forem se vývojáři mohou přiučit i to, jak vlastně interně pracuje makrosystém programovacího jazyka Clojure. Tímto tématem jsme se částečně zabývali zde.

2. Speciální forma if

Všechna makra popsaná v dalším textu jsou založena na využití speciální formy if, jejíž různé varianty si popíšeme v této kapitole i v kapitolách navazujících.

Nejprve vytvoříme několik jmen navázaných na hodnoty (což je v programovacím jazyku Clojure obdoba konstant). Tyto hodnoty budou použity v podmínkách uvedených později:

(def x 10)
(def y 20)
(def z 20)
Poznámka: v naprosté většině skutečných aplikací by se def tímto způsobem nepoužívalo, nebo alespoň ne frekventovaně, protože to vede k vytvoření obdoby hodnot dostupných „globálně“ v rámci celého jmenného prostoru.

Základní rozhodovací konstrukce je tvořena speciální formou if, která existuje ve dvou variantách – pouze s větví then a s oběma větvemi thenelse. Tuto speciální formu lze tedy zapsat ve zkrácené podobě:

(if test větev-then)

nebo v plné podobě:

(if test větev-then větev-else)

Bližší informace o této speciální formě získáme přímo z vestavěné nápovědy dostupné z interaktivní smyčky REPL programovacího jazyka Clojure. Připomeňme si, že nápovědu k jakémukoli symbolu je možné zobrazit makrem pojmenovaným doc. Pro speciální formu if se tedy zobrazí nápověda takto:

(doc if)
 
-------------------------
if
  (if test then else?)
Special Form
  Evaluates test. If not the singular values nil or false,
  evaluates and yields then, otherwise, evaluates and yields else. If
  else is not supplied it defaults to nil.
 
  Please see http://clojure.org/special_forms#if

Speciální formu if je možné použít ve formě rozvětvení, což je konstrukce známá například z programovacích jazyků C, Go, Pascalu, Pythonu atd. V tomto případě se (nyní již v Clojure) typicky setkáme s funkcemi nebo makry s vedlejším efektem, které jsou ve větvi then použity:

(if (< x y)
  (println "x < y"))
 
x < y
nil

Funkce println je v tomto případě zavolána pouze tehdy, pokud je podmínka platná. Předchozí podmínka platí, ovšem následující nikoli, proto nebude println zavolána a pouze se vrátí hodnota nil:

(if (< y x)
  (println "y < x"))
 
nil

V programovacím jazyku Clojure a současně i ve všech LISPovských programovacích se speciální forma if vyhodnocuje a následně se vrátí výsledek tohoto vyhodnocení. Pokud je podmínka splněna, je vrácena hodnota funkce/makra ve větvi then, v opačném případě je vrácena hodnota funkce/makra ve větvi else nebo nil tehdy, pokud větev else není vůbec zapsána:

(if (< x y)
  (+ x y))
 
30

Nebo ještě jednodušeji:

(if (< x y)
  :mensi)
 
:mensi

Následující konstrukce vrátí hodnotu nil, protože podmínka není splněna a současně není zapsána větev else:

(if (< y x)
  (+ x y))
 
nil

resp:

(if (> x y)
  :vetsi)
 
nil

Poznámka: podobným způsobem se konstrukce if používá například v programovacím jazyku Rustu, kde se taktéž jedná o výraz. V Rustu lze tedy psát:

fn if_expression(value:i32) {
  let value_type =
      if value < 0 {
          "zaporna"
      } else {
          if value == 0 {
              "nulova"
          } else {
              "kladna"
          }
      };
  println!("Hodnota {} je {}", value, value_type);
}
Poznámka: v předchozím příkladu je fakt, že se jedná o zápis výrazu ještě zvýrazněn tím, že není použit příkaz return.

3. Použití speciální formy if s oběma větvemi a problematika více volaných funkcí ve větvích

Vraťme se však k programovacímu jazyku Clojure. Již jsme si řekli, že je podporována i speciální forma if s oběma větvemi thenelse. Zápis je v tomto případě jednoduchý:

(if (< x y)
  (println "x < y")
  (println "x > y"))
 
x < y
nil
Poznámka: předchozí konstrukce vytiskla (přes vedlejší efekt) zprávu na terminál a za všech okolností vrátila hodnotu nil, nehledě na to, zda byla podmínka splněna či nesplněna.

Ještě lépe je konstrukce rozvětvení patrná na demonstračním příkladu, který vrátí hodnotu :mensi nebo :vetsi na základě vyhodnocení podmínky:

(if (< x y)
  :mensi
  :vetsi)

Konstrukce if je možné vnořovat:

(if (zero? x)
  :nulove
  (if (neg? x)
    :zaporne
    :kladne))

Praktičtější příklad – výpočet největšího společného dělitele:

(defn gcd
  [x y]
  (if (= x y)
    x
    (if (> x y)
      (gcd (- x y) y)
      (gcd x (- y x)))))
 
(println (gcd 64 24))
8
nil

V tomto případě je však lepší použít konstrukci cond popsanou níže.

Mnohdy se setkáme s požadavkem, že se v jedné větvi speciální formy if, popř. v obou větvích má použít více funkcí s vedlejším efektem (volání více funkcí bez vedlejšího efektu nemá význam, i když není zakázáno). Tento požadavek, který je v Algolských programovacích jazycích (C, Pascal, JavaScript, Go, …) vyřešen s využitím programových bloků (složené závorky), by bylo možné otrocky přepsat do jazyka Clojure pomocí speciální formy do:

(if (< x y)
  (do
    (println "x < y")
    :mensi)
  (do
    (println "x > y")
    :vetsi))

I ke speciální formě do (vedle if patří mezi základní konstrukce Clojure) samozřejmě existuje nápověda:

(doc do)
 

Nápověda:

-------------------------
do
  (do exprs*)
Special Form
  Evaluates the expressions in order and returns the value of
  the last. If no expressions are supplied, returns nil.
 
  Please see http://clojure.org/special_forms#do

4. Makra when a when-not

Ve skutečnosti je však výše zmíněná kombinace if + do velmi špatně čitelná, nehledě na to, že se kód začíná ztrácet ve velkém množství kulatých závorek. Existuje však i (pro mnoho účelů) lepší řešení. Pokud konstrukce if obsahuje pouze jedinou větev (předpokládejme nyní pro určité zjednodušení, že se jedná o větev then), lze namísto speciální formy if použít makro nazvané příznačně when:

(doc when)
 
;; -------------------------
;; clojure.core/when
;; ([test & body])
;; Macro
;;   Evaluates test. If logical true, evaluates body in an implicit do.

Následuje ukázka příkladu použití tohoto makra v situaci, kdy se má při splnění podmínky vykonat více funkcí (s vedlejším efektem) a navíc se má vrátit nějaká hodnota, typicky s využitím funkce bez vedlejšího efektu:

(when (< x y)
  (println "----------")
  (println "x < y")
  (println "----------")
  :mensi)
 
x < y
:mensi

Následuje nepatrně složitější příklad vracející výsledek funkce +:

(when (< x y)
  (println "----------")
  (println "x < y")
  (println "----------")
  (+ x y))
 
x < y
30

V případě, že podmínka splněna není, žádná funkce ani makro se nezavolá a makro when vrátí hodnotu nil:

(when (> x y)
  (println "x > y")
  :vetsi)
 
nil

Pokud je naopak nutné zavolat více funkcí při NEsplnění podmínky, použije se namísto makra when makro nazvané when-not:

(doc when-not)
 
;; -------------------------
;; clojure.core/when-not
;; ([test & body])
;; Macro
;;  Evaluates test. If logical false, evaluates body in an implicit do.

Opět si ve stručnosti ukažme příklad použití tohoto makra:

(when-not (> x y)
  (println "----------")
  (println "x < y")
  (println "----------")
  (+ x y))

Popř. pouze:

(when-not (< x y)
  (println "x > y")
  :vetsi)

Poznámka: pokud vám – ostatně podobně jako mnoha dalším programátorům – nevyhovuje název makra when-not, není nic jednoduššího, než si vytvořit vlastní makro typicky nazvané unless:

(defmacro unless [& args] `(when-not ~@args))
Poznámka: unless není náhodně zvolené jméno, protože ho najdeme v mnoha interpretrech a překladačích programovacího jazyka Scheme. Příkladem může být Racket: https://docs.racket-lang.org/reference/when_unless.html.

Vzhledem k tomu, že when je makro, můžeme se podívat na expanzi tohoto makra, tedy na programový kód, který je tímto makrem vygenerován:

(macroexpand-1
  '(when (< x y)
    (println "----------")
    (println "x lt; y")
    (println "----------")
    (+ x y)))

Z výsledku je patrné, že expanze je v tomto případě přímočaré rozepsání na základní speciální formy if a do:

(if
(< x y)
(do (println "----------") (println "x < y") (println "----------") (+ x y)))

Totéž, tedy výpis expandovaného makra, můžeme vyzkoušet i pro makro when-not:

(macroexpand-1
  '(when-not (< x y)
    (println "----------")
    (println "x < y")
    (println "----------")
    (+ x y)))

Nyní je výsledek expanze makra zajímavější, protože větev then je vygenerována, ovšem obsahuje pouze nil, zatímco větev else je umístěna do speciální formy do (což je logické, protože potřebujeme zavolat více funkcí):

(if
 (< x y)
 nil
 (do (println "----------") (println "x < y") (println "----------") (+ x y)))

Zajímavější je situace, která nastane při expanzi makra unless, které se expanduje na jiné makro, konkrétně na when-not:

(defmacro unless [& args] `(when-not ~@args))

Ostatně se sami podívejme, jak vypadá výsledek jedné expanze:

(macroexpand-1
  '(unless (< x y)
    (println "----------")
    (println "x < y")
    (println "----------")
    (+ x y)))

Výsledkem je pouhá náhrada makra unless za when-not:

(clojure.core/when-not
 (< x y)
 (println "----------")
 (println "x < y")
 (println "----------")
 (+ x y))

Úplnou expanzi si můžeme zobrazit pomocí macroexpand:

(macroexpand
  '(unless (< x y)
    (println "----------")
    (println "x < y")
    (println "----------")
    (+ x y)))

S tímto výsledkem:

(if
 (< x y)
 nil
 (do (println "----------") (println "x < y") (println "----------") (+ x y)))

5. Kombinace speciálních forem if a let

Prozatím jsme si ukázali zejména speciální formu if a způsoby nahrazení této formy v případě, že je zapotřebí vykonat jednu vícekrokovou větev. Náhrada spočívá v použití maker when a when-not. Existují však i další velmi často používané konstrukce, například kombinace speciální formy if s další speciální formou let nazvaná příznačně if-let:

(doc if-let)
 
-------------------------
clojure.core/if-let
([bindings then] [bindings then else & oldform])
Macro
  bindings => binding-form test

  If test is true, evaluates then with binding-form bound to the value of
  test, if not, yields else

Podívejme se nyní na způsob použití tohoto makra. Předpokládejme, že budeme chtít vytvořit skript, který se zeptá na jméno uživatele a pokud uživatel jméno skutečně zadá, vypíše se řetězec „Hello“, za nímž následuje zapsané jméno. Pokud ovšem uživatel pouze stiskne klávesu Enter, nic se neprovede. Takový skript by bylo možné zapsat následujícím způsobem:

(let [name (read-line)]
  (if (not-empty name)
    (println "Hello!" name)))

Výše uvedený kód je však zbytečně komplikovaný. Můžeme ho zkrátit na použití makra if-let a těla then s funkcí println (s vedlejším efektem):

(if-let [name (not-empty (read-line))]
  (println "Hello!" name))

Pro zajímavost se nyní podívejme na plnou expanzi předchozího skriptu, který je založen na makru if-let:

(macroexpand
  '(if-let [name (not-empty (read-line))]
    (println "Hello!" name)))

Z výsledku je patrné, že byla použita automaticky vytvořená lokální proměnná:

(let*
 [temp__5733__auto__ (not-empty (read-line))]
 (if
  temp__5733__auto__
  (clojure.core/let [name temp__5733__auto__] (println "Hello!" name))
  nil))
Poznámka: zdrojový kód makra zaručuje, že automaticky vytvořené lokální proměnné mají unikátní název.

Otestujme si, zda if-let vrací nějakou hodnotu:

(if-let [name (not-empty (read-line))]
  (str "Hello! " name))

Tento výraz vrátí buď řetězec „Hello! (zadaný řetězec)“ nebo hodnotu nil.

Úplná forma makra if-let s větví else vypadá následovně:

(if-let [name (not-empty (read-line))]
  (str "Hello! " name)
  :nothing)
Pozor: tento výraz obsahuje jednu past: pokud přistoupíte k lokálnímu jménu z větve else, vrátí se nikoli hodnota (ta není navázána), ale identifikátor ve stylu „Name= clojure.core$name@14df1ec3“:
(if-let [name (not-empty (read-line))]
  (str "Hello! " name)
  (str "Name= " name))

Úprava je v tomto případě snadná, i když ne na první pohled zřejmá: musíme použít unikátní jméno namísto existující funkce name:

(if-let [x (not-empty (read-line))]
  (str "Hello! " x)
  (str "Name= " x))

6. Makra if-some a when-some

Krátce se zmiňme o makrech if-some a when-some. Jedná se o obdobu již výše popsaných maker if-let a when-let, ovšem s jednou podstatnou výjimkou: makra if-some a when-some testují, zda je přiřazovaná hodnota rovna nil či nikoli zatímco makra if-let a when-let rozlišují mezi pravdivými a nepravdivými hodnotami. V Clojure jsou přitom prakticky všechny hodnoty považovány za pravdivé (a to včetně nuly či prázdného řetězce), jedinou výjimkou jsou hodnoty false a nil, které jsou považovány za nepravdu.

(doc if-some)
 
-------------------------
clojure.core/if-some
([bindings then] [bindings then else & oldform])
Macro
  bindings => binding-form test
 
   If test is not nil, evaluates then with binding-form bound to the
   value of test, if not, yields else
(doc when-some)
 
-------------------------
clojure.core/when-some
([bindings & body])
Macro
  bindings => binding-form test
 
   When test is not nil, evaluates body with binding-form bound to the
   value of test

Rozdíl mezi makry if-some/when-some a if-let/when-let si ukážeme na poněkud umělém příkladu (umělém z toho důvodu, že prakticky nikdy není nutné rozlišovat mezi false a nil, což jsou jediné hodnoty, v nichž se funkce maker od sebe odlišuje):

Zadefinujeme dva vektory a budeme zkoumat, zda jsou všechny jejich prvky kladné či nikoli:

(def values1 [-2 -1 0 1 2])
(def values2 [1 2 3])

Použití makra if-let pro vektor, v němž se nachází nula i záporné prvky:

(if-let [x (every? pos? values1)]
  (println "Found")
  (println "Not found"))
 
Not found
nil

Použití makra if-let pro vektor, v němž se nachází jen kladné prvky:

(if-let [x (every? pos? values2)]
  (println "Found")
  (println "Not found"))
 
Found
nil

Použití makra if-some pro vektor, v němž se nachází nula i záporné prvky:

(if-some [x (every? pos? values1)]
  (println "Found")
  (println "Not found"))
 
Found
nil
Poznámka: právě zde se if-some chová jinak než if-let, a to z toho důvodu, že false není rovno nil.

Použití makra if-some pro vektor, v němž se nachází jen kladné prvky:

(if-some [x (every? pos? values2)]
  (println "Found")
  (println "Not found"))
 
Found
nil

7. Použití maker and a or pro řízení toku programu

V některých programovacích jazycích je zvykem nahrazovat některé rozhodovací konstrukce (resp. přesněji řečeno podmínky) s využitím Booleovských operátorů and a or. Podobný koncept můžeme použít i v jazyce Clojure s využitím maker nazvaných taktéž and a or, které jsou n-ární, tj. akceptují libovolný počet parametrů:

(doc and)
 
-------------------------
clojure.core/and
([] [x] [x & next])
Macro
  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.
(doc or)
 
-------------------------
clojure.core/or
([] [x] [x & next])
Macro
  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.

Předností použití těchto maker může být fakt, že se složitější podmínky a současně i větev, která se má vykonat, mohou zapsat jediným výrazem – obě makra totiž akceptují proměnný počet parametrů.

Následuje příklad použití těchto dvou maker pro implementaci funkce, která vrátí znaménko čísla, které je této funkci předáno. Pro kladná čísla se vrátí +1, pro záporná čísla -1 a pro nulu nula:

(defn sgn
  [x]
  (or (and (> x 0) +1)
      (and (< x 0) -1)
      0))

Otestování funkcionality takto definované funkce:

(doseq [value [-100 -1 0 1 100]]
        (println (sgn value)))

S výsledky:

-1
-1
0
1
1

Alternativní zápis s predikáty namísto porovnání hodnoty x s nulou:

(defn sgn-2
  [x]
  (or (and (pos? x) +1)
      (and (neg? x) -1)
      0))

Opětovné otestování funkcionality takto upravené funkce:

(doseq [value [-100 -1 0 1 100]]
        (println (sgn-2 value)))

Se shodnými výsledky, jako tomu bylo v předchozím příkladu:

-1
-1
0
1
1

8. Rozhodovací konstrukce založená na makru cond

Všechny rozhodovací konstrukce popsané v předchozích kapitolách prováděly rozvětvení toku programu na základě vyhodnocení jediné podmínky. Ovšem v praxi se velmi často setkáme s nutností rozhodovat se na základě většího množství podmínek, popř. na základě většího množství hodnot (a obecně pak na základě pattern matchingu, což si ukážeme příště). Pokud je nutné provést rozhodnutí na základě více podmínek, nabízí se využití makra nazvaného cond, které se mj. objevilo (i když jinak zapisované) už v prvních verzích LISPu:

(doc cond)
 
-------------------------
clojure.core/cond
([& clauses])
Macro
  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.

Tomuto makru se předávají dvojice test+výraz. Pokud je test splněn, je vrácena hodnota příslušného výrazu. Poslední test bývá zapsán formou symbolu, který se vždy vyhodnotí na pravdu – což je vlastně jakýkoli symbol rozdílný od false nebo nil. Typicky se používá symbol :else, ovšem někteří vývojáři dávají přednost :default (takže se jedná o céčkaře nebo Javisty :-).

Poznámka: v programovacím jazyce Clojure je cond realizováno formou makra, zatímco v některých implementacích Scheme se jedná o speciální formu. Jediný podstatný rozdíl je, že v Clojure si můžeme zobrazit expanzi tohoto makra (což si ostatně ukážeme v navazující kapitole).

Funkci pro výpočet znaménka lze s využitím makra cond přepsat následujícím způsobem:

(defn sgn-3
  [x]
  (cond (pos? x) 1
        (neg? x) -1
        true 0))

Otestování funkcionality takto upravené funkce:

(doseq [value [-100 -1 0 1 100]]
        (println (sgn-3 value)))

Se shodnými výsledky, jako tomu bylo v předchozích příkladech:

-1
-1
0
1
1

Poslední test se ovšem většinou zapisuje symbolem :else:

(defn sgn-4
  [x]
  (cond (pos? x)  1
        (neg? x) -1
        :else     0))

nebo :default:

(defn sgn-5
  [x]
  (cond (pos? x)  1
        (neg? x) -1
        :default  0))

Přepis funkce pro výpočet největšího společného dělitele:

(defn gcd-2
  [x y]
  (cond
    (= x y) x
    (> x y) (gcd-2 (- x y) y)
    :else   (gcd-2 x (- y x))))

S otestováním:

(println (gcd-2 64 24))
8
nil
 
(println (gcd-2 123456 6543216))
48
nil

9. Plná expanze makra cond

Pokusme se nyní výraz s makrem cond expandovat, abychom zjistili, jaký kód bude vlastně přeložen do bajtkódu:

(macroexpand
  '(cond (pos? x)  1
         (neg? x) -1
         :else     0))

Výsledkem expanze bude:

(if (pos? x) 1 (clojure.core/cond (neg? x) -1 :else 0))

To je zajímavé, protože toto makro volá samo sebe, což funkce macroexpand nedokáže korektně zpracovat. Namísto této funkce bychom tedy potřebovali plnou (rekurzivní) expanzi. Ta je zajištěna s využitím macroexpand-all z balíčku clojure.walk, který je nejdříve nutné načíst:

(use 'clojure.walk)

Nyní se již můžeme pokusit o plnou expanzi makra cond použitého ve výrazu:

(macroexpand-all
  '(cond (pos? x)  1
         (neg? x) -1
         :else     0))

Nyní již výsledek bude skutečně obsahovat pouze základní funkce, speciální formy a atomické hodnoty (tedy takové symboly, které budou překládány do bajtkódu):

(if (pos? x) 1 (if (neg? x) -1 (if :else 0 nil)))

Pro zajímavost si vyzkoušejme expanzi složitějšího výrazu, který převádí bodové ohodnocení na známky:

(let [grade 85]
  (cond
    (>= grade 90) "A"
    (>= grade 80) "B"
    (>= grade 70) "C"
    (>= grade 55) "D"
    :else         "F"))

Expanzi (prozatím neúplnou) makra cond zobrazíme následovně:

(macroexpand
  '(cond
    (>= grade 90) "A"
    (>= grade 80) "B"
    (>= grade 70) "C"
    (>= grade 55) "D"
    :else "F"))

Samotný expandovaný kód vypadá takto:

(if
  (>= grade 90)
  "A"
  (clojure.core/cond
   (>= grade 80)
   "B"
   (>= grade 70)
   "C"
   (>= grade 55)
   "D"
   :else
   "F"))

Úplná expanze:

(macroexpand-all
  '(cond
    (>= grade 90) "A"
    (>= grade 80) "B"
    (>= grade 70) "C"
    (>= grade 55) "D"
    :else "F"))

Z expandovaného makra je patrné, že se jedná o sérii vnořených speciálních forem if:

(if (>= grade 90)
    "A"
    (if (>= grade 80)
        "B"
        (if (>= grade 70)
            "C"
            (if (>= grade 55)
                "D"
                (if :else
                    "F"
                    nil)))))
Poznámka: výše uvedený kód byl ručně upraven tak, aby byl dobře čitelný.

10. Sekvence testů prováděná makrem condp

Výše popsané makro cond je velmi univerzální, protože každý test (kterých může být libovolné množství) je realizován plnohodnotným predikátem, tj. funkcí, na základě jejíž (pravdivostní) návratové hodnoty se rozhoduje, jestli se má provést příslušná větev či zda se má vyzkoušet další test. Ve výsledku je toto makro expandováno na vnořené speciální formy if, což jsme ostatně mohli vidět v předchozí kapitole. Ovšem mnohdy takovou univerzálnost nepotřebujeme a naopak vyžadujeme, aby se výsledek nějakého výrazu porovnal se sekvencí známých hodnot. Taková konstrukce, která je v C, C++ či Javě realizována přes switch, se v programovacím jazyku Clojure zapisuje s využitím makra nazvaného condp:

(doc condp)
 
-------------------------
clojure.core/condp
([pred expr & clauses])
Macro
  Takes a binary predicate, an expression, and a set of clauses.
  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.

Z popisu je zřejmé, že je nutné uvést část výrazu, do kterého se postupně doplňují hodnoty z testů v jednotlivých větvích – skutečně se tedy jedná o obdobu case z C či Javy. Poslední větev pochopitelně žádnou hodnotu pro otestování neobsahuje.

Poznámka: alternativní formu používající :>> si uvedeme v navazujících kapitolách.

Podívejme se nyní na základní způsob použití tohoto makra. Na základě předaného řetězce se rozhodneme, jaká hodnota se vrátí (jedná se o primitivní transformaci, která by se v reálném programu realizovala přes mapu):

(let [value (read-line)]
  (condp = value
      "one"   1
      "two"   2
      "three" 3
      "four"  4
      "five"  5
              "unknown value"))

Pokud je na standardní vstup (do terminálu) zapsán jeden z pěti známých řetězců, provede se jeho převod na číselnou hodnotu. V opačném případě se vrátí řetězec „unknown value“.

V případě, že poslední výraz neuvedeme, dojde při zápisu neznámého vstupu k pádu (resp. přesněji řečeno k vyhození výjimky):

(let [value (read-line)]
  (condp = value
      "one"   1
      "two"   2
      "three" 3
      "four"  4
      "five"  5))
 
Execution error (IllegalArgumentException) at user/eval6625 (REPL:2).
No matching clause: foobarbaz

11. Makro condp a složitější výrazy

Samozřejmě namísto konstant ve větvích můžeme použít nějaký složitější výraz, zde konkrétně volání funkcí +, – atd:

(let [value (read-line)]
  (condp = value
      "one"   (+ 0 1)
      "two"   (+ 1 1)
      "three" 3
      "four"  (* 2 2)
      "five"  5
              (str "unexpected value, \"" value \")))

Výrazy mohou být použity i v testovaných hodnotách (což představuje rozdíl oproti některým výše zmíněným programovacím jazykům):

(let [value (read-line)]
  (condp = value
      "one"           (+ 0 1)
      (str "t" "wo")  (+ 1 1)
      (str "t" "ree") 3
      "four"          (* 2 2)
      "five"          5
                      (str "unexpected value, \"" value \")))
Poznámka: pro jednoduchost jsou všechny příklady z této kapitoly dosti umělé; typicky „školní“.

V praxi to znamená, že makro condp nemůže být přímo přeloženo do instrukcí tableswitch či lookupswitch bajtkódu JVM. Viz též 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.

Následuje poněkud praktičtější příklad, který realizuje další podobu funkce sgn:

(defn sgn-6
  [x]
  (condp apply [x 0]
    = 0
    < -1
    > 1))

Vlastní porovnání (tedy funkce =, < či > se vkládá mezi apply a vektor [x 0].

Otestování této funkce:

(println (sgn-6 -100))
-1
 
(println (sgn-6 -1))
-1
 
(println (sgn-6 0))
0
 
(println (sgn-6 100))
1
Poznámka: tato funkce de facto postupně provádí testy až do chvíle, kdy se vrátí hodnota odlišná od false či nil:
(apply = [x 0])
(apply < [x 0])
(apply > [x 0])

12. Aplikace funkce namísto vyhodnocení výrazu ve větvi

Existuje ještě jedna forma volání makra condp. Tato forma obsahuje v každé větvi tři výrazy: podmínku, nějaký keyword (symbol začínající na dvojtečku) a funkci. V případě, že je podmínka splněna, zavolá se příslušná funkce na výsledek predikátu (testu). Tato funkce tedy musí být unární, protože neexistuje možnost zápisu druhého či dalšího parametru funkce (samozřejmě však můžete v případě potřeby použít uzávěr):

(condp some [1 2 3 4]
  #{0 6 7} :>> inc
  #{4 5 9} :>> dec
  #{1 2 3} :>> #(+ % 3))

Při expanzi se postupně provádí tyto testy až do doby, kdy nějaký test (predikát) vrátí hodnotu rozdílnou od false či nil:

(some #{0 6 7} [1 2 3 4])
(some #{4 5 9} [1 2 3 4])
(some #{1 2 3} [1 2 3 4])

Ve skutečnosti je již druhý test úspěšný:

(some #{4 5 9} [1 2 3 4])
4

Následně je vrácena hodnota výrazu:

(dec 4)
3
Poznámka: tento opět dosti umělý demonstrační příklad byl získán z adresy https://clojuredocs.org/clo­jure.core/condp. Popravdě jsem prozatím tuto variantu v produkčním kódu nikdy nepoužil, už jen z toho důvodu, že podobné rozhodovací konstrukce se mnohdy dají řešit spíše s využitím map.

13. Ukázka expanze makra condp

Zkusme si nyní zobrazit expanzi výše popsaného makra condp. Expandovat budeme výraz pro převod vybraných řetězců na celá čísla:

(def value "two")
 
(condp = value
    "one"   1
    "two"   2
    "three" 3
    "four"  4
    "five"  5
            "unknown value")

Provedeme expanzi makra nám již známou funkcí macroexpand:

(macroexpand
  '(condp = value
      "one"   1
      "two"   2
      "three" 3
      "four"  4
      "five"  5
              "unknown value"))

Z výsledku je patrné, že se provedla expanze (opět) na vnořené speciální formy if:

(let*
  [pred__6764 clojure.core/= expr__6765 user/value]
  (if (pred__6764 "one" expr__6765)
      1
      (if (pred__6764 "two" expr__6765)
          2
          (if (pred__6764 "three" expr__6765)
              3
              (if (pred__6764 "four" expr__6765)
                  4
                  (if (pred__6764 "five" expr__6765) 5 "unknown value"))))))

Povšimněte si použití pomocné proměnné, která je zde důležitá, protože zabraňuje tomu, aby se výraz vyhodnocoval vícekrát za sebou.

Poznámka: výsledek byl opět ručně přeformátován do čitelnější podoby.

Zajímavější je expanze těla funkce sgn realizované makrem condp:

(macroexpand-1
  '(condp apply [x 0]
     = 0
     < -1
     > 1))

Z expandovaného makra je patrné, že se explicitně vytvořil i kód pro vyhození výjimky, zatímco v předchozím demonstračním příkladu se neznámá hodnota řešila ve větvi else nejvnitřnější speciální formy if:

(clojure.core/let [pred__6768 apply
                   expr__6769 [x 0]]
 (if (pred__6768 = expr__6769)
     0
     (if (pred__6768 < expr__6769)
         -1
         (if (pred__6768 > expr__6769)
             1
             (throw (java.lang.IllegalArgumentException.
               (clojure.core/str "No matching clause: " expr__6769)))))))

14. Obsah navazujícího článku

V navazujícím článku dokončíme téma, kterému jsme se věnovali dnes. Nejprve si popíšeme některá další 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 if-not a makro 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 nestandardních knihoven, zejména pak velmi povedené makro cond-table, jehož autorem je Daniel Gregoire a které vypadá takto:

(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)))))

Zabývat se ovšem budeme i dalšími knihovnami pro jazyk Clojure, například:

  1. better-cond
    https://cljdoc.org/d/better-cond/better-cond/2.1.0/doc/readme
  2. A micro-library around the useful cond-let macro
    https://cljdoc.org/d/com.wal­martlabs/cond-let/1.0.0/doc/readme
  3. An adaption of the Racket cond macro for Clojure
    https://cljdoc.org/d/cond-plus/cond-plus/1.0.1/doc/readme
  4. Makro cond-table (vypsané výše)
    https://github.com/semperos/ran­kle/blob/master/src/com/sem­peros/rankle/util.clj

15. Dodatek č.1: makra a speciální formy

Ze syntaktického hlediska jsou speciální formy zapisovány naprosto stejným způsobem jako běžné funkce (tj. v kulatých závorkách je nejprve zapsáno jméno funkce, resp. formy a posléze její parametry), ovšem existuje zde jeden významný rozdíl – zatímco u funkcí jsou všechny jejich parametry nejdříve rekurzivně vyhodnoceny a teprve posléze je funkce zavolána, u speciálních forem k tomuto vyhodnocení obecně nedochází, resp. jsou vyhodnoceny pouze některé parametry (které konkrétně, to závisí na tom, o jakou speciální formu se jedná). K čemu jsou speciální formy dobré? Typickým a pro praxi naprosto nezbytným příkladem je zápis podmíněných bloků kódu, což je ostatně i téma dnešního článku. V tomto případě potřebujeme, aby se nějaká část programu vykonala pouze v případě, že je splněna (popř. nesplněna) nějaká podmínka, v opačném případě nemá být tato část programu vůbec vykonána.

Jazyk Clojure nabízí tvorbu uživatelských maker s využitím makra nazvaného defmacro, jehož použití se do určité míry podobá definici funkce pomocí defn, ovšem s tím rozdílem, že se makro expanduje a nikoli přímo překládá. Význam uživatelských maker spočívá především v tom, že se tato makra provedou (aplikují) při zpracování zadávaných forem těsně předtím, než jsou tyto formy zkompilovány do bajtkódu. To má dva důsledky: makro je v daném místě programu vykonáno pouze jednou a navíc makro může pracovat přímo s uživatelem zadanou formou bez toho, aby se daná forma automaticky vyhodnotila. To mj. znamená, že s využitím uživatelských maker lze realizovat i speciální formy, například nové typy programových smyček apod.

16. Dodatek č.2: expanze maker

Jak při tvorbě nových uživatelských maker, tak i při používání již vytvořených maker, které jsou součástí knihoven programovacího jazyka Clojure, je mnohdy důležité prozkoumat, jak bylo makro expandováno, tj. jakým způsobem změnilo výraz, který byl makru předán. Pro tyto účely lze využít dvojici funkcí nazvaných macroexpand a macroexpand-1:

(doc macroexpand)
-------------------------
clojure.core/macroexpand
([form])
  Repeatedly calls macroexpand-1 on form until it no longer
  represents a macro form, then returns it.  Note neither
  macroexpand-1 nor macroexpand expand macros in subforms.
(doc macroexpand-1)
-------------------------
clojure.core/macroexpand-1
([form])
  If form represents a macro form, returns its expansion,
  else returns form.

Funkce macroexpand-1, které se předá volání makra (jde o funkci, takže je zapotřebí volání zkoumaného makra quotovat!), vrátí expandovaný tvar makra, tj. formu, která je dále zpracována. Přitom se provádí vždy jen expanze prvně aplikovaného makra. Jedná se o funkci, která se velmi často používá při ladění uživatelských maker, protože nás většinou nezajímá, jak se expandují další makra (například when apod.) použitá v expandovaném výrazu. Pokud je z nějakého důvodu nutné vidět celou expanzi výrazu až do té míry, v jaké je výraz předán překladači, lze pro tento účel použít funkci nazvanou macroexpand (její chování velmi zhruba odpovídá chování preprocesoru jazyka C či C++, který taktéž vrátí všechna makra rekurzivně expandovaná).

V některých případech však makro volá samo sebe; zde tedy macroexpand nebude dostačovat. Ovšem i rekurzivní expanzi makra lze zobrazit a to konkrétně s využitím macroexpand-all z balíčku clojure.walk:

(doc macroexpand-all)
-------------------------
clojure.walk/macroexpand-all
([form])
  Recursively performs all possible macroexpansions in form.
Poznámka: tuto možnost jsme využili při expanzi makra cond.

17. Dodatek č.3: speciální formy jazyka Clojure, porovnání se Scheme

Na závěr se ještě zmiňme o speciálních formách programovacího jazyka Clojure. V tomto článku jsme se setkali se čtyřmi speciálními formami (jsou uvedeny na začátku tabulky), ovšem pro úplnost si je vypišme všechny:

DT2021 tip

# Jméno Stručný popis speciální formy
1 def definice nového jména navázaného na hodnotu
2 if podmíněné vyhodnocení prvního či druhého podvýrazu na základě vyhodnocené podmínky
3 do vyhodnocení více výrazů, vrátí se návratová hodnota posledního z nich
4 let blok, na který je navázána nová lokální proměnná či proměnné
5 quote zakazuje vyhodnocení podvýrazu (tedy seznamu)
6 var vrací objekt typu Var (nikoli jeho hodnotu), varianta quote
7 fn definice (anonymní) funkce
8 loop použito pro konstrukci smyčky – cíl pro recur
9 recur skok na začátek smyčky s novými hodnotami navázanými na jména v recur
10 throw vyhození výjimky
11 try vyhodnocení výrazů se zachycením výjimky
12 monitor-enter nízkoúrovňové synchronizační primitivum
13 monitor-exit nízkoúrovňové synchronizační primitivum
14 . přístup k metodám a atributům objektu
15 new konstrukce instance třídy
15 set! nastavení hodnoty lokální proměnné v aktuálním vláknu

Poněkud odlišná je sada speciálních forem v jazyku Scheme (což je taktéž LISPovský programovací jazyk):

# Jméno Stručný popis speciální formy
1 lambda vytvoření anonymní funkce nebo uzávěru
2 define definice nové proměnné (může jít i o funkci)
3 quote zakazuje vyhodnocení podvýrazu (tedy seznamu)
4 set! změna hodnoty proměnné
5 let blok, na který je navázána nová lokální proměnná či proměnné
6 let* podobné let, ovšem umožňuje při inicializaci proměnných použít proměnné nalevo (nahoře) od právě deklarované proměnné
7 letrec podobné let, ovšem navíc je možné se při inicializaci proměnných rekurzivně odkazovat na další proměnné
8 letrec* kombinace let* a letrec
9 begin umožňuje definovat blok s více výrazy, které se postupně vyhodnotí
10 if podmíněné vyhodnocení prvního či druhého podvýrazu na základě vyhodnocené podmínky
11 cond vícenásobné rozvětvení (vyhodnocení podvýrazů)
12 case rozeskok na základě hodnoty vyhodnoceného podvýrazu
13 when pokud je podmínka splněna, vyhodnotí všechny podvýrazy
14 unless pokud podmínka není splněna, vyhodnotí všechny podvýrazy
15 and zkrácené vyhodnocení logického součinu
16 or zkrácené vyhodnocení logického součtu
17 do zápis iterace s inicializací logických proměnných i s jejich změnou v každé iteraci

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/

20. Odkazy na Internetu

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