Obsah
1. Validace dat s využitím knihovny spec v Clojure 1.9.0 (dokončení)
2. Spuštění interaktivní smyčky REPL s interpretrem Clojure 1.9.0
5. Validace v případě, že hodnota může být nil
6. Vylepšení validace s využitím makra nilable
7. Varianty funkce explain – explain-str a explain-data
8. Malé zopakování z minula: základní validace obsahu kolekcí
9. První varianta validátoru IPv4 adres
10. Validace sekvencí s využitím operátorů převzatých z regulárních výrazů
11. Druhá varianta validátoru IPv4 adres
12. Příklad validace sekvence s využitím operátoru +
13. Validace složitější datové struktury
15. Validace vektoru s konfiguračními volbami
16. Automatický destructuring po validaci datové struktury
17. Generování testovacích dat na základě validačních kritérií
18. Repositář s demonstračními příklady
19. Odkazy na předchozí části tohoto seriálu
1. Validace dat s využitím knihovny spec v Clojure 1.9.0 (dokončení)
V dnešním článku o nejnovější stabilní verzi 1.9.0 programovacího jazyka Clojure dokončíme popis možností nabízených velmi užitečnou knihovnou nazvanou spec. Již z předchozího článku víme, že tato knihovna slouží k deklaraci očekávané struktury dat (popř. i jediné hodnoty) a následně k jejich validaci. Složitější datové struktury jsou v jazyku Clojure většinou reprezentovány formou slovníku, který (rekurzivně) obsahuje další struktury, tj. další slovníky, seznamy, vektory či množiny. Právě z tohoto důvodu používá knihovna spec operátory (či možná lépe řečeno klauzule), které do jisté míry dokážou zkontrolovat i sekvence, jejichž délka nemusí být dopředu známá. Tyto operátory byly převzaty z regulárních výrazů a mají i stejnou vyjadřovací schopnost (včetně všech omezení). Pěkným příkladem může být seznam či vektor osob, mapa představující konfigurační volby apod.
Ve druhé části článku se zmíníme i o kombinaci schopností knihoven spec a test.check pro generování testovacích dat. Samotná knihovna test.check je přitom reimplementací známé knihovny QuickCheck, která původně vznikla v Haskellu, ale později byla znovu implementována pro potřeby dalších programovacích jazyků.
2. Spuštění interaktivní smyčky REPL s interpretrem Clojure 1.9.0
Všechny dále uváděné deklarace, volání funkcí i maker atd., budeme zkoušet přímo v interaktivní smyčce (REPL) programovacího jazyka Clojure. Ta ovšem musí být spuštěna pro interpret Clojure 1.9.0 (nebo i Clojure 1.10.0 Alpha), což lze zajistit například tak, že v adresáři projektu clojure9-test spustíte skript nazvaný repl:
$ ./repl nREPL server started on port 35985 on host 127.0.0.1 - nrepl://127.0.0.1:35985 REPL-y 0.3.7, nREPL 0.2.12 Clojure 1.9.0 OpenJDK 64-Bit Server VM 1.8.0_151-b12 Docs: (doc function-name-here) (find-doc "part-of-name-here") Source: (source function-name-here) Javadoc: (javadoc java-object-or-class-here) Exit: Control+D or (exit) or (quit) Results: Stored in vars *1, *2, *3, an exception in *e clojure9-test.core=>
Ihned poté si přenastavte jmenný prostor z clojure-9-test.core na user a následně naimportujte knihovnu spec:
clojure9-test.core=> (ns user) nil user=> (require '[clojure.spec.alpha :as spec]) nil
Ještě nám zbývá dořešit maličkost – po změně jmenného prostoru totiž přestane být dostupné makro doc, což si ostatně můžeme vyzkoušet:
user=> (doc doc) CompilerException java.lang.RuntimeException: Unable to resolve symbol: doc in this context, compiling:(/tmp/form-init3972254276603866443.clj:1:1)
Makro doc je ovšem velmi užitečné a několikrát ho použijeme, takže si ho naimportujeme:
user=> (use '[clojure.repl :only (doc)]) nil
Opět zkusíme toto makro zavolat, tentokrát již úspěšně:
user=> (doc doc) ------------------------- clojure.repl/doc ([name]) Macro Prints documentation for a var or special form given its name, or for a spec if given a keyword nil
Nyní je již interaktivní smyčka REPL připravena pro další testy.
3. Použití klauzule spec/and
Začneme krátkým připomenutím existence klauzule and, která nám umožňuje spojit větší množství validačních kritérií. Data budou zvalidována jen ve chvíli, kdy budou splněna všechna kritéria (ta jsou reprezentována „obyčejnými“ predikáty). Samotná klauzule and (asi nepřekvapivě reprezentovaná makrem) akceptuje libovolné množství kritérií:
user=> (doc spec/and) ------------------------- clojure.spec.alpha/and ([& pred-forms]) Macro Takes predicate/spec-forms, e.g. (s/and even? #(< % 42)) Returns a spec that returns the conformed value. Successive conformed values propagate through rest of predicates.
Ukažme si nyní jednoduchý příklad použití této klauzule. Budeme testovat, zda nějaká hodnota odpovídá UID běžného uživatele, tj. zda se jedná o kladné celé číslo větší nebo rovno 1000 a současně menší než 231-1 (u starších Unixů jen 65535 nebo dokonce jen 32767). S využitím klauzule and se validace zapíše jednoduše. Povšimněte si, že využíváme anonymní funkce; samozřejmě je však můžeme nahradit explicitně zapsanými predikáty:
user=> (spec/def ::user-id (spec/and pos-int? #(>= % 1000) #(< % Integer/MAX_VALUE))) :user/user-id
K validačnímu kritériu se automaticky vygenerovala nápověda:
user=> (doc ::user-id) ------------------------- :user/user-id Spec (and pos-int? (>= % 1000) (< % MAX_VALUE))
Funkcionalitu si samozřejmě ihned můžeme vyzkoušet:
user=> (spec/valid? ::user-id nil) false user=> (spec/valid? ::user-id "hello") false user=> (spec/valid? ::user-id 1) false user=> (spec/valid? ::user-id 1000) true user=> (spec/valid? ::user-id 10000000) true user=> (spec/valid? ::user-id 100000000) true user=> (spec/valid? ::user-id 1000000000) true user=> (spec/valid? ::user-id 10000000000) false
Tato klauzule je použita i v dnešním prvním demonstračním příkladu, který naleznete na adrese https://github.com/tisnik/clojure-examples/tree/master/spec-demo6.
4. Použití klauzule spec/or
Kromě klauzule and samozřejmě existuje i klauzule or, která je opět reprezentovaná makrem. Tato klauzule umožňuje, aby bylo možné zapsat větší množství predikátů, přičemž data budou validována i v případě, kdy bude platný jeden jediný predikát (nezávisle na tom který). Před každým predikátem (validačním kritériem) se zapisuje jeho identifikátor, aby bylo možné později určit, která podmínka (podmínky) byly splněny a které nikoli:
user=> (doc spec/or) ------------------------- clojure.spec.alpha/or ([& key-pred-forms]) Macro Takes key+pred pairs, e.g. (s/or :even even? :small #(< % 42)) Returns a destructuring spec that returns a map entry containing the key of the first matching pred and the corresponding value. Thus the 'key' and 'val' functions can be used to refer generically to the components of the tagged return.
Opět si funkci tohoto predikátu ukažme na příkladu. Tentokrát budeme testovat, zda validovaná hodnota představuje identifikátor, který může být představován buď řetězcem nebo kladným celým číslem. Povšimněte si, že před oběma predikáty string? a pos-int? skutečně zapisujeme jednoznačný klíč:
user=> (spec/def ::unique-id (spec/or :name string? :id pos-int?)) :user/unique-id
Vygenerovaná nápověda je podobná nápovědě, kterou jsme viděli v předchozí kapitole:
user=> (doc ::unique-id) ------------------------- :user/unique-id Spec (or :name string? :id pos-int?)
Správnou funkci validace si opět otestujeme, podobně jako v předchozím případě:
user=> (spec/valid? ::unique-id 42) true user=> (spec/valid? ::unique-id "foo") true user=> (spec/valid? ::unique-id nil) false user=> (spec/valid? ::unique-id []) false
Pomocí spec/conform lze získat jak správnou hodnotu (pokud byla zvalidována), tak i informaci o tom, který predikát byl splněn:
user=> (spec/conform ::unique-id 42) [:id 42] user=> (spec/conform ::unique-id "foo") [:name "foo"] user=> (spec/conform ::unique-id nil) :clojure.spec.alpha/invalid user=> (spec/conform ::unique-id []) :clojure.spec.alpha/invalid user=> (spec/explain ::unique-id 42) Success! nil
Taktéž můžeme s využitím spec/explain zjistit, které predikáty byly či nebyly splněny:
user=> (spec/explain ::unique-id []) val: [] fails spec: :user/unique-id at: [:name] predicate: string? val: [] fails spec: :user/unique-id at: [:id] predicate: pos-int? nil user=> (spec/explain ::unique-id nil) val: nil fails spec: :user/unique-id at: [:name] predicate: string? val: nil fails spec: :user/unique-id at: [:id] predicate: pos-int? nil
Předchozí validační kritérium bylo zvláštní tím, že vždy platil maximálně jeden predikát, nikdy ne oba dva (žádná hodnota není současně řetězec a současně celé číslo). Ovšem můžeme samozřejmě použít i predikáty, které se „překrývají“. Například lze napsat více kritérií pro všechna celá čísla a zjistit, zda se skutečně vyhodnotí všechny, nebo se vyhodnocování zastaví při nalezení prvního platného predikátu:
user=> (spec/def ::integer (spec/or :negative neg-int? :zero zero? :positive pos-int?)) user=> (doseq [i (range -5 6)] (println (spec/conform ::integer i))) [:negative -5] [:negative -4] [:negative -3] [:negative -2] [:negative -1] [:zero 0] [:positive 1] [:positive 2] [:positive 3] [:positive 4] [:positive 5] nil
Klauzule or je použita i v dnešním druhém demonstračním příkladu, který naleznete na adrese https://github.com/tisnik/clojure-examples/tree/master/spec-demo7.
5. Validace v případě, že hodnota může být nil
Poměrně často se setkáme se situací, kdy nějaká hodnota musí splňovat určité kritérium (například být řetězcem) popř. může být prázdná, což je v Clojure a vlastně i ve většině LISPovských jazyků představováno hodnotou nil. S využitím klauzule or samozřejmě takovou podmínku můžeme napsat, a to například takto (komentář musí být řetězcem nebo nesmí být zadaný vůbec):
user=> (spec/def ::comment (spec/or :filled string? :empty nil?)) :user/comment
Pro jistotu se podívejme na vygenerovanou nápovědu:
user=> (doc ::comment) ------------------------- :user/comment Spec (or :filled string? :empty nil?)
Nové validační kritérium je snadné otestovat pro hodnoty různých typů:
user=> (spec/valid? ::comment "komentar") true user=> (spec/valid? ::comment nil) true user=> (spec/valid? ::comment []) false user=> (spec/valid? ::comment false) false user=> (spec/valid? ::comment (range 10)) false user=> (spec/explain ::comment (range 10)) val: (0 1 2 3 4 5 6 7 8 9) fails spec: :user/comment at: [:filled] predicate: string? val: (0 1 2 3 4 5 6 7 8 9) fails spec: :user/comment at: [:empty] predicate: nil? nil user=> (spec/explain ::comment '()) val: () fails spec: :user/comment at: [:filled] predicate: string? val: () fails spec: :user/comment at: [:empty] predicate: nil? nil
Úprava příkladu z konce předchozí kapitoly:
user=> (spec/def ::possible-integer (spec/or :none nil? :negative neg-int? :zero zero? :positive pos-int?)) :user/possible-integer user=> (spec/conform ::possible-integer nil) [:none nil] user=> (doseq [i (range -5 6)] (println (spec/conform ::possible-integer i))) [:negative -5] [:negative -4] [:negative -3] [:negative -2] [:negative -1] [:zero 0] [:positive 1] [:positive 2] [:positive 3] [:positive 4] [:positive 5] nil
Ve třetím demonstračním příklad, který naleznete na adrese https://github.com/tisnik/clojure-examples/tree/master/spec-demo8, je ukázáno použití takto vytvořeného validačního kritéria.
6. Vylepšení validace s využitím makra nilable
Validační kritéria popsaná v předchozí kapitole jsou sice funkční, ale existuje i idiomatičtější řešení založené na použití makra nilable. Tomu se předá libovolný predikát, k němuž je automaticky doplněno „spec/or nil?“:
user=> (doc spec/nilable) ------------------------- clojure.spec.alpha/nilable ([pred]) Macro returns a spec that accepts nil and values satisfying pred nil
Zkusme si nyní předeklarovat validační kritérium pro komentáře s využitím makra nilable. Je to velmi jednoduché:
user=> (spec/def ::comment (spec/nilable string?)) :user/comment user=> (doc ::comment) ------------------------- :user/comment Spec (nilable string?)
Interně se makro nilable expanduje následujícím způsobem:
user=> (macroexpand-1 '(spec/nilable string?)) (clojure.spec.alpha/nilable-impl (quote clojure.core/string?) string? nil)
Opět si novou variantu validačního kritéria pro komentáře můžeme otestovat:
user=> (spec/valid? ::comment "komentar") true user=> (spec/valid? ::comment nil) true user=> (spec/valid? ::comment []) false user=> (spec/valid? ::comment false) false user=> (spec/explain ::comment (range 10)) val: (0 1 2 3 4 5 6 7 8 9) fails spec: :user/comment at: [:clojure.spec.alpha/pred] predicate: string? val: (0 1 2 3 4 5 6 7 8 9) fails spec: :user/comment at: [:clojure.spec.alpha/nil] predicate: nil? nil user=> (spec/explain ::comment '()) val: () fails spec: :user/comment at: [:clojure.spec.alpha/pred] predicate: string? val: () fails spec: :user/comment at: [:clojure.spec.alpha/nil] predicate: nil? nil
Makro nilable je použito ve čtvrtém demonstračním příkladu, který naleznete na adrese https://github.com/tisnik/clojure-examples/tree/master/spec-demo9.
7. Varianty funkce explain – explain-str a explain-data
Při zjišťování, z jakého důvodu nebyla data zvalidována, jsme až doposud používali pouze jedinou funkci nazvanou explain. Tato funkce vytiskla výsledek validace na standardní výstup, popř. do libovolného výstupního streamu navázaného na symbol *out*:
user=> (doc spec/explain) ------------------------- clojure.spec.alpha/explain ([spec x]) Given a spec and a value that fails to conform, prints an explanation to *out*. nil
V některých případech, například při potřebě logování, je však výhodnější použít funkci pojmenovanou explain-str. Ta provádí stejnou činnost jako funkce explain, ovšem výsledek je vrácen formou řetězce:
user=> (doc spec/explain-str) ------------------------- clojure.spec.alpha/explain-str ([spec x]) Given a spec and a value that fails to conform, returns an explanation as a string. nil
Nejzajímavější je však poslední funkce pojmenovaná explain-data. Tato funkce totiž vrátí výsledek validace ve formě datové struktury, kterou je možné relativně snadno spravovat (určitě jednodušeji, než parsováním textového výstupu):
user=> (doc spec/explain-data) ------------------------- clojure.spec.alpha/explain-data ([spec x]) Given a spec and a value x which ought to conform, returns nil if x conforms, else a map with at least the key ::problems whose value is a collection of problem-maps, where problem-map has at least :path :pred and :val keys describing the predicate and the value that failed at that path. nil
Můžeme si to vyzkoušet na již dříve ukázaných validačních kritériích:
user=> (spec/explain-data ::comment '()) #:clojure.spec.alpha{:problems [{:path [:clojure.spec.alpha/pred], :pred clojure.core/string?, :val (), :via [:user/comment], :in []} {:path [:clojure.spec.alpha/nil], :pred nil?, :val (), :via [:user/comment], :in []}], :spec :user/comment, :value ()}
Poměrně nečitelný výstup lze vylepšit zavoláním „pretty-print“ funkce clojure.pprint/pprint. Pro zpřehlednění zápisu použijeme threading makro → (:pred znamená predikát):
user=> (-> (spec/explain-data ::comment '()) clojure.pprint/pprint) #:clojure.spec.alpha{:problems [{:path [:clojure.spec.alpha/pred], :pred clojure.core/string?, :val (), :via [:user/comment], :in []} {:path [:clojure.spec.alpha/nil], :pred nil?, :val (), :via [:user/comment], :in []}], :spec :user/comment, :value ()} user=> (-> (spec/explain-data ::unique-id nil) clojure.pprint/pprint) #:clojure.spec.alpha{:problems ({:path [:name], :pred clojure.core/string?, :val nil, :via [:user/unique-id], :in []} {:path [:id], :pred clojure.core/pos-int?, :val nil, :via [:user/unique-id], :in []}), :spec :user/unique-id, :value nil}
Pro validní data se však vrátí pouze nil (což je vcelku rozumné chování):
user=> (-> (spec/explain-data ::possible-integer 42) clojure.pprint/pprint) nil nil user=> (-> (spec/explain-data ::possible-integer nil) clojure.pprint/pprint) nil nil
8. Malé zopakování z minula: základní validace obsahu kolekcí
Připomeňme si, že v knihovně spec nalezneme i makro pojmenované coll-of. Toto makro je možné použít ve chvíli, kdy potřebujeme otestovat delší sekvenci, vektor či seznam. Můžeme přitom specifikovat predikát aplikovaný na prvky sekvence, očekávaný počet prvků v sekvenci, minimální a maximální očekávaný počet prvků, to, zda se prvky mohou opakovat atd. Dokonce je možné specifikovat, zda se má sekvence po validaci převést na jiný typ sekvence (vektor na seznam atd.). Nejjednodušší použití spočívá pouze ve specifikaci predikátu, který musí platit pro všechny prvky sekvence či kolekce:
user=> (spec/def ::positive-integers (spec/coll-of pos-int?)) :user/pos-integers
Funkci lze otestovat snadno:
user=> (spec/valid? ::positive-integers [1 2 3]) true user=> (spec/valid? ::positive-integers (range 10)) false user=> (spec/valid? ::positive-integers (range 1 10)) true user=> (spec/valid? ::positive-integers (repeat 10 42)) true user=> (spec/valid? ::positive-integers (repeat 10 "A")) false user=> (spec/explain ::positive-integers (range 1 10)) Success!
Podobně lze vytvořit i kontrolu pro všechny prvky slovníku. Zde musíme testovat jak klíče, tak (samostatně) hodnoty uložené pod jednotlivými klíči::
user=> (spec/def ::dictionary (spec/map-of string? string?)) :user/dictionary
Opět se podívejme na výsledky použití tohoto validačního kritéria:
user=> (spec/valid? ::dictionary nil) false user=> (spec/valid? ::dictionary [1 2]) false user=> (spec/valid? ::dictionary {"one" "jedna" "two" "dve"}) true user=> (spec/valid? ::dictionary {"one" 1 "two" 2}) false
9. První varianta validátoru IPv4 adres
Minule jsme se zmínili i o funkci spec/tuple určené pro validaci n-tic, u nichž je známý počet a typ prvků (na rozdíl od potenciálně nekonečných sekvencí!). Podívejme se nyní na nepatrně složitější příklad, konkrétně na kontrolu, zda daná čtveřice obsahuje validní IPv4 adresu. Pro jednoduchost prozatím předpokládejme, že jednotlivé oktety mohou nabývat libovolné hodnoty od 0 do 255. Nejprve si tedy nadefinujeme validační kritérium pro každý oktet:
user=> (spec/def ::byte (spec/and nat-int? #(<= % 255))) :user/byte
Otestování je snadné:
user=> (spec/valid? ::byte -1) false user=> (spec/valid? ::byte 0) true user=> (spec/valid? ::byte 255) true user=> (spec/valid? ::byte 256) false user=> (spec/valid? ::byte nil) false user=> (spec/valid? ::byte "adresa") false user=> (spec/valid? ::byte [:foo :bar]) false
Nyní již můžeme vytvořit validační kritérium pro celou IPv4 adresu:
user=> (spec/def ::ip-address (spec/tuple ::byte ::byte ::byte ::byte)) :user/ip-address
Opět se nám vygenerovala nápověda:
user=> (doc ::ip-address) ------------------------- :user/ip-address Spec (tuple :user/byte :user/byte :user/byte :user/byte)
Validace adres:
user=> (spec/valid? ::ip-address [127 0 0 1]) true user=> (spec/valid? ::ip-address [256 0 0 1]) false user=> (spec/valid? ::ip-address [127 0 0 0 1]) false
Zjištění, která část nebyla zvalidována (problém je vyznačen tučným písmem):
user=> (-> (spec/explain-data ::ip-address [127 0 0 0 1]) clojure.pprint/pprint) #:clojure.spec.alpha{:problems [{:path [], :pred (clojure.core/= (clojure.core/count %) 4), :val [127 0 0 0 1], :via [:user/ip-address], :in []}], :spec :user/ip-address, :value [127 0 0 0 1]} nil
user=> (-> (spec/explain-data ::ip-address [127 0 256 0]) clojure.pprint/pprint) #:clojure.spec.alpha{:problems ({:path [2], :pred (clojure.core/fn [%] (clojure.core/<= % 255)), :val 256, :via [:user/ip-address :user/byte], :in [2]}), :spec :user/ip-address, :value [127 0 256 0]} nil
10. Validace sekvencí s využitím operátorů převzatých z regulárních výrazů
Nyní se již dostáváme k zajímavější problematice. Ve chvíli, kdy validujeme sekvence, se totiž může stát, že chceme aplikovat různá validační kritéria na různé prvky sekvence, přičemž ovšem délka sekvence nemusí být předem známá (potom by se vlastně jednalo o n-tice). Pro sekvence s předem neznámým počtem prvků můžeme použít operátory, které jsou převzaté z regulárních výrazů, u nichž také nemusíme specifikovat přesný počet prvků (příkladem je [a-z]+, což je výraz, který jen říká, že očekáváme libovolný počet písmen malé abecedy). V knihovně spec můžeme postupovat stejně, a to díky použití těchto operátorů předávaných do spec/def:
Operátor | Význam |
---|---|
cat | spojení (zřetězení) predikátů |
alt | výběr jedné alternativy z množiny predikátů |
* | očekává se 0 nebo více výskytů hodnoty odpovídající predikátu |
+ | očekává se jeden nebo více výskytů hodnoty odpovídající predikátu |
? | očekává se 0 nebo jeden výskyt hodnoty odpovídající predikátu |
Vidíme, že poslední tři operátory skutečně odpovídají stejně zapisovaným operátorům v regulárních výrazech. Příklady si ukážeme v navazujících kapitolách.
11. Druhá varianta validátoru IPv4 adres
Druhou variantu validátoru IPv4 adres vytvoříme s využitím operátoru cat, který umožní zřetězení většího množství predikátů. Před každým predikátem se navíc uvede jednoznačný identifikátor (typicky klíč), jehož význam si vysvětlíme později. Víme, že IPv4 adresa se skládá ze čtyř oktetů, takže zapíšeme „regulární výraz“ takto:
user=> (spec/def ::ip-address-2 (spec/cat :1st ::byte :2nd ::byte :3rd ::byte :4th ::byte)) :user/ip-address-2
Samozřejmě, jak již očekáváte, se vytvořila dokumentace k validačnímu kritériu:
user=> (doc ::ip-address-2) ------------------------- :user/ip-address-2 Spec (cat :1st :user/byte :2nd :user/byte :3rd :user/byte :4th :user/byte) nil
Mnohem zajímavější je však zjistit, jak bude probíhat validace:
user=> (spec/valid? ::ip-address-2 [127 0 0 0 1]) false user=> (spec/valid? ::ip-address-2 [127 0 0 1]) true user=> (-> (spec/explain-data ::ip-address-2 [127 0 0 0 1]) clojure.pprint/pprint) #:clojure.spec.alpha{:problems [{:path [], :reason "Extra input", :pred (clojure.spec.alpha/cat :1st :user/byte :2nd :user/byte :3rd :user/byte :4th :user/byte), :val (1), :via [:user/ip-address-2], :in [4]}], :spec :user/ip-address-2, :value [127 0 0 0 1]}
Trošku předběhneme, ale ukažme si výsledek volání spec/conform. Pokud jsou vstupní data korektní, provede se automatický destructuring, a to právě s využitím klíčů. To je velmi zajímavá vlastnost, kterou oceníme především u složitěji strukturovaných dat:
user=> (spec/conform ::ip-address-2 [127 0 0 1]) {:1st 127, :2nd 0, :3rd 0, :4th 1}
12. Příklad validace sekvence s využitím operátoru +
S využitím operátoru + můžeme definovat, že se očekává alespoň jeden prvek splňující další predikát. V nejjednodušším případě použití tohoto operátoru vypadá následovně – budeme testovat, zda sekvence obsahuje alespoň jedno sudé číslo nebo více sudých čísel:
user=> (spec/def ::even-numbers (spec/+ even?)) :user/even-numbers
Opět si otestujeme chování:
user=> (spec/valid? ::even-numbers [2]) true user=> (spec/valid? ::even-numbers []) false user=> (spec/valid? ::even-numbers [1 2 3]) false user=> (spec/valid? ::even-numbers [2 4 6]) true user=> (spec/explain ::even-numbers [1 2 3]) In: [0] val: 1 fails spec: :user/even-numbers predicate: even? nil user=> (spec/valid? ::even-numbers (map #(* % 2) (range 10))) true
Malý počet prvků v sekvenci:
user=> (spec/explain ::even-numbers []) val: () fails spec: :user/even-numbers predicate: even?, Insufficient input nil
13. Validace složitější datové struktury
Vhodnou kombinací operátorů cat, + atd. lze zajistit i validaci složitěji strukturovaných sekvencí. Například v následujícím příkladu očekáváme, že prvním prvkem sekvence bude UID (řetězec) a další prvky budou striktně kladná čísla. Zápis přesně odpovídá předchozímu slovnímu popisu podmínky:
user=> (spec/def ::uid string?) :user/uid user=> (spec/def ::sequence-with-uid (spec/cat :uid ::uid :seq (spec/+ pos-int?))) :user/sequence-with-uid
Otestování:
user=> (spec/valid? ::sequence-with-uid ["x" 1 2 3]) true user=> (spec/valid? ::sequence-with-uid [0 1 2 3]) false user=> (spec/valid? ::sequence-with-uid [0 1 2 3 "x"]) false
I v tomto případě bude probíhat velmi užitečný destructuring:
user=> (spec/conform ::sequence-with-uid ["x" 1 2 3]) {:uid "x", :seq [1 2 3]}
Zjištění důvodu, proč se validace nepovedla (predikát, který nebyl splněn, je označen tučným písmem):
user=> (-> (spec/explain-data ::sequence-with-uid [0 1 2 3 "x"]) clojure.pprint/pprint) #:clojure.spec.alpha{:problems [{:path [:uid], :pred clojure.core/string?, :val 0, :via [:user/sequence-with-uid :user/sequence-with-uid :user/uid :user/uid], :in [0]}], :spec :user/sequence-with-uid, :value [0 1 2 3 "x"]} nil
Samozřejmě nám nic nebrání převést UID na konec celé sekvence, opět ve stylu regulárních výrazů:
user=> (spec/def ::sequence-with-uid-at-end (spec/cat :seq (spec/+ pos-int?) :uid ::uid)) :user/sequence-with-uid-at-end user=> (spec/valid? ::sequence-with-uid-at-end [1 2 3 "x"]) true user=> (spec/conform ::sequence-with-uid-at-end [1 2 3 "x"]) {:seq [1 2 3], :uid "x"}
14. Kombinace operátorů + a ?
Následující příklad je převzat z oficiální dokumentace ke knihovně spec. Deklarujeme v něm validační kritérium, kdy sekvence má na začátku obsahovat libovolné množství lichých čísel, za nimiž může (ale také nemusí) následovat jedno číslo sudé. Zápis opět připomíná regulární výraz (liché+sudé?):
user=> (spec/def ::odds-then-maybe-even (spec/cat :odds (spec/+ odd?) :even (spec/? even?))) :user/odds-then-maybe-even
V tomto příkladu je nejzajímavější destructuring, který vypadá takto:
user=> (spec/conform ::odds-then-maybe-even [1 3 5 100]) {:odds [1 3 5], :even 100} user=> (spec/conform ::odds-then-maybe-even [1]) {:odds [1]} user=> (spec/conform ::odds-then-maybe-even [1 2]) {:odds [1], :even 2} user=> (spec/conform ::odds-then-maybe-even [1 2 3]) :clojure.spec.alpha/invalid user=> (spec/conform ::odds-then-maybe-even [1 3 5 7 2]) {:odds [1 3 5 7], :even 2} user=> (spec/conform ::odds-then-maybe-even [1 3 5 7]) {:odds [1 3 5 7]}
Povšimněte si, že pokud není na konci nalezeno sudé číslo, v mapě se vůbec nevrátí klíč :even.
15. Validace vektoru s konfiguračními volbami
V dalším příkladu si ukážeme, jak je možné validovat datovou strukturu obsahující konfigurační volby. Od této struktury požadujeme, aby obsahovala vždy klíč, což je řetězec (lze doplnit požadavek na jeho unikátnost), a povinnou hodnotu libovolného typu, ovšem odlišnou od nil. Druhou podmínku nám zajistí predikát some?. Počet dvojic klíč+hodnota není předem známý:
user=> (spec/def ::configuration (spec/+ (spec/cat :name string? :value some?))) :user/configuration
Ukázka otestování:
user=> (spec/valid? ::configuration ["debug" true "url" "localhost" "port" 8080]) true user=> (spec/valid? ::configuration ["debug" true "url" "localhost" "port"]) false user=> (spec/explain ::configuration ["debug" true "url" "localhost" "port"]) val: () fails spec: :user/configuration at: [:value] predicate: some?, Insufficient input nil
Prázdný vektor bez konfigurace není validní:
user=> (spec/valid? ::configuration []) false user=> (spec/explain ::configuration []) val: () fails spec: :user/configuration at: [:name] predicate: string?, Insufficient input nil
Pokud přesto budeme chtít povolit použití prázdných vektorů, je to velmi snadné – změnou operátoru + za operátor *:
user=> (spec/def ::configuration (spec/* (spec/cat :name string? :value some?))) :user/configuration
Otestování:
user=> (spec/valid? ::configuration nil) true user=> (spec/valid? ::configuration []) true user=> (spec/valid? ::configuration ["debug" true "url" "localhost" "port"]) false user=> (spec/valid? ::configuration ["debug" true "url" "localhost" "port" 8080]) true
16. Automatický destructuring po validaci datové struktury
Již několikrát jsme si ukázali možnosti „destructuringu“ dat, takže si tuto vlastnost vyzkoušejme na nepatrně složitějším příkladu. Opět se bude jednat o konfigurační volby, resp. přesněji řečeno o vektor obsahující dvojice klíč+hodnota. Tentokrát ovšem budeme požadovat jak rozdělení na zmíněné dvojice klíč+hodnota, tak i vrácení hodnot v mapě podle toho, o jaký datový typ se jedná. Zde využijeme poslední prozatím nepopsaný operátor alt pro výběr jedné z možností:
user=> (spec/def ::configuration (spec/* (spec/cat :name string? :value (spec/alt :str string? :bool boolean? :integer int?)))) :user/configuration
Nejprve se podívejme, zda se data skutečně validují:
user=> (spec/valid? ::configuration ["debug" true "url" "localhost" "port" 8080]) true user=> (spec/valid? ::configuration ["debug" true "url" "localhost" "port" 8080.1]) false
Zobrazení důvodu, proč data nebyla zvalidována (zde konkrétně kvůli použití reálného čísla):
user=> (spec/explain ::configuration ["debug" true "url" "localhost" "port" 8080.1]) In: [5] val: 8080.1 fails spec: :user/configuration at: [:value :str] predicate: string? In: [5] val: 8080.1 fails spec: :user/configuration at: [:value :bool] predicate: boolean? In: [5] val: 8080.1 fails spec: :user/configuration at: [:value :integer] predicate: int? nil
Ovšem nejzajímavější je vlastní destructuring:
user=> (spec/conform ::configuration ["debug" true "url" "localhost" "port" 8080]) [{:name "debug", :value [:bool true]} {:name "url", :value [:str "localhost"]} {:name "port", :value [:integer 8080]}]
Čitelnější bude použití pretty-printingu, takže opět využijeme threading makro:
user=> (-> (spec/conform ::configuration ["debug" true "url" "localhost" "port" 8080]) clojure.pprint/pprint) [{:name "debug", :value [:bool true]} {:name "url", :value [:str "localhost"]} {:name "port", :value [:integer 8080]}] nil
Vidíme, že se destructuring skutečně provedl, a to rekurzivně:
- Původní vektor se rozdělil na dvojice klíč+hodnota
- Hodnota se vrací ve formě vektoru, v němž se nachází opět klíč (typ) a hodnota daného typu
Podobně je možné provést automatický destructuring jakkoli složité datové struktury, samozřejmě za předpokladu, že došlo k její validaci.
17. Generování testovacích dat na základě validačních kritérií
Jednou z nejzajímavějších a potenciálně i nejužitečnějších vlastností knihovny spec je její schopnost kooperace s knihovnou test.check při generování testovacích dat. Nejprve si však musíme upravit projektový soubor project.clj tak, aby obsahoval zvýrazněný řádek (pozor – z předchozího řádku odstraňte poslední dvě uzavírací závorky):
(defproject spec-demo12 "0.1.0-SNAPSHOT" :description "FIXME: write description" :url "http://example.com/FIXME" :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"} :dependencies [[org.clojure/clojure "1.9.0"]] :main ^:skip-aot spec-demo12.core :target-path "target/%s" :profiles {:uberjar {:aot :all} :dev {:dependencies [[org.clojure/test.check "0.9.0"]]}})
Dále je nutné načíst knihovnu clojure.spec.gen.alpha. Přistupovat k ní budeme přes zkrácený jmenný prostor gen:
user=> (require '[clojure.spec.gen.alpha :as gen]) nil
Nejzajímavější je funkce gen/generate, které se předá validační kritérium a výsledkem je náhodná hodnota, jenž ovšem tomuto kritériu odpovídá. Otestujme si to například na vygenerování náhodných celých čísel:
user=> (gen/generate (spec/gen int?)) 506842 user=> (gen/generate (spec/gen int?)) 75 user=> (gen/generate (spec/gen int?)) -1 user=> (gen/generate (spec/gen int?)) -347611
Podobně můžeme vygenerovat náhodné komentáře, a to včetně nil (protože jsou nilable):
user=> (spec/def ::comment (spec/nilable string?)) :user/comment user=> (gen/generate (spec/gen ::comment)) "2MT4fYI7qDn4p" user=> (gen/generate (spec/gen ::comment)) nil user=> (gen/generate (spec/gen ::comment)) "Pb"
Podobně lze postupovat i u složitějších struktur, například u našich IPv4 adres:
user=> (spec/def ::ip-address-2 (spec/cat :1st ::byte :2nd ::byte :3rd ::byte :4th ::byte)) :user/ip-address-2 user=> (gen/generate (spec/gen ::ip-address-2)) (118 4 30 127) user=> (gen/generate (spec/gen ::ip-address-2)) (5 123 1 0) user=> (gen/generate (spec/gen ::ip-address-2)) (193 1 1 13) user=> (gen/generate (spec/gen ::ip-address-2)) (96 1 5 34) user=> (gen/generate (spec/gen ::ip-address-2)) (71 102 48 0)
Vše nejlépe osvětlí poslední demonstrační příklad:
(ns spec-demo12.core) (require '[clojure.pprint :as pprint]) (require '[clojure.spec.alpha :as spec]) (require '[clojure.spec.gen.alpha :as gen]) (spec/def ::user-id (spec/and pos-int? #(>= % 1000) #(< % Integer/MAX_VALUE))) (spec/def ::unique-id (spec/or :name string? :id pos-int?)) (spec/def ::comment-ver1 (spec/or :filled string? :empty nil?)) (spec/def ::comment-ver2 (spec/nilable string?)) (spec/def ::byte (spec/and nat-int? #(<= % 255))) (spec/def ::ip-address (spec/tuple ::byte ::byte ::byte ::byte)) (defn generate-test-data [validation-key] (println "validation-key" validation-key) (doseq [i (range 10)] (println (str "#" i ": ") (gen/generate (spec/gen validation-key)))) (println "\n\n\n")) (defn -main [& args] (let [validation-keys [::user-id ::unique-id ::comment-ver1 ::comment-ver2 ::ip-address]] (doseq [validation-key validation-keys] (generate-test-data validation-key))))
Tento příklad v mém případě vygeneroval následující testovací data:
validation-key :spec-demo12.core/user-id #0: 59033803 #1: 13394 #2: 104186169 #3: 16706747 #4: 440193707 #5: 116487 #6: 905027 #7: 464669 #8: 40352 #9: 1460 validation-key :spec-demo12.core/unique-id #0: kSo4dm0Fjn1kvl0n0lVL2 #1: B2q3ThZ442L3sj4U4c #2: oB11ShtmWj #3: qiUkMcyg027V7BQ5KR9455h #4: #5: 411640 #6: 98602 #7: k #8: 8OJpvma #9: oqG09AJKJGyxEHwgyhA9kjeGAql validation-key :spec-demo12.core/comment-ver1 #0: oLeX40PKWrG #1: 52ts09TxOik8fO2Y7vAYUT #2: Rfoac85p #3: nil #4: nil #5: nil #6: 1bVu1XG #7: W1y8g7MuV1w #8: nil #9: ZNCz3hu1G70H3BE5owGae97cpk validation-key :spec-demo12.core/comment-ver2 #0: 8sMXcl39lWDg642dtF7dV37sIb800 #1: T951gGeS3pD5KT1 #2: jp0K54INQ5Pb7kf948xE6XixsqNwH8 #3: VhcT24P090mxF06Rq #4: tX14hRwPZw2T6RjjlOb19F01 #5: nil #6: 9RoXd2H25o09u #7: KvqXN4NY9Mp9KF6 #8: 8AAmSjz9SjvcQI3r6HfDneoFbVNg #9: dMpIW9By4c2vjR8mfK0 validation-key :spec-demo12.core/ip-address #0: [73 210 0 2] #1: [46 1 1 1] #2: [37 1 127 20] #3: [3 20 6 21] #4: [181 0 1 1] #5: [1 1 91 10] #6: [12 83 28 179] #7: [184 126 2 28] #8: [12 171 26 14] #9: [101 26 3 28]
18. Repositář s demonstračními příklady
Všechny demonstrační příklady a projekty určené pro Clojure verze 1.9.0 byly uloženy do repositáře https://github.com/tisnik/clojure-examples:
Projekt | Popis | Odkaz |
---|---|---|
spec-demo6 | ukázka použití klauzule and | https://github.com/tisnik/clojure-examples/tree/master/spec-demo6 |
spec-demo7 | ukázka použití klauzule or | https://github.com/tisnik/clojure-examples/tree/master/spec-demo7 |
spec-demo8 | nilable řetězec, první varianta | https://github.com/tisnik/clojure-examples/tree/master/spec-demo8 |
spec-demo9 | nilable řetězec, druhá varianta | https://github.com/tisnik/clojure-examples/tree/master/spec-demo9 |
spec-demo10 | funkce explain-data | https://github.com/tisnik/clojure-examples/tree/master/spec-demo10 |
spec-demo11 | validace IP adres, dvě varianty | https://github.com/tisnik/clojure-examples/tree/master/spec-demo11 |
spec-demo12 | generování testovacích dat | https://github.com/tisnik/clojure-examples/tree/master/spec-demo12 |
Pro spuštění projektů je vyžadován nainstalovaný správce projektů Leiningen a samozřejmě i Clojure verze 1.9.0. Pokud tato verze Clojure není nainstalována, provede se instalace po prvním příkazu lein deps nebo lein run.
19. Odkazy na předchozí části tohoto seriálu
- Clojure 1: Úvod
http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm/ - Clojure 2: Symboly, kolekce atd.
http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-2-cast/ - Clojure 3: Funkcionální programování
http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-3-cast-funkcionalni-programovani/ - Clojure 4: Kolekce, sekvence a lazy sekvence
http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-4-cast-kolekce-sekvence-a-lazy-sekvence/ - Clojure 5: Sekvence, lazy sekvence a paralelní programy
http://www.root.cz/clanky/clojure-a-bezpecne-aplikace-pro-jvm-sekvence-lazy-sekvence-a-paralelni-programy/ - Clojure 6: Podpora pro paralelní programování
http://www.root.cz/clanky/programovaci-jazyk-clojure-6-futures-nejsou-jen-financni-derivaty/ - Clojure 7: Další funkce pro paralelní programování
http://www.root.cz/clanky/programovaci-jazyk-clojure-7-dalsi-podpurne-prostredky-pro-paralelni-programovani/ - Clojure 8: Identity, stavy, neměnné hodnoty a reference
http://www.root.cz/clanky/programovaci-jazyk-clojure-8-identity-stavy-nemenne-hodnoty-a-referencni-typy/ - Clojure 9: Validátory, pozorovatelé a kooperace s Javou
http://www.root.cz/clanky/programovaci-jazyk-clojure-9-validatory-pozorovatele-a-kooperace-mezi-clojure-a-javou/ - Clojure 10: Kooperace mezi Clojure a Javou
http://www.root.cz/clanky/programovaci-jazyk-clojure-10-kooperace-mezi-clojure-a-javou-pokracovani/ - Clojure 11: Generátorová notace seznamu/list comprehension
http://www.root.cz/clanky/programovaci-jazyk-clojure-11-generatorova-notace-seznamu-list-comprehension/ - Clojure 12: Překlad programů z Clojure do bajtkódu JVM I:
http://www.root.cz/clanky/programovaci-jazyk-clojure-12-preklad-programu-z-clojure-do-bajtkodu-jvm/ - Clojure 13: Překlad programů z Clojure do bajtkódu JVM II:
http://www.root.cz/clanky/programovaci-jazyk-clojure-13-preklad-programu-z-clojure-do-bajtkodu-jvm-pokracovani/ - Clojure 14: Základy práce se systémem maker
http://www.root.cz/clanky/programovaci-jazyk-clojure-14-zaklady-prace-se-systemem-maker/ - Clojure 15: Tvorba uživatelských maker
http://www.root.cz/clanky/programovaci-jazyk-clojure-15-tvorba-uzivatelskych-maker/ - Clojure 16: Složitější uživatelská makra
http://www.root.cz/clanky/programovaci-jazyk-clojure-16-slozitejsi-uzivatelska-makra/ - Clojure 17: Využití standardních maker v praxi
http://www.root.cz/clanky/programovaci-jazyk-clojure-17-vyuziti-standardnich-maker-v-praxi/ - Clojure 18: Základní techniky optimalizace aplikací
http://www.root.cz/clanky/programovaci-jazyk-clojure-18-zakladni-techniky-optimalizace-aplikaci/ - Clojure 19: Vývojová prostředí pro Clojure
http://www.root.cz/clanky/programovaci-jazyk-clojure-19-vyvojova-prostredi-pro-clojure/ - Clojure 20: Vývojová prostředí pro Clojure (Vimu s REPL)
http://www.root.cz/clanky/programovaci-jazyk-clojure-20-vyvojova-prostredi-pro-clojure-integrace-vimu-s-repl/ - Clojure 21: ClojureScript aneb překlad Clojure do JS
http://www.root.cz/clanky/programovaci-jazyk-clojure-21-clojurescript-aneb-preklad-clojure-do-javascriptu/ - Leiningen: nástroj pro správu projektů napsaných v Clojure
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (2)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-2/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (3)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-3/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (4)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-4/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (5)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-5/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (6)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-6/ - Programovací jazyk Clojure a databáze (1.část)
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-databaze-1-cast/ - Pluginy pro Leiningen
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-pluginy-pro-leiningen/ - Programovací jazyk Clojure a knihovny pro práci s vektory a maticemi
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-knihovny-pro-praci-s-vektory-a-maticemi/ - Programovací jazyk Clojure a knihovny pro práci s vektory a maticemi (2)
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-knihovny-pro-praci-s-vektory-a-maticemi-2/ - Programovací jazyk Clojure: syntéza procedurálních textur s využitím knihovny Clisk
http://www.root.cz/clanky/programovaci-jazyk-clojure-synteza-proceduralnich-textur-s-vyuzitim-knihovny-clisk/ - Programovací jazyk Clojure: syntéza procedurálních textur s využitím knihovny Clisk (2)
http://www.root.cz/clanky/programovaci-jazyk-clojure-synteza-proceduralnich-textur-s-vyuzitim-knihovny-clisk-2/ - Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure
http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure/ - Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure (2)
http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure-2/ - Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure (3)
http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure-3/ - Programovací jazyk Clojure a práce s Gitem
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem/ - Programovací jazyk Clojure: 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/ - Programovací jazyk Clojure a práce s Gitem (2)
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem-2/ - Programovací jazyk Clojure – triky při práci s řetězci
http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-retezci/ - Programovací jazyk Clojure – triky při práci s kolekcemi
http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-kolekcemi/ - Programovací jazyk Clojure – práce s mapami a množinami
http://www.root.cz/clanky/programovaci-jazyk-clojure-prace-s-mapami-a-mnozinami/ - Programovací jazyk Clojure – základy zpracování XML
http://www.root.cz/clanky/programovaci-jazyk-clojure-zaklady-zpracovani-xml/ - Programovací jazyk Clojure – testování s využitím knihovny Expectations
http://www.root.cz/clanky/programovaci-jazyk-clojure-testovani-s-vyuzitim-knihovny-expectations/ - Programovací jazyk Clojure – některé užitečné triky použitelné (nejenom) v testech
http://www.root.cz/clanky/programovaci-jazyk-clojure-nektere-uzitecne-triky-pouzitelne-nejenom-v-testech/ - Enlive – výkonný šablonovací systém pro jazyk Clojure
http://www.root.cz/clanky/enlive-vykonny-sablonovaci-system-pro-jazyk-clojure/ - Nástroj Leiningen a programovací jazyk Clojure: tvorba vlastních knihoven pro veřejný repositář Clojars
http://www.root.cz/clanky/nastroj-leiningen-a-programovaci-jazyk-clojure-tvorba-vlastnich-knihoven-pro-verejny-repositar-clojars/ - Novinky v Clojure verze 1.8.0
http://www.root.cz/clanky/novinky-v-clojure-verze-1–8–0/ - 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/ - 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/
20. Odkazy na Internetu
- Test.check 0.9.0
https://clojure.github.io/test.check/index.html - Knihovna test.check na GitHubu
https://github.com/clojure/test.check - Introduction to test.check
https://clojure.github.io/test.check/intro.html - QuickCheck
https://en.wikipedia.org/wiki/QuickCheck - Originální QuickCheck
http://www.cse.chalmers.se/~rjmh/QuickCheck/ - Hypothesis (QuickCheck pro Python)
https://hypothesis.works/ - Generator Examples
https://clojure.github.io/test.check/generator-examples.html - 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 - Clojure home page
http://clojure.org/ - Clojure (downloads)
http://clojure.org/downloads - Clojure Sequences
http://clojure.org/sequences - Clojure Data Structures
http://clojure.org/data_structures - The Structure and Interpretation of Computer Programs: 2.2.1 Representing Sequences
http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-15.html#%_sec2.2.1 - The Structure and Interpretation of Computer Programs: 3.3.1 Mutable List Structure
http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-22.html#%_sec3.3.1 - Clojure – Functional Programming for the JVM
http://java.ociweb.com/mark/clojure/article.html - Clojure quick reference
http://faustus.webatu.com/clj-quick-ref.html - 4Clojure
http://www.4clojure.com/ - ClojureDoc (rozcestník s dokumentací jazyka Clojure)
http://clojuredocs.org/ - Clojure (na Wikipedia EN)
http://en.wikipedia.org/wiki/Clojure - Clojure (na Wikipedia CS)
http://cs.wikipedia.org/wiki/Clojure - SICP (The Structure and Interpretation of Computer Programs)
http://mitpress.mit.edu/sicp/ - Pure function
http://en.wikipedia.org/wiki/Pure_function - Funkcionální programování
http://cs.wikipedia.org/wiki/Funkcionální_programování - Čistě funkcionální (datové struktury, jazyky, programování)
http://cs.wikipedia.org/wiki/Čistě_funkcionální - Clojure Macro Tutorial (Part I, Getting the Compiler to Write Your Code For You)
http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-i-getting.html - Clojure Macro Tutorial (Part II: The Compiler Strikes Back)
http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-ii-compiler.html - Clojure Macro Tutorial (Part III: Syntax Quote)
http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-ii-syntax.html - Tech behind Tech: Clojure Macros Simplified
http://techbehindtech.com/2010/09/28/clojure-macros-simplified/ - Fatvat – Exploring functional programming: Clojure Macros
http://www.fatvat.co.uk/2009/02/clojure-macros.html - Eulerovo číslo
http://cs.wikipedia.org/wiki/Eulerovo_číslo - List comprehension
http://en.wikipedia.org/wiki/List_comprehension - List Comprehensions in Clojure
http://asymmetrical-view.com/2008/11/18/list-comprehensions-in-clojure.html - Clojure Programming Concepts: List Comprehension
http://en.wikibooks.org/wiki/Clojure_Programming/Concepts#List_Comprehension - Clojure core API: for macro
http://clojure.github.com/clojure/clojure.core-api.html#clojure.core/for - cirrus machina – The Clojure for macro
http://www.cirrusmachina.com/blog/comment/the-clojure-for-macro/ - Riastradh's Lisp Style Rules
http://mumble.net/~campbell/scheme/style.txt - Dynamic Languages Strike Back
http://steve-yegge.blogspot.cz/2008/05/dynamic-languages-strike-back.html - Scripting: Higher Level Programming for the 21st Century
http://www.tcl.tk/doc/scripting.html - Java Virtual Machine Support for Non-Java Languages
http://docs.oracle.com/javase/7/docs/technotes/guides/vm/multiple-language-support.html - Třída java.lang.String
http://docs.oracle.com/javase/7/docs/api/java/lang/String.html - Třída java.lang.StringBuffer
http://docs.oracle.com/javase/7/docs/api/java/lang/StringBuffer.html - Třída java.lang.StringBuilder
http://docs.oracle.com/javase/7/docs/api/java/lang/StringBuilder.html - StringBuffer versus String
http://www.javaworld.com/article/2076072/build-ci-sdlc/stringbuffer-versus-string.html - Threading macro (dokumentace k jazyku Clojure)
https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/-> - Understanding the Clojure → macro
http://blog.fogus.me/2009/09/04/understanding-the-clojure-macro/ - clojure.inspector
http://clojure.github.io/clojure/clojure.inspector-api.html - The Clojure Toolbox
http://www.clojure-toolbox.com/ - Unit Testing in Clojure
http://nakkaya.com/2009/11/18/unit-testing-in-clojure/ - Testing in Clojure (Part-1: Unit testing)
http://blog.knoldus.com/2014/03/22/testing-in-clojure-part-1-unit-testing/ - API for clojure.test – Clojure v1.6 (stable)
https://clojure.github.io/clojure/clojure.test-api.html - Leiningen: úvodní stránka
http://leiningen.org/ - Leiningen: Git repository
https://github.com/technomancy/leiningen - leiningen-win-installer
http://leiningen-win-installer.djpowell.net/ - Clojure.org: Vars and the Global Environment
http://clojure.org/Vars - Clojure.org: Refs and Transactions
http://clojure.org/Refs - Clojure.org: Atoms
http://clojure.org/Atoms - Clojure.org: Agents as Asynchronous Actions
http://clojure.org/agents - Transient Data Structureshttp://clojure.org/transients