Hlavní navigace

Validace dat s využitím knihovny spec v Clojure 1.9.0 (dokončení)

Pavel Tišnovský

Popis možností nabízených knihovnou spec dnes dokončíme. Ukážeme si především velmi užitečnou vlastnost: možnost deklarovat validační kritéria způsobem, který do jisté míry připomíná zápis regulárních výrazů.

Doba čtení: 33 minut

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

14. Kombinace operátorů + a ?

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

20. Odkazy na Internetu

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
Poznámka: předchozí validační kritérium je schválně napsané špatně, protože nebude fungovat například ve chvíli, kdy budeme validovat řetězec atd. Oprava spočívá v tom, že se celý test „obalí“ klauzulí and, v níž se bude testovat predikátem int?.

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ě:

  1. Původní vektor se rozdělil na dvojice klíč+hodnota
  2. 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

  1. Clojure 1: Úvod
    http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm/
  2. Clojure 2: Symboly, kolekce atd.
    http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-2-cast/
  3. Clojure 3: Funkcionální programování
    http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-3-cast-funkcionalni-programovani/
  4. Clojure 4: Kolekce, sekvence a lazy sekvence
    http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-4-cast-kolekce-sekvence-a-lazy-sekvence/
  5. Clojure 5: Sekvence, lazy sekvence a paralelní programy
    http://www.root.cz/clanky/clojure-a-bezpecne-aplikace-pro-jvm-sekvence-lazy-sekvence-a-paralelni-programy/
  6. Clojure 6: Podpora pro paralelní programování
    http://www.root.cz/clanky/programovaci-jazyk-clojure-6-futures-nejsou-jen-financni-derivaty/
  7. Clojure 7: Další funkce pro paralelní programování
    http://www.root.cz/clanky/programovaci-jazyk-clojure-7-dalsi-podpurne-prostredky-pro-paralelni-programovani/
  8. Clojure 8: Identity, stavy, neměnné hodnoty a reference
    http://www.root.cz/clanky/programovaci-jazyk-clojure-8-identity-stavy-nemenne-hodnoty-a-referencni-typy/
  9. Clojure 9: Validátory, pozorovatelé a kooperace s Javou
    http://www.root.cz/clanky/programovaci-jazyk-clojure-9-validatory-pozorovatele-a-kooperace-mezi-clojure-a-javou/
  10. Clojure 10: Kooperace mezi Clojure a Javou
    http://www.root.cz/clanky/programovaci-jazyk-clojure-10-kooperace-mezi-clojure-a-javou-pokracovani/
  11. Clojure 11: Generátorová notace seznamu/list comprehension
    http://www.root.cz/clanky/programovaci-jazyk-clojure-11-generatorova-notace-seznamu-list-comprehension/
  12. Clojure 12: Překlad programů z Clojure do bajtkódu JVM I:
    http://www.root.cz/clanky/programovaci-jazyk-clojure-12-preklad-programu-z-clojure-do-bajtkodu-jvm/
  13. Clojure 13: Překlad programů z Clojure do bajtkódu JVM II:
    http://www.root.cz/clanky/programovaci-jazyk-clojure-13-preklad-programu-z-clojure-do-bajtkodu-jvm-pokracovani/
  14. Clojure 14: Základy práce se systémem maker
    http://www.root.cz/clanky/programovaci-jazyk-clojure-14-zaklady-prace-se-systemem-maker/
  15. Clojure 15: Tvorba uživatelských maker
    http://www.root.cz/clanky/programovaci-jazyk-clojure-15-tvorba-uzivatelskych-maker/
  16. Clojure 16: Složitější uživatelská makra
    http://www.root.cz/clanky/programovaci-jazyk-clojure-16-slozitejsi-uzivatelska-makra/
  17. Clojure 17: Využití standardních maker v praxi
    http://www.root.cz/clanky/programovaci-jazyk-clojure-17-vyuziti-standardnich-maker-v-praxi/
  18. Clojure 18: Základní techniky optimalizace aplikací
    http://www.root.cz/clanky/programovaci-jazyk-clojure-18-zakladni-techniky-optimalizace-aplikaci/
  19. Clojure 19: Vývojová prostředí pro Clojure
    http://www.root.cz/clanky/programovaci-jazyk-clojure-19-vyvojova-prostredi-pro-clojure/
  20. Clojure 20: Vývojová prostředí pro Clojure (Vimu s REPL)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-20-vyvojova-prostredi-pro-clojure-integrace-vimu-s-repl/
  21. Clojure 21: ClojureScript aneb překlad Clojure do JS
    http://www.root.cz/clanky/programovaci-jazyk-clojure-21-clojurescript-aneb-preklad-clojure-do-javascriptu/
  22. Leiningen: nástroj pro správu projektů napsaných v Clojure
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure/
  23. Leiningen: nástroj pro správu projektů napsaných v Clojure (2)
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-2/
  24. Leiningen: nástroj pro správu projektů napsaných v Clojure (3)
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-3/
  25. Leiningen: nástroj pro správu projektů napsaných v Clojure (4)
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-4/
  26. Leiningen: nástroj pro správu projektů napsaných v Clojure (5)
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-5/
  27. Leiningen: nástroj pro správu projektů napsaných v Clojure (6)
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-6/
  28. Programovací jazyk Clojure a databáze (1.část)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-databaze-1-cast/
  29. Pluginy pro Leiningen
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-pluginy-pro-leiningen/
  30. Programovací jazyk Clojure a knihovny pro práci s vektory a maticemi
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-knihovny-pro-praci-s-vektory-a-maticemi/
  31. Programovací jazyk Clojure a knihovny pro práci s vektory a maticemi (2)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-knihovny-pro-praci-s-vektory-a-maticemi-2/
  32. Programovací jazyk Clojure: syntéza procedurálních textur s využitím knihovny Clisk
    http://www.root.cz/clanky/programovaci-jazyk-clojure-synteza-proceduralnich-textur-s-vyuzitim-knihovny-clisk/
  33. Programovací jazyk Clojure: syntéza procedurálních textur s využitím knihovny Clisk (2)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-synteza-proceduralnich-textur-s-vyuzitim-knihovny-clisk-2/
  34. Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure
    http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure/
  35. 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/
  36. 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/
  37. Programovací jazyk Clojure a práce s Gitem
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem/
  38. 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/
  39. Programovací jazyk Clojure a práce s Gitem (2)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem-2/
  40. Programovací jazyk Clojure – triky při práci s řetězci
    http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-retezci/
  41. Programovací jazyk Clojure – triky při práci s kolekcemi
    http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-kolekcemi/
  42. Programovací jazyk Clojure – práce s mapami a množinami
    http://www.root.cz/clanky/programovaci-jazyk-clojure-prace-s-mapami-a-mnozinami/
  43. Programovací jazyk Clojure – základy zpracování XML
    http://www.root.cz/clanky/programovaci-jazyk-clojure-zaklady-zpracovani-xml/
  44. Programovací jazyk Clojure – testování s využitím knihovny Expectations
    http://www.root.cz/clanky/programovaci-jazyk-clojure-testovani-s-vyuzitim-knihovny-expectations/
  45. Programovací jazyk Clojure – některé užitečné triky použitelné (nejenom) v testech
    http://www.root.cz/clanky/programovaci-jazyk-clojure-nektere-uzitecne-triky-pouzitelne-nejenom-v-testech/
  46. Enlive – výkonný šablonovací systém pro jazyk Clojure
    http://www.root.cz/clanky/enlive-vykonny-sablonovaci-system-pro-jazyk-clojure/
  47. Nástroj Leiningen a programovací jazyk Clojure: tvorba vlastních knihoven pro veřejný repositář Clojars
    http://www.root.cz/clanky/nastroj-leiningen-a-programovaci-jazyk-clojure-tvorba-vlastnich-knihoven-pro-verejny-repositar-clojars/
  48. Novinky v Clojure verze 1.8.0
    http://www.root.cz/clanky/novinky-v-clojure-verze-1–8–0/
  49. 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/
  50. 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/
  51. 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/
  52. Vytváříme IRC bota v programovacím jazyce Clojure
    http://www.root.cz/clanky/vytvarime-irc-bota-v-programovacim-jazyce-clojure/
  53. Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
    https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/
  54. Multimetody v Clojure aneb polymorfismus bez použití OOP
    https://www.root.cz/clanky/multimetody-v-clojure-aneb-polymorfismus-bez-pouziti-oop/
  55. 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/
  56. Pixie: lehký skriptovací jazyk s „kouzelnými“ schopnostmi
    https://www.root.cz/clanky/pixie-lehky-skriptovaci-jazyk-s-kouzelnymi-schopnostmi/
  57. Programovací jazyk Pixie: funkce ze základní knihovny a použití FFI
    https://www.root.cz/clanky/pro­gramovaci-jazyk-pixie-funkce-ze-zakladni-knihovny-a-pouziti-ffi/
  58. Novinky v Clojure verze 1.9.0
    https://www.root.cz/clanky/novinky-v-clojure-verze-1–9–0/
  59. Validace dat s využitím knihovny spec v Clojure 1.9.0
    https://www.root.cz/clanky/validace-dat-s-vyuzitim-knihovny-spec-v-clojure-1–9–0/

20. Odkazy na Internetu

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