Hlavní navigace

Validace dat s využitím knihovny spec v Clojure 1.9.0

Pavel Tišnovský

Dnes navážeme na text z minulého týdne, v němž jsme si představili hlavní novinky, které vývojářům přinesla nová verze 1.9.0 jazyka Clojure. Dnes si popíšeme další možnosti nabízené knihovnou spec.

Doba čtení: 41 minut

11. Validace parametrů předávaných funkci, validace návratových hodnot

12. Třetí demonstrační příklad – validace parametrů předávaných funkci

13. Čtvrtý demonstrační příklad – chování funkce ve chvíli, kdy není zvalidována její návratová hodnota

14. Volitelné položky v mapě

15. Pátý demonstrační příklad – validace mapy s volitelnými položkami

16. Validace n-tic, kolekcí a sekvencí

17. Možnosti nabízené makrem coll-of

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

V předchozím článku jsme si popsali hlavní novinky, s nimiž se mohou vývojáři setkat v Clojure verze 1.9.0. Připomeňme si jen, že se jedná o prozatím poslední stabilní verzi tohoto jazyka, protože Clojure 1.10.0 je stále ještě v alfa stavu (nicméně se již zdá být poměrně stabilní). Jedním důležitým vylepšením, s nímž se můžeme v Clojure 1.9.0 setkat, je knihovna nazvaná spec. Tato knihovna slouží k deklaraci struktury dat 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. Jen málokdy se v Clojure setkáme s tím, že by byla datová struktura „zabalena“ do třídy společně s metodami.

Navíc se v Clojure, podobně jako v dalších jazycích, mnohdy velmi intenzivně pracuje se strukturovanými daty reprezentovanými v JSONu; výjimkou nejsou ani situace, kdy k těmto datům není dodáváno JSON schéma. A právě z toho důvodu, že se většinou pracuje s holými (nezabalenými) datovými strukturami, je nutné nějakým způsobem zajišťovat validitu dat. Poměrně často se pro tyto účely používala knihovna Schema (neplést se Scheme :-), ovšem knihovna spec tento problém řeší poněkud odlišným způsobem.

2. Proč je validace dat důležitá a užitečná

Ukažme si jednoduchý příklad, na němž si ukážeme, z jakého důvodu je validace dat důležitá. Představme si, že v aplikaci potřebujeme pracovat s údaji o osobách, například o zaměstnancích. Datová struktura nesoucí informace o jedné osobě se vytvoří triviálně – bude se jednat o obyčejný slovník (asociativní pole), v němž budou klíče představovány hodnotami typu keyword, a to z toho důvodu, že je u těchto hodnot zajištěna jedinečnost:

{:id 10
 :name "Rich"
 :surname "Hickey"}

Poznámka: samozřejmě je možné použít jakoukoli hodnotu pro klíče, například i řetězce. V tomto případě se však bude jednat o zbytečné plýtvání operační pamětí, protože není zaručeno, že každý řetězec (se stejným textem) bude unikátní:

{"id" 10
 "name" "Rich"
 "surname" "Hickey"}

