Obsah
1. Řídicí struktury využitelné v programovacím jazyku Clojure
3. Použití speciální formy if s oběma větvemi a problematika více volaných funkcí ve větvích
5. Kombinace speciálních forem if a let
7. Použití maker and a or pro řízení toku programu
8. Rozhodovací konstrukce založená na makru 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
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
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 |
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)
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 then i else. 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); }
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 then i else. Zápis je v tomto případě jednoduchý:
(if (< x y) (println "x < y") (println "x > y")) x < y nil
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))
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))
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)
(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
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 :-).
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)))))
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.
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 \")))
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
(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
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.
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:
- better-cond
https://cljdoc.org/d/better-cond/better-cond/2.1.0/doc/readme - A micro-library around the useful cond-let macro
https://cljdoc.org/d/com.walmartlabs/cond-let/1.0.0/doc/readme - An adaption of the Racket cond macro for Clojure
https://cljdoc.org/d/cond-plus/cond-plus/1.0.1/doc/readme - Makro cond-table (vypsané výše)
https://github.com/semperos/rankle/blob/master/src/com/semperos/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.
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:
# | 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
- Clojure 1: Úvod
http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm/ - Clojure 2: Symboly, kolekce atd.
http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-2-cast/ - Clojure 3: Funkcionální programování
http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-3-cast-funkcionalni-programovani/ - Clojure 4: Kolekce, sekvence a lazy sekvence
http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-4-cast-kolekce-sekvence-a-lazy-sekvence/ - Clojure 5: Sekvence, lazy sekvence a paralelní programy
http://www.root.cz/clanky/clojure-a-bezpecne-aplikace-pro-jvm-sekvence-lazy-sekvence-a-paralelni-programy/ - Clojure 6: Podpora pro paralelní programování
http://www.root.cz/clanky/programovaci-jazyk-clojure-6-futures-nejsou-jen-financni-derivaty/ - Clojure 7: Další funkce pro paralelní programování
http://www.root.cz/clanky/programovaci-jazyk-clojure-7-dalsi-podpurne-prostredky-pro-paralelni-programovani/ - Clojure 8: Identity, stavy, neměnné hodnoty a reference
http://www.root.cz/clanky/programovaci-jazyk-clojure-8-identity-stavy-nemenne-hodnoty-a-referencni-typy/ - Clojure 9: Validátory, pozorovatelé a kooperace s Javou
http://www.root.cz/clanky/programovaci-jazyk-clojure-9-validatory-pozorovatele-a-kooperace-mezi-clojure-a-javou/ - Clojure 10: Kooperace mezi Clojure a Javou
http://www.root.cz/clanky/programovaci-jazyk-clojure-10-kooperace-mezi-clojure-a-javou-pokracovani/ - Clojure 11: Generátorová notace seznamu/list comprehension
http://www.root.cz/clanky/programovaci-jazyk-clojure-11-generatorova-notace-seznamu-list-comprehension/ - Clojure 12: Překlad programů z Clojure do bajtkódu JVM I:
http://www.root.cz/clanky/programovaci-jazyk-clojure-12-preklad-programu-z-clojure-do-bajtkodu-jvm/ - Clojure 13: Překlad programů z Clojure do bajtkódu JVM II:
http://www.root.cz/clanky/programovaci-jazyk-clojure-13-preklad-programu-z-clojure-do-bajtkodu-jvm-pokracovani/ - Clojure 14: Základy práce se systémem maker
http://www.root.cz/clanky/programovaci-jazyk-clojure-14-zaklady-prace-se-systemem-maker/ - Clojure 15: Tvorba uživatelských maker
http://www.root.cz/clanky/programovaci-jazyk-clojure-15-tvorba-uzivatelskych-maker/ - Programovací jazyk Clojure – triky při práci s řetězci
http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-retezci/ - Programovací jazyk Clojure – triky při práci s kolekcemi
http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-kolekcemi/ - Programovací jazyk Clojure – práce s mapami a množinami
http://www.root.cz/clanky/programovaci-jazyk-clojure-prace-s-mapami-a-mnozinami/ - Programovací jazyk Clojure – základy zpracování XML
http://www.root.cz/clanky/programovaci-jazyk-clojure-zaklady-zpracovani-xml/ - Programovací jazyk Clojure – testování s využitím knihovny Expectations
http://www.root.cz/clanky/programovaci-jazyk-clojure-testovani-s-vyuzitim-knihovny-expectations/ - Programovací jazyk Clojure – některé užitečné triky použitelné (nejenom) v testech
http://www.root.cz/clanky/programovaci-jazyk-clojure-nektere-uzitecne-triky-pouzitelne-nejenom-v-testech/ - Enlive – výkonný šablonovací systém pro jazyk Clojure
http://www.root.cz/clanky/enlive-vykonny-sablonovaci-system-pro-jazyk-clojure/ - Nástroj Leiningen a programovací jazyk Clojure: tvorba vlastních knihoven pro veřejný repositář Clojars
http://www.root.cz/clanky/nastroj-leiningen-a-programovaci-jazyk-clojure-tvorba-vlastnich-knihoven-pro-verejny-repositar-clojars/ - Novinky v Clojure verze 1.8.0
http://www.root.cz/clanky/novinky-v-clojure-verze-1–8–0/ - 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/ - 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/ - 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/ - Vytváříme IRC bota v programovacím jazyce Clojure
http://www.root.cz/clanky/vytvarime-irc-bota-v-programovacim-jazyce-clojure/ - Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/ - Multimetody v Clojure aneb polymorfismus bez použití OOP
https://www.root.cz/clanky/multimetody-v-clojure-aneb-polymorfismus-bez-pouziti-oop/ - 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/ - Clojure 16: Složitější uživatelská makra
http://www.root.cz/clanky/programovaci-jazyk-clojure-16-slozitejsi-uzivatelska-makra/ - Clojure 17: Využití standardních maker v praxi
http://www.root.cz/clanky/programovaci-jazyk-clojure-17-vyuziti-standardnich-maker-v-praxi/ - Clojure 18: Základní techniky optimalizace aplikací
http://www.root.cz/clanky/programovaci-jazyk-clojure-18-zakladni-techniky-optimalizace-aplikaci/ - Clojure 19: Vývojová prostředí pro Clojure
http://www.root.cz/clanky/programovaci-jazyk-clojure-19-vyvojova-prostredi-pro-clojure/ - Clojure 20: Vývojová prostředí pro Clojure (Vimu s REPL)
http://www.root.cz/clanky/programovaci-jazyk-clojure-20-vyvojova-prostredi-pro-clojure-integrace-vimu-s-repl/ - Clojure 21: ClojureScript aneb překlad Clojure do JS
http://www.root.cz/clanky/programovaci-jazyk-clojure-21-clojurescript-aneb-preklad-clojure-do-javascriptu/ - Leiningen: nástroj pro správu projektů napsaných v Clojure
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (2)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-2/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (3)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-3/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (4)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-4/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (5)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-5/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (6)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-6/ - Programovací jazyk Clojure a databáze (1.část)
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-databaze-1-cast/ - Pluginy pro Leiningen
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-pluginy-pro-leiningen/ - Programovací jazyk Clojure a knihovny pro práci s vektory a maticemi
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-knihovny-pro-praci-s-vektory-a-maticemi/ - Programovací jazyk Clojure a knihovny pro práci s vektory a maticemi (2)
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-knihovny-pro-praci-s-vektory-a-maticemi-2/ - Programovací jazyk Clojure: syntéza procedurálních textur s využitím knihovny Clisk
http://www.root.cz/clanky/programovaci-jazyk-clojure-synteza-proceduralnich-textur-s-vyuzitim-knihovny-clisk/ - Programovací jazyk Clojure: syntéza procedurálních textur s využitím knihovny Clisk (2)
http://www.root.cz/clanky/programovaci-jazyk-clojure-synteza-proceduralnich-textur-s-vyuzitim-knihovny-clisk-2/ - Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure
http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure/ - Seesaw: knihovna pro snadnou tvorbu GUI v azyce Clojure (2)
http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure-2/ - Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure (3)
http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure-3/ - Programovací jazyk Clojure a práce s Gitem
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem/ - Programovací jazyk Clojure a práce s Gitem (2)
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem-2/ - Programovací jazyk Clojure: 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/ - Pixie: lehký skriptovací jazyk s „kouzelnými“ schopnostmi
https://www.root.cz/clanky/pixie-lehky-skriptovaci-jazyk-s-kouzelnymi-schopnostmi/ - Programovací jazyk Pixie: funkce ze základní knihovny a použití FFI
https://www.root.cz/clanky/programovaci-jazyk-pixie-funkce-ze-zakladni-knihovny-a-pouziti-ffi/ - Novinky v Clojure verze 1.9.0
https://www.root.cz/clanky/novinky-v-clojure-verze-1–9–0/ - 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/ - 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/ - 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/ - 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/ - Incanter: operace s maticemi
https://www.root.cz/clanky/incanter-operace-s-maticemi/ - Interpret programovacího jazyka Clojure integrovaný do Jupyter Notebooku
https://www.root.cz/clanky/interpret-programovaciho-jazyka-clojure-integrovany-do-jupyter-notebooku/ - 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/ - 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/ - 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/ - 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
- better-cond
https://cljdoc.org/d/better-cond/better-cond/2.1.0/doc/readme - A micro-library around the useful cond-let macro
https://cljdoc.org/d/com.walmartlabs/cond-let/1.0.0/doc/readme - An adaption of the Racket cond macro for Clojure
https://cljdoc.org/d/cond-plus/cond-plus/1.0.1/doc/readme - Makro cond-table
https://github.com/semperos/rankle/blob/master/src/com/semperos/rankle/util.clj - cond v jazyku Racket
https://docs.racket-lang.org/reference/if.html?q=cond#%28form._%28%28lib._racket%2Fprivate%2Fletstx-scheme..rkt%29._cond%29%29 - Learn Clojure – Flow Control
https://clojure.org/guides/learn/flow - clojure.core.logic
https://clojuredocs.org/clojure.core.logic - ETL Batch Processing With Kafka?
https://medium.com/swlh/etl-batch-processing-with-kafka-7f66f843e20d - ETL with Kafka
https://blog.codecentric.de/en/2018/03/etl-kafka/ - Building ETL Pipelines with Clojure and Transducers
https://www.grammarly.com/blog/engineering/building-etl-pipelines-with-clojure-and-transducers/ - pipeline (možné použít pro ETL)
https://clojuredocs.org/clojure.core.async/pipeline - 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/ - Kafka – Understanding Offset Commits
https://www.logicbig.com/tutorials/misc/kafka/committing-offsets.html - fundingcircle/jackdaw (na Clojars)
https://clojars.org/fundingcircle/jackdaw/versions/0.7.6 - Dokumentace ke knihovně jackdaw
https://cljdoc.org/d/fundingcircle/jackdaw/0.7.6/doc/readme - Jackdaw AdminClient API
https://cljdoc.org/d/fundingcircle/jackdaw/0.7.6/doc/jackdaw-adminclient-api - Jackdaw Client API
https://cljdoc.org/d/fundingcircle/jackdaw/0.7.6/doc/jackdaw-client-api - Kafka.clj
https://github.com/helins-io/kafka.clj - 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/ - Apache Kafka: distribuovaná streamovací platforma
https://www.root.cz/clanky/apache-kafka-distribuovana-streamovaci-platforma/ - Real-Time Payments with Clojure and Apache Kafka (podcast)
https://www.evidentsystems.com/news/confluent-podcast-about-apache-kafka/ - Kafka and Clojure – Immutable event streams
https://practicalli.github.io/kafka-and-clojure/ - Kafka Streams, the Clojure way
https://blog.davemartin.me/posts/kafka-streams-the-clojure-way/ - dvlopt.kafka na GitHubu
https://github.com/helins-io/kafka.clj - kafka-streams-the-clojure-way na GitHubu
https://github.com/DaveWM/kafka-streams-the-clojure-way - babashka: A Clojure babushka for the grey areas of Bash
https://github.com/borkdude/babashka - Babashka and the Small Clojure Interpreter @ ClojureD 2020 (slajdy)
https://speakerdeck.com/borkdude/babashka-and-the-small-clojure-interpreter-at-clojured-2020 - Babashka: ukázky použití
https://github.com/borkdude/babashka/blob/master/doc/examples.md - clojureD 2020: „Babashka and Small Clojure Interpreter: Clojure in new contexts“ by Michiel Borkent
https://www.youtube.com/watch?v=Nw8aN-nrdEk&t=5s - Meetup #124 Babashka, implementing an nREPL server & game engines with Clojure
https://www.youtube.com/watch?v=0YmZYnwyHHc - The Last Programming Language (shrnutí vývoje programovacích jazyků)
https://www.youtube.com/watch?v=P2yr-3F6PQo - Shebang (Unix): Wikipedia EN
https://en.wikipedia.org/wiki/Shebang_(Unix) - Shebang (Unix): Wikipedia CZ
https://cs.wikipedia.org/wiki/Shebang_(Unix) - How to create Clojure notebooks in Jupyter
https://s01blog.wordpress.com/2017/12/10/how-to-create-clojure-notebooks-in-jupyter/ - Dokumentace k nástroji Conda
https://docs.conda.io/en/latest/ - Notebook interface
https://en.wikipedia.org/wiki/Notebook_interface - Jypyter: open source, interactive data science and scientific computing across over 40 programming languages
https://jupyter.org/ - Calysto Scheme
https://github.com/Calysto/calysto_scheme - scheme.py (základ projektu Calysto Scheme)
https://github.com/Calysto/calysto_scheme/blob/master/calysto_scheme/scheme.py - Humane test output for clojure.test
https://github.com/pjstadig/humane-test-output - iota
https://github.com/juxt/iota - 5 Differences between clojure.spec and Schema
https://lispcast.com/clojure.spec-vs-schema/ - Schema: Clojure(Script) library for declarative data description and validation
https://github.com/plumatic/schema - Zip archiv s Clojure 1.9.0
http://repo1.maven.org/maven2/org/clojure/clojure/1.9.0/clojure-1.9.0.zip - Clojure 1.9 is now available
https://clojure.org/news/2017/12/08/clojure19 - Deps and CLI Guide
https://clojure.org/guides/deps_and_cli - Changes to Clojure in Version 1.9
https://github.com/clojure/clojure/blob/master/changes.md - clojure.spec – Rationale and Overview
https://clojure.org/about/spec - Zip archiv s Clojure 1.8.0
http://repo1.maven.org/maven2/org/clojure/clojure/1.8.0/clojure-1.8.0.zip - Clojure 1.8 is now available
http://clojure.org/news/2016/01/19/clojure18 - Socket Server REPL
http://dev.clojure.org/display/design/Socket+Server+REPL - CLJ-1671: Clojure socket server
http://dev.clojure.org/jira/browse/CLJ-1671 - CLJ-1449: Add clojure.string functions for portability to ClojureScript
http://dev.clojure.org/jira/browse/CLJ-1449 - Launching a Socket Server
http://clojure.org/reference/repl_and_main#_launching_a_socket_server - API for clojure.string
http://clojure.github.io/clojure/branch-master/clojure.string-api.html - Clojars:
https://clojars.org/ - Seznam knihoven na Clojars:
https://clojars.org/projects - Clojure Cookbook: Templating HTML with Enlive
https://github.com/clojure-cookbook/clojure-cookbook/blob/master/07_webapps/7–11_enlive.asciidoc - An Introduction to Enlive
https://github.com/swannodette/enlive-tutorial/ - Enlive na GitHubu
https://github.com/cgrand/enlive - Expectations: příklady atd.
http://jayfields.com/expectations/ - Expectations na GitHubu
https://github.com/jaycfields/expectations - Lein-expectations na GitHubu
https://github.com/gar3thjon3s/lein-expectations - Testing Clojure With Expectations
https://semaphoreci.com/blog/2014/09/23/testing-clojure-with-expectations.html - Clojure testing TDD/BDD libraries: clojure.test vs Midje vs Expectations vs Speclj
https://www.reddit.com/r/Clojure/comments/1viilt/clojure_testing_tddbdd_libraries_clojuretest_vs/ - Testing: One assertion per test
http://blog.jayfields.com/2007/06/testing-one-assertion-per-test.html - Rewriting Your Test Suite in Clojure in 24 hours
http://blog.circleci.com/rewriting-your-test-suite-in-clojure-in-24-hours/ - Clojure doc: zipper
http://clojuredocs.org/clojure.zip/zipper - Clojure doc: parse
http://clojuredocs.org/clojure.xml/parse - Clojure doc: xml-zip
http://clojuredocs.org/clojure.zip/xml-zip - Clojure doc: xml-seq
http://clojuredocs.org/clojure.core/xml-seq - Parsing XML in Clojure
https://github.com/clojuredocs/guides - Clojure Zipper Over Nested Vector
https://vitalyper.wordpress.com/2010/11/23/clojure-zipper-over-nested-vector/ - Understanding Clojure's PersistentVector implementation
http://blog.higher-order.net/2009/02/01/understanding-clojures-persistentvector-implementation - Understanding Clojure's PersistentHashMap (deftwice…)
http://blog.higher-order.net/2009/09/08/understanding-clojures-persistenthashmap-deftwice.html - Assoc and Clojure's PersistentHashMap: part ii
http://blog.higher-order.net/2010/08/16/assoc-and-clojures-persistenthashmap-part-ii.html - Ideal Hashtrees (paper)
http://lampwww.epfl.ch/papers/idealhashtrees.pdf - 4Clojure
http://www.4clojure.com/ - ClojureDoc (rozcestník s dokumentací jazyka Clojure)
http://clojuredocs.org/ - Clojure (na Wikipedia EN)
http://en.wikipedia.org/wiki/Clojure - Clojure (na Wikipedia CS)
http://cs.wikipedia.org/wiki/Clojure - SICP (The Structure and Interpretation of Computer Programs)
http://mitpress.mit.edu/sicp/ - Pure function
http://en.wikipedia.org/wiki/Pure_function - Funkcionální programování
http://cs.wikipedia.org/wiki/Funkcionální_programování - Čistě funkcionální (datové struktury, jazyky, programování)
http://cs.wikipedia.org/wiki/Čistě_funkcionální - Clojure Macro Tutorial (Part I, Getting the Compiler to Write Your Code For You)
http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-i-getting.html - Clojure Macro Tutorial (Part II: The Compiler Strikes Back)
http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-ii-compiler.html - Clojure Macro Tutorial (Part III: Syntax Quote)
http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-ii-syntax.html - Tech behind Tech: Clojure Macros Simplified
http://techbehindtech.com/2010/09/28/clojure-macros-simplified/ - Fatvat – Exploring functional programming: Clojure Macros
http://www.fatvat.co.uk/2009/02/clojure-macros.html