Jak by mohla vypadat funkce akceptující tuto datovou strukturu? V nejjednodušším případě může taková funkce vypadat například následovně. Zde se konkrétně snažíme o uložení struktury do databázové tabulky (funkce je součástí projektu, který naleznete na adrese https://github.com/tisnik/clojure-examples/tree/master/db-store:

(defn store-user-name
    [db-spec user-name]
    (jdbc/insert! db-spec "users" user-name))

Způsob použití této funkce:

(require '[clojure.java.jdbc :as jdbc])
 
; struktura reprezentující způsob připojení do vybrané databáze
(def test-db
    {:classname   "org.sqlite.JDBC"
     :subprotocol "sqlite"
     :subname     "test.db"
    })
 
(store-user-name test-db {:id 1 :name "Rich" :surname "Hickey"})

Databázová tabulka byla vytvořena s následující strukturou:

create table users (
    id        integer primary key,
    name      text not null,
    surname   text not null
);

Zajímavé bude zjistit, co se stane ve chvíli, kdy funkci store-user-name předáme neúplnou datovou struktur, hodnotu jiného typu popř. strukturu, která sice bude obsahovat všechny dvojice klíč-hodnota, ale hodnoty nebudou odpovídat schématu databáze.

Hodnota uložená pod klíčem :id není typu integer:

(store-user-name test-db {:id "XXXXXXXX" :name "Rich" :surname "Hickey"})

Neúplná datová struktura:

(store-user-name test-db {:id 2"})

Namísto mapy se předává odlišná hodnota:

(store-user-name test-db "foobar")

Ve všech těchto případech samozřejmě dojde k chybě, neboť není dodržena struktura vyžadovaná schématem databáze (samotný výpis zásobníkových rámců je dosti nepřehledný, nicméně chybové hlášení je pochopitelné):

Caused by: java.sql.SQLException: [SQLITE_MISMATCH]  Data type mismatch (datatype mismatch)
    at org.sqlite.DB.newSQLException(DB.java:383)
    at org.sqlite.DB.newSQLException(DB.java:387)
    at org.sqlite.DB.execute(DB.java:342)
    at org.sqlite.DB.executeUpdate(DB.java:363)
    at org.sqlite.PrepStmt.executeUpdate(PrepStmt.java:85)
    at clojure.java.jdbc$db_do_prepared_return_keys$exec_and_return_keys__497.invoke(jdbc.clj:692)
    at clojure.java.jdbc$db_do_prepared_return_keys.invokeStatic(jdbc.clj:707)
    at clojure.java.jdbc$db_do_prepared_return_keys.invoke(jdbc.clj:679)
    at clojure.java.jdbc$multi_insert_helper$fn__556.invoke(jdbc.clj:897)
    at clojure.core$map$fn__5587.invoke(core.clj:2747)
    at clojure.lang.LazySeq.sval(LazySeq.java:40)
    at clojure.lang.LazySeq.seq(LazySeq.java:49)
    at clojure.lang.RT.seq(RT.java:528)
    at clojure.core$seq__5124.invokeStatic(core.clj:137)
    at clojure.core$dorun.invokeStatic(core.clj:3125)
    at clojure.core$doall.invokeStatic(core.clj:3140)
    at clojure.core$doall.invoke(core.clj:3140)
    at clojure.java.jdbc$multi_insert_helper.invokeStatic(jdbc.clj:896)
    at clojure.java.jdbc$multi_insert_helper.invoke(jdbc.clj:891)
    at clojure.java.jdbc$insert_helper$fn__559.invoke(jdbc.clj:907)
    at clojure.java.jdbc$db_transaction_STAR_.invokeStatic(jdbc.clj:580)
    at clojure.java.jdbc$db_transaction_STAR_.doInvoke(jdbc.clj:553)
    at clojure.lang.RestFn.invoke(RestFn.java:425)
    at clojure.java.jdbc$insert_helper.invokeStatic(jdbc.clj:907)
    at clojure.java.jdbc$insert_helper.invoke(jdbc.clj:900)
    at clojure.java.jdbc$insert_BANG_.invokeStatic(jdbc.clj:999)
    at clojure.java.jdbc$insert_BANG_.doInvoke(jdbc.clj:984)
    at clojure.lang.RestFn.invoke(RestFn.java:442)
    at db_store.core$store_user_name.invokeStatic(core.clj:14)
    at db_store.core$store_user_name.invoke(core.clj:12)
    at db_store.core$_main.invokeStatic(core.clj:20)
    at db_store.core$_main.doInvoke(core.clj:16)
    at clojure.lang.RestFn.invoke(RestFn.java:397)
    at clojure.lang.Var.invoke(Var.java:377)
    at user$eval139.invokeStatic(form-init5353435627860668704.clj:1)
    at user$eval139.invoke(form-init5353435627860668704.clj:1)
    at clojure.lang.Compiler.eval(Compiler.java:7062)
    at clojure.lang.Compiler.eval(Compiler.java:7052)
    at clojure.lang.Compiler.load(Compiler.java:7514)
    ... 12 more

3. Kontrola dat přímo v těle funkce

Samozřejmě je možné kontrolu dat provádět přímo ve volané funkci, což je pravděpodobně první možnost, která programátora napadne. Ukažme si to na typickém „školním“ příkladu. Bude se jednat o funkci pro výpočet faktoriálu, kterou je možné naprogramovat například tak, že při zadání záporného čísla vyhodí výjimku:

(defn factorial
    [n]
    (if (neg? n)
        (throw (IllegalArgumentException. "negative numbers are not supported!"))
        (apply * (range 1 (inc n)))))

Otestování chování je snadné:

user=> (factorial 3)
6
 
user=> (factorial 2)
2
 
user=> (factorial 1)
1
 
user=> (factorial 0)
1
 
user=> (factorial -1)
 
IllegalArgumentException negative numbers are not supported!  user/factorial (NO_SOURCE_FILE:3)
user=>

Jednoduchý test však nezabrání chybám při předání hodnot odlišného typu (zde nepomůže ani type hint):

clojure9-test.core=> (factorial :x)
 
ClassCastException clojure.lang.Keyword cannot be cast to java.lang.Number  clojure.lang.Numbers.isNeg (Numbers.java:100)
clojure9-test.core=> (factorial nil)
 
NullPointerException   clojure.lang.Numbers.ops (Numbers.java:1018)
clojure9-test.core=> (factorial "42")
 
ClassCastException java.lang.String cannot be cast to java.lang.Number  clojure.lang.Numbers.isNeg (Numbers.java:100)

Další explicitně naprogramovaná kontrol vstupních parametrů vede k již dosti nečitelnému kódu (příklad je ovšem možné napsat i jinak, například s využitím nového predikátu nat-int?):

(defn factorial
    [n]
    (if (int? n)
        (if (neg? n)
            (throw (IllegalArgumentException. "negative numbers are not supported!"))
            (apply * (range 1 (inc n))))
        (throw (IllegalArgumentException. "only integers are accepted!"))))

Otestování funkčnosti:

clojure9-test.core=> (factorial 10)
3628800
clojure9-test.core=> (factorial -1)
 
IllegalArgumentException negative numbers are not supported!  clojure9-test.core/factorial (form-init2633858370945804905.clj:4)
clojure9-test.core=> (factorial :x)
 
IllegalArgumentException only integers are accepted!  clojure9-test.core/factorial (form-init2633858370945804905.clj:6)
clojure9-test.core=> (factorial nil)
 
IllegalArgumentException only integers are accepted!  clojure9-test.core/factorial (form-init2633858370945804905.clj:6)

4. Kontrola dat využívající vstupní podmínky (pre-condition)

Výše uvedený kód sice můžeme poměrně často vidět v mnoha knihovnách, ovšem ve skutečnosti není pro programovací jazyk Clojure příliš idiomatický. Je tomu tak z toho důvodu, že jazyk Clojure umožňuje ve funkci deklarovat vstupní podmínky (pre-condition) a dokonce i podmínky výstupní (post-condition). Vstupní podmínky jsou pochopitelně kontrolovány při vstupu do funkce, podmínky výstupní pak automaticky ve všech místech, kde funkce předává návratovou hodnotu volajícímu kódu (tj. těsně před implicitně vkládanou instrukcí RETURN). Nejprve se opět podívejme na klasický „školní“ příklad použití vstupní podmínky u funkce factorial, jejíž parametr musí být přirozené číslo:

(defn factorial
    [n]
    {:pre [(pos? n)]}
    (apply * (range 1 (inc n))))

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

user=> (factorial 3)
6
user=> (factorial 2)
2
user=> (factorial 1)
1

Neočekávané hodnoty (správného typu):

user=> (factorial 0)
 
AssertionError Assert failed: (pos? n)  user/factorial (NO_SOURCE_FILE:1)
user=> (factorial -1)
 
AssertionError Assert failed: (pos? n)  user/factorial (NO_SOURCE_FILE:1)

Nejzajímavější jsou samozřejmě poslední dva případy, protože v nich je ukázáno, že se kontrola hodnoty vstupního parametru skutečně provádí.

S existencí nových predikátů přidaných do Clojure 1.9.0 navíc můžeme předchozí příklad nepatrně upravit tak, aby akceptoval i nulu (ve skutečnosti samozřejmě nemusíme nutně použít nový predikát, je to však elegantnější):

(defn factorial
    [n]
    {:pre [(nat-int? n)]}
    (apply * (range 1 (inc n))))

Test při předání celých čísel:

clojure9-test.core=> (factorial -1)
 
AssertionError Assert failed: (nat-int? n)  clojure9-test.core/factorial (form-init3895554752445190478.clj:1)
 
clojure9-test.core=> (factorial 0)
1
 
clojure9-test.core=> (factorial 1)
1
 
clojure9-test.core=> (factorial 2)
2
 
clojure9-test.core=> (factorial 10)
3628800

Špatný typ hodnot:

clojure9-test.core=> (factorial nil)
 
AssertionError Assert failed: (nat-int? n)  clojure9-test.core/factorial (form-init8084795436103962898.clj:1)
clojure9-test.core=> (factorial "foobar")
 
AssertionError Assert failed: (nat-int? n)  clojure9-test.core/factorial (form-init8084795436103962898.clj:1)
clojure9-test.core=> (factorial :42)
 
AssertionError Assert failed: (nat-int? n)  clojure9-test.core/factorial (form-init8084795436103962898.clj:1)

5. Deklarace validátorů pro složitější datovou strukturu (mapu)

Vraťme se nyní k datové struktuře nesoucí informace o jedné osobě. Nejprve knihovnu spec načteme do interaktivní smyčky REPL:

user=> (require '[clojure.spec.alpha :as spec])
nil

Následně můžeme deklarovat kontrolu na existenci klíčů v datové struktuře. Použijeme přitom již minule popsané makro spec/def a taktéž makro spec/keys, kterému se v nejjednodušším případě předá vektor klíčů. Klíče přitom musí být buď plně kvalifikovány (ve formátu :jmenný-prostor/klíč) nebo lze použít zápis ::klíč pro klíče platné v aktuálně platném jmenném prostoru (taktéž se jedná o novinku v Clojure 1.9.0):

user=> (spec/def ::person? (spec/keys :req [::id ::name ::surname]))
:user/person?

Vyzkoušejme si nyní, jak kontrola probíhá:

user=> (spec/valid? ::person? {:id 1 :name "Name" :surname "Surname"})
false
 
user=> (spec/valid? ::person? {::id 1 ::name "Name" ::surname "Surname"})
true
 
user=> (spec/valid? ::person? {::name "Name" ::surname "Surname"})
false

Výsledky jsou možná překvapivé, protože validní je pouze mapa obsahující klíče v aktuálním jmenném prostoru, před nimiž se zapisuje :: („čtyřtečka“). Ve skutečnosti však bude validní i mapa s plně kvalifikovanými klíči:

user=> (spec/valid? ::person? {:user/id 1 :user/name "Name" :user/surname "Surname"})
true

Pro klíče z jiného prostoru to však neplatí:

user=> (spec/valid? ::person? {:test/id 1 :test/name "Name" :test/surname "Surname"})
false

Použití plně kvalifikovaných klíčů je sice doporučováno, ovšem v praxi se mnohem častěji setkáme s datovými strukturami, v nichž jsou použity klíče „obyčejné“, tj. bez jmenného prostoru. Pokud budeme chtít validovat struktury s těmito klíči (a to většinou skutečně budeme chtít), musí se namísto klauzule :req použít klauzule :req-un. Namísto:

user=> (spec/def ::person? (spec/keys :req [::id ::name ::surname]))
:user/person?

Použijeme definici:

user=> (spec/def ::person? (spec/keys :req-un [::id ::name ::surname]))
:user/person?

Nyní se můžeme pokusit zvalidovat několik datových struktur:

user=> (spec/valid? ::person? {::id -10 ::name "Name" ::surname "Surname"})
false
user=> (spec/valid? ::person? {::id 10 ::name "Name" ::surname "Surname"})
false
user=> (spec/valid? ::person? {:id -10 :name "Name" :surname "Surname"})
true
user=> (spec/valid? ::person? {:id 10 :name "Name" :surname "Surname"})
true

U map samozřejmě nezáleží na pořadí dvojic klíč-hodnota:

user=> (spec/valid? ::person? {:name "Name" :surname "Surname" :id 10})
true

6. První demonstrační příklad – jednoduchá validace mapy, test existence všech klíčů

V dnešním prvním demonstračním příkladu, jehož úplnou strukturu naleznete na adrese https://github.com/tisnik/clojure-examples/tree/master/spec-demo1, je ukázán průběh základní validace s využitím plně kvalifikovaných klíčů popř. klíčů platných pro aktuální jmenný prostor. Povšimněte si, že namísto jmenného prostoru user musíme nyní použít jmenný prostor odpovídající názvu demonstračního příkladu:

(ns spec-demo1.core)
 
(require '[clojure.spec.alpha :as spec])
 
(defn -main
    [& args]
    (spec/def ::person? (spec/keys :req [::id ::name ::surname]))
 
    (println "valid?")
    (println "--------------------------------------------------")
    (println (spec/valid? ::person? {:id 1 :name "Name" :surname "Surname"}))
    (println (spec/valid? ::person? {::name "Name" ::surname "Surname"}))
    (println (spec/valid? ::person? {::id 1 ::name "Name" ::surname "Surname"}))
    (println (spec/valid? ::person? {:user/id 1 :user/name "Name" :user/surname "Surname"}))
    (println (spec/valid? ::person? {:spec-demo1.core/id 1 :spec-demo1.core/name "Name" :spec-demo1.core/surname "Surname"}))
    (println (spec/valid? ::person? {:other.namespace/id 1 :other.namespace/name "Name" :other.namespace/surname "Surname"}))
 
    (println "\nexplain")
    (println "--------------------------------------------------")
    (println (spec/explain ::person? {:id 1 :name "Name" :surname "Surname"}))
    (println)
    (println (spec/explain ::person? {::name "Name" ::surname "Surname"}))
    (println)
    (println (spec/explain ::person? {::id 1 ::name "Name" ::surname "Surname"}))
    (println)
    (println (spec/explain ::person? {:user/id 1 :user/name "Name" :user/surname "Surname"}))
    (println)
    (println (spec/explain ::person? {:spec-demo1.core/id 1 :spec-demo1.core/name "Name" :spec-demo1.core/surname "Surname"}))
    (println)
    (println (spec/explain ::person? {:other.namespace/id 1 :other.namespace/name "Name" :other.namespace/surname "Surname"})))

Po spuštění tohoto příkladu by se na standardním výstupu měly objevit přesně tyto řádky:

valid?
--------------------------------------------------
false
false
true
false
true
false
 
explain
--------------------------------------------------
val: {:id 1, :name "Name", :surname "Surname"} fails spec: :spec-demo1.core/person? predicate: (contains? % :spec-demo1.core/id)
val: {:id 1, :name "Name", :surname "Surname"} fails spec: :spec-demo1.core/person? predicate: (contains? % :spec-demo1.core/name)
val: {:id 1, :name "Name", :surname "Surname"} fails spec: :spec-demo1.core/person? predicate: (contains? % :spec-demo1.core/surname)
nil
 
val: #:spec-demo1.core{:name "Name", :surname "Surname"} fails spec: :spec-demo1.core/person? predicate: (contains? % :spec-demo1.core/id)
nil
 
Success!
nil
 
val: #:user{:id 1, :name "Name", :surname "Surname"} fails spec: :spec-demo1.core/person? predicate: (contains? % :spec-demo1.core/id)
val: #:user{:id 1, :name "Name", :surname "Surname"} fails spec: :spec-demo1.core/person? predicate: (contains? % :spec-demo1.core/name)
val: #:user{:id 1, :name "Name", :surname "Surname"} fails spec: :spec-demo1.core/person? predicate: (contains? % :spec-demo1.core/surname)
nil
 
Success!
nil
 
val: #:other.namespace{:id 1, :name "Name", :surname "Surname"} fails spec: :spec-demo1.core/person? predicate: (contains? % :spec-demo1.core/id)
val: #:other.namespace{:id 1, :name "Name", :surname "Surname"} fails spec: :spec-demo1.core/person? predicate: (contains? % :spec-demo1.core/name)
val: #:other.namespace{:id 1, :name "Name", :surname "Surname"} fails spec: :spec-demo1.core/person? predicate: (contains? % :spec-demo1.core/surname)
nil

7. Kontrola hodnot uložených v mapě

Kontrolu existence klíčů již máme vyřešenou, ovšem samozřejmě nám zbývá zkontrolovat i hodnoty, aby například nebylo možné pod :id uložit řetězec a pod :name prázdné jméno, číslo nebo vektor. Pro jistotu si znovu zopakujme první validační kritérium, tj. kontrolu na existenci klíčů v mapě:

user=> (spec/def ::person? (spec/keys :req [::id ::name ::surname]))
:user/person?

V některých dalších knihovnách určených pro validaci dat by nyní následovala specifikace, jak má vypadat hodnota uložená pod klíčem ::id, ::name atd. V knihovně spec se však používá odlišný postup, protože specifikujeme validátor přímo pojmenovaný po klíči, tj. uvedený zcela mimo vlastní mapu. Například můžeme specifikovat validátor pro hodnotu uloženou pod klíčem ::id:

user=> (spec/def ::id pos-int?)
:user/id?

Validaci lze provést pro jednotlivé hodnoty, čímž si současně ověříme, zda je test korektní:

user=> (spec/valid? ::id 10)
true
user=> (spec/valid? ::id -10)
false

Navíc se validace začne „magicky“ provádět i pro hodnoty uložené v mapě:

user=> (spec/valid? ::person? {::id 10 ::name "Name" ::surname "Surname"})
true
 
user=> (spec/valid? ::person? {::id -10 ::name "Name" ::surname "Surname"})
false

Samozřejmě nejsme omezeni pouze na mapy s plně kvalifikovanými klíči. Zkusme si nyní napsat pravidla pro validaci map s klíči bez uvedení jmenného prostoru. Postup je shodný, pouze se namísto klauzule :req použije nám již známá klauzule :req.un:

user=> (spec/def ::person? (spec/keys :req-un [::id ::name ::surname]))
:user/person?
 
user=> (spec/def ::id pos-int?)
:user/id

Následně otestujeme validaci pro testovací mapy:

user=> (spec/valid? ::person? {:id 10 :name "Name" :surname "Surname"})
true
 
user=> (spec/valid? ::person? {:id -10 :name "Name" :surname "Surname"})
false
 
user=> (spec/valid? ::person? {:id 0 :name "Name" :surname "Surname"})
false
 
user=> (spec/valid? ::person? {:id "foobar" :name "Name" :surname "Surname"})
false

8. Validace jména a příjmení

Kritéria samozřejmě můžeme rozšířit a kromě testu hodnot uložených pod klíčem :id budeme ověřovat i to, zda je zadáno korektní jméno a příjmení. Pro zápis nových predikátů se většinou kvůli sevřenějšímu zápisu používají anonymní funkce, ovšem pokud teprve jednotlivé predikáty ladíme, je lepší použít běžné (pojmenované) funkce. Příkladem může být funkce pro test, zda je zadáno jméno začínající velkým písmenem a pokračující písmeny malými. V tomto případě použijeme běžný regulární výraz:

user=> (defn name? [s] (and (string? s) (re-matches #"[A-Z][a-z]+" s)))
#'user/name?

Otestování nového predikátu je snadné:

user=> (name? 42)
false
 
user=> (name? "")
nil
 
user=> (name? "hello")
nil
 
user=> (name? "Pavel")
"Pavel"
 
user=> (name? "P")
nil

Pokud budete chtít být precizní a skutečně vracet jen hodnoty true nebo false (což ovšem není striktně vyžadováno), je možné predikát nepatrně upravit:

user=> (defn name? [s] (boolean (and (string? s) (re-matches #"[A-Z][a-z]+" s))))
#'user/name?

Nyní se jedná o funkci, která vždy vrátí pravdivostní hodnotu:

user=> (name? "")
false
 
user=> (name? "pavel")
false
 
user=> (name? "Pavel")
true
 
user=> (name? 42)
false
 
user=> (name? nil)
false

Podobně můžeme vytvořit i funkci pro kontrolu příjmení, kde ovšem povolíme i mezery a víceslovná příjmení:

user=> (defn surname? [s] (and (string? s) (re-matches #"[A-Z][A-Za-z ]+" s)))
#'user/surname?

Otestování:

user=> (surname? "Hickey")
"Hickey"
 
user=> (surname? "De Boor")
"De Boor"
 
user=> (surname? "foobar")
nil
 
user=> (surname? 42)
false
 
user=> (surname? "")
nil

Úprava na striktní predikát:

user=> (defn surname? [s] (boolean (and (string? s) (re-matches #"[A-Z][A-Za-z ]+" s))))
#'user/surname?

Otestování:

user=> (surname? "Hickey")
true
 
user=> (surname? "De Boor")
true
 
user=> (surname? "foobar")
false
 
user=> (surname? 42)
false
 
user=> (surname? "")
false

Použití predikátů pro validaci jména a příjmení je už jen záležitostí dvou řádků:

user=> (spec/def ::name name?)
:user/name
 
user=> (spec/def ::surname surname?)
:user/surname

9. Validace jména a příjmení v mapě

Nyní již můžeme všechny kontroly provést nad různými testovacími mapami (nebo nad jinými strukturami).

Specifikace validačních kritérií:

clojure9-test.core=> (ns user)
nil
 
user=> (require '[clojure.spec.alpha :as spec])
nil
 
user=> (spec/def ::person? (spec/keys :req-un [::id ::name ::surname]))
:user/person?
 
user=> (defn name? [s] (boolean (and (string? s) (re-matches #"[A-Z][a-z]+" s))))
#'user/name?
 
user=> (defn surname? [s] (boolean (and (string? s) (re-matches #"[A-Z][A-Za-z ]+" s))))
#'user/surname?
 
user=> (spec/def ::id pos-int?)
:user/id
 
user=> (spec/def ::name name?)
:user/name
 
user=> (spec/def ::surname surname?)
:user/surname

Otestování, zda validace probíhá tak, jak předpokládáme:

user=> (spec/valid? ::person? nil)
false
 
user=> (spec/valid? ::person? "")
false
 
user=> (spec/valid? ::person? [])
false
 
user=> (spec/valid? ::person? {:id 10 :name "Rich" :surname "Hickey"})
true
 
user=> (spec/valid? ::person? {:id 10 :name "Carl" :surname "De Boor"})
true
 
user=> (spec/valid? ::person? {:id 0 :name "carl" :surname "De Boor"})
false
 
user=> (spec/valid? ::person? {:id 10 :name "carl" :surname "De Boor"})
false

Zjištění, proč nebyla data validována:

user=> (spec/explain ::person? {:id 10 :name "Carl" :surname "De Boor"})
Success!
nil
 
user=> (spec/explain ::person? {:id -10 :name "Carl" :surname "De Boor"})
In: [:id] val: -10 fails spec: :user/id at: [:id] predicate: pos-int?
nil
 
user=> (spec/explain ::person? {:id -10 :name "Carl"})
In: [:id] val: -10 fails spec: :user/id at: [:id] predicate: pos-int?
val: {:id -10, :name "Carl"} fails spec: :user/person? predicate: (contains? % :surname)
nil
 
user=> (spec/explain ::person? "")
val: "" fails spec: :user/person? predicate: map?
nil
 
user=> (spec/explain ::person? [])
val: [] fails spec: :user/person? predicate: map?
nil
 
user=> (spec/explain ::person? nil)
val: nil fails spec: :user/person? predicate: map?
nil

10. Druhý demonstrační příklad – kontrola hodnot uložených v mapě

Ve druhém demonstračním příkladu je ukázán způsob validace mapy, v níž se kontroluje jak existence jednotlivých klíčů, tak i hodnot, které jsou na klíče navázány. Identifikátor musí být celé kladné číslo, jméno musí odpovídat zadanému regulárnímu výrazu (první velké písmeno, další písmena malá) a příjmení taktéž musí odpovídat zadanému regulárnímu výrazu. Pro lepší čitelnost jsou všechny nové predikáty rozepsány do neanonymních funkcí:

(ns spec-demo2.core)
 
(require '[clojure.spec.alpha :as spec])
 
(defn name?
    [s]
    (and (string? s) (re-matches #"[A-Z][a-z]+" s)))
 
(defn surname?
    [s]
    (and (string? s) (re-matches #"[A-Z][A-Za-z ]+" s)))
 
(defn -main
    [& args]
    (spec/def ::id pos-int?)
    (spec/def ::name name?)
    (spec/def ::surname surname?)
    (spec/def ::person? (spec/keys :req-un [::id ::name ::surname]))
 
    (println "valid?")
    (println "--------------------------------------------------")
    (println (spec/valid? ::person? {:id 10 :name "Rich" :surname "Hickey"}))
    (println (spec/valid? ::person? {:id 10 :name "rich" :surname "Hickey"}))
    (println (spec/valid? ::person? {:id 10 :name "Rich" :surname "hickey"}))
    (println (spec/valid? ::person? {:id -10 :name "Rich" :surname "Hickey"}))
    (println (spec/valid? ::person? {:id -10 :name "rich" :surname "hickey"}))
 
    (println "\nexplain")
    (println "--------------------------------------------------")
    (println (spec/explain ::person? {:id 10 :name "Rich" :surname "Hickey"}))
    (println)
    (println (spec/explain ::person? {:id 10 :name "rich" :surname "Hickey"}))
    (println)
    (println (spec/explain ::person? {:id 10 :name "Rich" :surname "hickey"}))
    (println)
    (println (spec/explain ::person? {:id -10 :name "Rich" :surname "Hickey"}))
    (println)
    (println (spec/explain ::person? {:id -10 :name "rich" :surname "hickey"}))
    (println))
valid?
--------------------------------------------------
true
false
false
false
false
 
explain
--------------------------------------------------
Success!
nil
 
In: [:name] val: "rich" fails spec: :spec-demo2.core/name at: [:name] predicate: name?
nil
 
In: [:surname] val: "hickey" fails spec: :spec-demo2.core/surname at: [:surname] predicate: surname?
nil
 
In: [:id] val: -10 fails spec: :spec-demo2.core/id at: [:id] predicate: pos-int?
nil
 
In: [:id] val: -10 fails spec: :spec-demo2.core/id at: [:id] predicate: pos-int?
In: [:name] val: "rich" fails spec: :spec-demo2.core/name at: [:name] predicate: name?
In: [:surname] val: "hickey" fails spec: :spec-demo2.core/surname at: [:surname] predicate: surname?
nil

11. Validace parametrů předávaných funkci, validace návratových hodnot

Jakmile máme v centrálním registru uložené validační kritérium ::person?, můžeme si k němu zobrazit i automaticky vygenerovanou nápovědu:

user=> (doc ::person?)
-------------------------
:user/person?
Spec
  (keys :req-un [:user/id :user/name :user/surname])
nil

Validaci je samozřejmě možné provést i u volaných funkcí. Mějme například funkci vracející jméno osoby získané z mapy (takovou funkci samozřejmě není zapotřebí programovat, takže se jedná jen o příklad):

(defn get-name
    [person]
    (:name person))

Pro validní mapy bude tato funkce vracet i očekávané výsledky:

user=> (spec/explain ::person? {:id 10 :name "Carl" :surname "De Boor"})
Success!
nil
 
user=> (get-name {:id 10 :name "Carl" :surname "De Boor"})
"Carl"

Ovšem validátor lze použít přímo ve funkci. Můžeme zvalidovat jak vstupní parametr (mapu), tak i výsledek funkce, a to například následovně:

(defn checked-get-name
    [person]
    {:pre [(spec/valid? ::person? person)]
     :post [(spec/valid? ::name %)]}
    (:name person))

Chování si opět můžeme snadno otestovat:

user=> (checked-get-name {:id 10 :name "Carl" :surname "De Boor"})
"Carl"
 
user=> (checked-get-name {:id 10 :name "carl" :surname "De Boor"})
AssertionError Assert failed: (spec/valid? :user/person? person)  user/checked-get-name (NO_SOURCE_FILE:27)

Pokud například programátor v budoucnu změní způsob generování návratové hodnoty (přidá prefix „Name: “, což je opět jednoduchý příklad; v praxi půjde nejspíše o skutečnou chybu), bude na to ihned upozorněn a bude muset buď funkci opravit nebo změnit validační kritérium:

(defn checked-get-name
    [person]
    {:pre [(spec/valid? ::person? person)]
     :post [(spec/valid? ::name %)]}
    (str "Name: " (:name person)))
user=> (checked-get-name {:id 10 :name "Carl" :surname "De Boor"})
AssertionError Assert failed: (spec/valid? :user/name %)  user/checked-get-name (NO_SOURCE_FILE:33)

12. Třetí demonstrační příklad – validace parametrů předávaných funkci

Ve třetím příkladu je ukázán způsob validace vstupních parametrů funkce i návratové hodnoty funkce. Vše je založeno na příkladech z předchozích kapitol:

(ns spec-demo3.core)
 
(require '[clojure.spec.alpha :as spec])
 
(defn name?
    [s]
    (and (string? s) (re-matches #"[A-Z][a-z]+" s)))
 
(defn surname?
    [s]
    (and (string? s) (re-matches #"[A-Z][A-Za-z ]+" s)))
 
(spec/def ::id pos-int?)
(spec/def ::name name?)
(spec/def ::surname surname?)
(spec/def ::person? (spec/keys :req-un [::id ::name ::surname]))
 
(defn get-name
    [person]
    (:name person))
 
(defn checked-get-name
    [person]
    {:pre  [(spec/valid? ::person? person)]
     :post [(spec/valid? ::name %)]}
    (:name person))
 
(defn -main
    [& args]
    (println "get-name")
    (println (get-name {:id 10 :name "Rich" :surname "Hickey"}))
    (println (get-name {:id 10 :name "rich" :surname "Hickey"}))
    (println (get-name {:id 10 :name "Rich" :surname "hickey"}))
    (println (get-name {:id -10 :name "Rich" :surname "Hickey"}))
    (println (get-name {:id -10 :name "rich" :surname "hickey"}))
    (println)
    (println "checked-get-name")
    (println (checked-get-name {:id 10 :name "Rich" :surname "Hickey"}))
    (println (checked-get-name {:id 10 :name "rich" :surname "Hickey"}))
    (println (checked-get-name {:id 10 :name "Rich" :surname "hickey"}))
    (println (checked-get-name {:id -10 :name "Rich" :surname "Hickey"}))
    (println (checked-get-name {:id -10 :name "rich" :surname "hickey"})))

Po spuštění tohoto demonstračního příkladu by měla úspěšně proběhnout všechna volání funkce get-name, ovšem již druhé volání funkce checked-get-name skončí s chybou vlivem nevalidních vstupních dat:

get-name
Rich
rich
Rich
Rich
rich
 
checked-get-name
Rich
Exception in thread "main" java.lang.AssertionError: Assert failed: (spec/valid? :spec-demo3.core/person? person), compiling:(/tmp/form-init5569844852904633983.clj:1:73)
        at clojure.lang.Compiler.load(Compiler.java:7526)
        at clojure.lang.Compiler.loadFile(Compiler.java:7452)
        at clojure.main$load_script.invokeStatic(main.clj:278)
        at clojure.main$init_opt.invokeStatic(main.clj:280)
        at clojure.main$init_opt.invoke(main.clj:280)
        at clojure.main$initialize.invokeStatic(main.clj:311)
        at clojure.main$null_opt.invokeStatic(main.clj:345)
        at clojure.main$null_opt.invoke(main.clj:342)
        at clojure.main$main.invokeStatic(main.clj:424)
        at clojure.main$main.doInvoke(main.clj:387)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.lang.Var.applyTo(Var.java:702)
        at clojure.main.main(main.java:37)
Caused by: java.lang.AssertionError: Assert failed: (spec/valid? :spec-demo3.core/person? person)
        at spec_demo3.core$checked_get_name.invokeStatic(core.clj:22)
        at spec_demo3.core$checked_get_name.invoke(core.clj:22)
        at spec_demo3.core$_main.invokeStatic(core.clj:39)
        at spec_demo3.core$_main.doInvoke(core.clj:28)
        at clojure.lang.RestFn.invoke(RestFn.java:397)
        at clojure.lang.Var.invoke(Var.java:377)
        at user$eval139.invokeStatic(form-init5569844852904633983.clj:1)
        at user$eval139.invoke(form-init5569844852904633983.clj:1)
        at clojure.lang.Compiler.eval(Compiler.java:7062)
        at clojure.lang.Compiler.eval(Compiler.java:7052)
        at clojure.lang.Compiler.load(Compiler.java:7514)
        ... 12 more

13. Čtvrtý demonstrační příklad – chování funkce ve chvíli, kdy není zvalidována její návratová hodnota

Předposlední příklad vznikl úpravou příkladu předchozího. Je v něm definována funkce broken-checked-get-name, která sice má podle zapsané podmínky vracet hodnotu odpovídající validačnímu kritériu ::name, ovšem ve skutečnosti programátor udělal chybu a funkce vrací řetězec, který jménu neodpovídá:

(ns spec-demo4.core)
 
(require '[clojure.spec.alpha :as spec])
 
(defn name?
    [s]
    (and (string? s) (re-matches #"[A-Z][a-z]+" s)))
 
(defn surname?
    [s]
    (and (string? s) (re-matches #"[A-Z][A-Za-z ]+" s)))
 
(spec/def ::id pos-int?)
(spec/def ::name name?)
(spec/def ::surname surname?)
(spec/def ::person? (spec/keys :req-un [::id ::name ::surname]))
 
(defn get-name
    [person]
    (:name person))
 
(defn broken-checked-get-name
    [person]
    {:pre  [(spec/valid? ::person? person)]
     :post [(spec/valid? ::name %)]}
    (str "name: " (:name person)))
 
(defn -main
    [& args]
    (println "get-name")
    (println (get-name {:id 10 :name "Rich" :surname "Hickey"}))
    (println (get-name {:id 10 :name "rich" :surname "Hickey"}))
    (println (get-name {:id 10 :name "Rich" :surname "hickey"}))
    (println (get-name {:id -10 :name "Rich" :surname "Hickey"}))
    (println (get-name {:id -10 :name "rich" :surname "hickey"}))
    (println)
    (println "broken-checked-get-name")
    (println (broken-checked-get-name {:id 10 :name "Rich" :surname "Hickey"})))

Výsledkem je chyba při prvním návratu z funkce broken-checked-get-name:

get-name
Rich
rich
Rich
Rich
rich
 
broken-checked-get-name
Exception in thread "main" java.lang.AssertionError: Assert failed: (spec/valid? :spec-demo4.core/name %), compiling:(/tmp/form-init6747488402196930090.clj:1:73)
        at clojure.lang.Compiler.load(Compiler.java:7526)
        at clojure.lang.Compiler.loadFile(Compiler.java:7452)
        at clojure.main$load_script.invokeStatic(main.clj:278)
        at clojure.main$init_opt.invokeStatic(main.clj:280)
        at clojure.main$init_opt.invoke(main.clj:280)
        at clojure.main$initialize.invokeStatic(main.clj:311)
        at clojure.main$null_opt.invokeStatic(main.clj:345)
        at clojure.main$null_opt.invoke(main.clj:342)
        at clojure.main$main.invokeStatic(main.clj:424)
        at clojure.main$main.doInvoke(main.clj:387)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.lang.Var.applyTo(Var.java:702)
        at clojure.main.main(main.java:37)
Caused by: java.lang.AssertionError: Assert failed: (spec/valid? :spec-demo4.core/name %)
        at spec_demo4.core$broken_checked_get_name.invokeStatic(core.clj:22)
        at spec_demo4.core$broken_checked_get_name.invoke(core.clj:22)
        at spec_demo4.core$_main.invokeStatic(core.clj:38)
        at spec_demo4.core$_main.doInvoke(core.clj:28)
        at clojure.lang.RestFn.invoke(RestFn.java:397)
        at clojure.lang.Var.invoke(Var.java:377)
        at user$eval139.invokeStatic(form-init6747488402196930090.clj:1)
        at user$eval139.invoke(form-init6747488402196930090.clj:1)
        at clojure.lang.Compiler.eval(Compiler.java:7062)
        at clojure.lang.Compiler.eval(Compiler.java:7052)
        at clojure.lang.Compiler.load(Compiler.java:7514)
        ... 12 more

14. Volitelné položky v mapě

V mnoha případech se setkáme s daty, které obsahují volitelné položky. Když se stále budeme držet našeho příkladu s informacemi o osobách, mohou být těmito volitelnými položkami telefon a e-mailová adresa. Existuje několik způsobů specifikace volitelných položek, ovšem nejjednodušší je použití klauzule opt či opt-un. Za touto klauzulí následuje vektor s klíči, tj. formát zadání je stejný jako u klauzule reqreq-un:

(spec/def ::person? (spec/keys :req-un [::id ::name ::surname] :opt-un [::phone ::e-mail]))

Samozřejmě můžete provést definici validačního kritéria i pro mapy s klíči patřícími do aktuálního jmenného prostoru:

(spec/def ::person? (spec/keys :req [::id ::name ::surname] :opt [::phone ::e-mail]))

15. Pátý demonstrační příklad – validace mapy s volitelnými položkami

V dalším demonstračním příkladu je ukázán způsob validace map, které mohou obsahovat volitelné položky. Povšimněte si, že jsou nadefinovány další dva predikáty pro telefonní číslo a e-mailovou adresu (regulární výraz pro adresu jsem si „vypůjčil“ ze Stack Overflow :). Do validační části vstupuje vektor map, což je pravděpodobně nejtypičtější případ při práci s relačními databázemi. Následuje zdrojový kód tohoto demonstračního příkladu:

(ns spec-demo5.core)
 
(require '[clojure.spec.alpha :as spec])
 
(defn name?
    [s]
    (and (string? s) (re-matches #"[A-Z][a-z]+" s)))
 
(defn surname?
    [s]
    (and (string? s) (re-matches #"[A-Z][A-Za-z ]+" s)))
 
(defn email?
    [s]
    (let [pattern #"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"]
        (and (string? s) (re-matches pattern s))))
 
(spec/def ::id      pos-int?)
(spec/def ::phone   pos-int?)
(spec/def ::name    name?)
(spec/def ::surname surname?)
(spec/def ::e-mail   email?)
(spec/def ::person? (spec/keys :req-un [::id ::name ::surname] :opt-un [::phone ::e-mail]))
 
(defn -main
    "I don't do a whole lot ... yet."
    [& args]
    (let [persons [ {:id 10 :name "Rich" :surname "Hickey"}
                    {:id 10 :name "rich" :surname "Hickey"}
                    {:id 10 :name "Rich" :surname "hickey"}
                    {:id -10 :name "Rich" :surname "Hickey"}
                    {:id -10 :name "rich" :surname "hickey"}
                    {:id 10 :name "Rich" :surname "Hickey" :e-mail "rich@somewhere.org"}
                    {:id 10 :name "Rich" :surname "Hickey" :e-mail "wrong"}
                    {:id 10 :name "Rich" :surname "Hickey" :phone 123456789}
                    {:id 10 :name "Rich" :surname "Hickey" :phone nil}
                    {:id 10 :name "Rich" :surname "Hickey" :phone 987654321 :e-mail "rich@somewhere.org"}
                    {:id -10 :name "rich" :surname "" :phone -5 :e-mail "wrong"}]]
        (println "valid?")
        (println "-------------------------------")
 
        (doseq [person persons]
            (println person)
            (println (if (spec/valid? ::person? person) "yes\n" "no\n")))
 
        (println "\n\n\n")
        (println "explain")
        (println "-------------------------------")
 
        (doseq [person persons]
            (println person)
            (println (spec/explain ::person? person))
 
            (println))))

Výsledky vypsané při validaci jednotlivých záznamů:

valid?
-------------------------------
{:id 10, :name Rich, :surname Hickey}
yes
 
{:id 10, :name rich, :surname Hickey}
no
 
{:id 10, :name Rich, :surname hickey}
no
 
{:id -10, :name Rich, :surname Hickey}
no
 
{:id -10, :name rich, :surname hickey}
no
 
{:id 10, :name Rich, :surname Hickey, :e-mail rich@somewhere.org}
yes
 
{:id 10, :name Rich, :surname Hickey, :e-mail wrong}
no
 
{:id 10, :name Rich, :surname Hickey, :phone 123456789}
yes
 
{:id 10, :name Rich, :surname Hickey, :phone nil}
no
 
{:id 10, :name Rich, :surname Hickey, :phone 987654321, :e-mail rich@somewhere.org}
yes
 
{:id -10, :name rich, :surname , :phone -5, :e-mail wrong}
no

Výsledky vypsané při zjišťování, proč nebyl záznam zvalidován:

explain
-------------------------------
{:id 10, :name Rich, :surname Hickey}
Success!
nil
 
{:id 10, :name rich, :surname Hickey}
In: [:name] val: "rich" fails spec: :spec-demo5.core/name at: [:name] predicate: name?
nil
 
{:id 10, :name Rich, :surname hickey}
In: [:surname] val: "hickey" fails spec: :spec-demo5.core/surname at: [:surname] predicate: surname?
nil
 
{:id -10, :name Rich, :surname Hickey}
In: [:id] val: -10 fails spec: :spec-demo5.core/id at: [:id] predicate: pos-int?
nil
 
{:id -10, :name rich, :surname hickey}
In: [:id] val: -10 fails spec: :spec-demo5.core/id at: [:id] predicate: pos-int?
In: [:name] val: "rich" fails spec: :spec-demo5.core/name at: [:name] predicate: name?
In: [:surname] val: "hickey" fails spec: :spec-demo5.core/surname at: [:surname] predicate: surname?
nil
 
{:id 10, :name Rich, :surname Hickey, :e-mail rich@somewhere.org}
Success!
nil
 
{:id 10, :name Rich, :surname Hickey, :e-mail wrong}
In: [:e-mail] val: "wrong" fails spec: :spec-demo5.core/e-mail at: [:e-mail] predicate: email?
nil
 
{:id 10, :name Rich, :surname Hickey, :phone 123456789}
Success!
nil
 
{:id 10, :name Rich, :surname Hickey, :phone nil}
In: [:phone] val: nil fails spec: :spec-demo5.core/phone at: [:phone] predicate: pos-int?
nil
 
{:id 10, :name Rich, :surname Hickey, :phone 987654321, :e-mail rich@somewhere.org}
Success!
nil
 
{:id -10, :name rich, :surname , :phone -5, :e-mail wrong}
In: [:id] val: -10 fails spec: :spec-demo5.core/id at: [:id] predicate: pos-int?
In: [:name] val: "rich" fails spec: :spec-demo5.core/name at: [:name] predicate: name?
In: [:surname] val: "" fails spec: :spec-demo5.core/surname at: [:surname] predicate: surname?
In: [:phone] val: -5 fails spec: :spec-demo5.core/phone at: [:phone] predicate: pos-int?
In: [:e-mail] val: "wrong" fails spec: :spec-demo5.core/e-mail at: [:e-mail] predicate: email?
nil
 

16. Validace n-tic, kolekcí a sekvencí

V závěrečné části dnešního článku se seznámíme s dvojicí maker, která jsou velmi užitečná při validaci n-tic (tuple), kolekcí i obecných sekvencí. První makro se jmenuje jednoduše tuple a najdeme ho pochopitelně ve jmenném prostoru spec:

spec-demo5.core=> (doc spec/tuple)
-------------------------
clojure.spec.alpha/tuple
([& preds])
Macro
  takes one or more preds and returns a spec for a tuple, a vector
  where each element conforms to the corresponding pred. Each element
  will be referred to in paths using its ordinal.
nil

Toto makro je možné použít k otestování typů a popř. i hodnot prvků n-tice, přičemž si pod jménem n-tice můžeme představit vektor (nikoli seznam!) s předem známým počtem prvků. Příkladem může být dvourozměrný bod reprezentovaný dvojicí čísel uložených ve vektoru.

Následuje příklad použití tohoto makra (podobný příklad naleznete v dokumentaci ke knihovně spec):

user=> (spec/def ::point (spec/tuple double? double? double?))
:user/point

Otestování, které dvojice odpovídají dvourozměrnému bodu:

user=> (spec/conform ::point [1.5 2.5 -0.5])
[1.5 2.5 -0.5]
 
user=> (spec/valid? ::point [1.5 2.5 -0.5])
true
 
user=> (spec/valid? ::point [1/2 2.5 -0.5])
false
 
user=> (spec/valid? ::point [1 2 3])
false

Seznam nikdy nebude zvalidován, podobně jako n-tice s jiným počtem prvků či zcela jiný datový typ:

user=> (spec/conform ::point '(1.2 2.3 3.4))
:clojure.spec.alpha/invalid
 
user=> (spec/conform ::point [1.2 2.3 3.4 5.6])
:clojure.spec.alpha/invalid
 
user=> (spec/conform ::point "BOD")
:clojure.spec.alpha/invalid
 
user=> (spec/conform ::point {:x 1 :y 2})
:clojure.spec.alpha/invalid
 
user=> (spec/conform ::point nil)
:clojure.spec.alpha/invalid

Druhé makro se jmenuje coll-of a jeho použití při validaci je mnohem flexibilnější. 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.):

spec-demo5.core=> (doc spec/coll-of)
-------------------------
clojure.spec.alpha/coll-of
([pred & opts])
Macro
  Returns a spec for a collection of items satisfying pred. Unlike
  'every', coll-of will exhaustively conform every value.
 
  Same options as 'every'. conform will produce a collection
  corresponding to :into if supplied, else will match the input collection,
  avoiding rebuilding when possible.
 
  See also - every, map-of

17. Možnosti nabízené makrem coll-of

Podívejme se nyní na způsob použití makra coll-of, od těch nejjednodušších příkladů až po příklady nepatrně složitější.

Validace, zda kolekce nebo sekvence obsahují kladná čísla (a nic jiného):

user=> (spec/conform (spec/coll-of pos-int?) [1 2 3 4])
[1 2 3 4]
 
user=> (spec/conform (spec/coll-of pos-int?) '(1 2 3 4))
(1 2 3 4)
 
user=> (spec/conform (spec/coll-of pos-int?) '(-1 2 3 4))
:clojure.spec.alpha/invalid

Test (přesněji řečeno validace) bude provedena i pro generované sekvence:

user=> (spec/conform (spec/coll-of pos-int?) (range 10))
:clojure.spec.alpha/invalid
 
user=> (spec/conform (spec/coll-of pos-int?) (map inc (range 10)))
(1 2 3 4 5 6 7 8 9 10)

V případě potřeby je možné specifikovat minimální a maximální počet prvků v sekvenci, případně přímo počet prvků klauzulí :count:

user=> (spec/conform (spec/coll-of nat-int? :count 10) (range 10))
(0 1 2 3 4 5 6 7 8 9)
 
user=> (spec/conform (spec/coll-of nat-int? :count 10) (range 9))
:clojure.spec.alpha/invalid
 
user=> (spec/conform (spec/coll-of nat-int? :count 10) (range 11))
:clojure.spec.alpha/invalid
 
user=> (spec/conform (spec/coll-of nat-int? :min-count 5 :max-count 20) (range 10))
(0 1 2 3 4 5 6 7 8 9)
 
user=> (spec/conform (spec/coll-of nat-int? :min-count 5 :max-count 20) (range 30))
:clojure.spec.alpha/invalid
 
user=> (spec/conform (spec/coll-of nat-int? :min-count 5 :max-count 20) (range 3))
:clojure.spec.alpha/invalid

Další možností je test, jakého typu je vstupní sekvence. Sekvence generovaná pomocí range skutečně není vektorem atd.:

user=> (spec/conform (spec/coll-of nat-int? :min-count 5 :max-count 20 :kind vector?) (range 10))
:clojure.spec.alpha/invalid
 
user=> (spec/conform (spec/coll-of nat-int? :min-count 5 :max-count 20 :kind vector?) (into [] (range 10)))
[0 1 2 3 4 5 6 7 8 9]

Taktéž je možné testovat, jestli se v sekvenci vyskytují některé prvky dvakrát či vícekrát:

user=> (spec/conform (spec/coll-of nat-int? :min-count 5 :max-count 20 :kind vector? :distinct true) [1 2 3 4 5 6])
[1 2 3 4 5 6]
 
user=> (spec/conform (spec/coll-of nat-int? :min-count 5 :max-count 20 :kind vector? :distinct true) [1 2 3 1 5 6])
:clojure.spec.alpha/invalid

Poslední užitečná vlastnost – automatické převedení na jinou sekvenci v případě úspěšné validace:

user=> (spec/conform (spec/coll-of nat-int? :min-count 5 :max-count 20 :kind vector? :distinct true :into '()) [1 2 3 4 5 6])
(1 2 3 4 5 6)

V dalším článku si popíšeme poslední velmi užitečnou vlastnost – možnost deklarovat validační kritéria způsobem, který používá klauzule podobné „žolíkům“ z regulárních výrazů.

MIF18 tip v článku ROSA

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. Následují odkazy na jednotlivé projekty:

Projekt Popis Odkaz
db-store uložení mapy do databáze https://github.com/tisnik/clojure-examples/tree/master/db-store
spec-demo1 test existence klíčů v mapě https://github.com/tisnik/clojure-examples/tree/master/spec-demo1
spec-demo2 kontrola hodnot uložených v mapě https://github.com/tisnik/clojure-examples/tree/master/spec-demo2
spec-demo3 validace parametrů funkce https://github.com/tisnik/clojure-examples/tree/master/spec-demo3
spec-demo4 validace návratových hodnot https://github.com/tisnik/clojure-examples/tree/master/spec-demo4
spec-demo5 validace návratových hodnot https://github.com/tisnik/clojure-examples/tree/master/spec-demo5

Pro spuštění projektů je vyžadován nainstalovaný správce projektů Leiningen.

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/

20. Odkazy na Internetu

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