clojure
clojure.spec
Zoeken…
Syntaxis
- :: is een afkorting een sleutelwoord gekwalificeerd voor naamruimte. Als we bijvoorbeeld in de naamruimte user staan: :: foo is een afkorting voor: user / foo
- #: of # - letterlijke syntaxis van kaarten voor kwalificerende sleutels in een kaart met een naamruimte
Opmerkingen
Clojure spec is een nieuwe specificatie / contractenbibliotheek voor clojure beschikbaar vanaf versie 1.9.
Specs worden op verschillende manieren gebruikt, waaronder opname in documentatie, gegevensvalidatie, gegevens genereren voor testen en meer.
Een predikaat gebruiken als spec
Elke predikaatfunctie kan als spec worden gebruikt. Hier is een eenvoudig voorbeeld:
(clojure.spec/valid? odd? 1)
;;=> true
(clojure.spec/valid? odd? 2)
;;=> false
de valid?
functie neemt een spec en een waarde en retourneert true als de waarde overeenkomt met de spec en anders false.
Een ander interessant predicaat is het vastgestelde lidmaatschap:
(s/valid? #{:red :green :blue} :red)
;;=> true
fdef: een specificatie schrijven voor een functie
Laten we zeggen dat we de volgende functie hebben:
(defn nat-num-count [nums] (count (remove neg? nums)))
We kunnen een specificatie voor deze functie schrijven door een functiespecificatie met dezelfde naam te definiëren:
(clojure.spec/fdef nat-num-count
:args (s/cat :nums (s/coll-of number?))
:ret integer?
:fn #(<= (:ret %) (-> % :args :nums count)))
:args
een regex-specificatie die de reeks argumenten beschrijft met een trefwoordlabel dat overeenkomt met de argumentnaam en een overeenkomstige specificatie. De reden waarom de specificatie vereist door :args
een regex-specificatie is, is het ondersteunen van meerdere arities voor een functie. :ret
geeft een specificatie op voor de retourwaarde van de functie.
:fn
is een specificatie die de relatie tussen de :args
en de :ret
beperkt. Het wordt gebruikt als een eigenschap wanneer het door test.check wordt uitgevoerd. Het wordt opgeroepen met een enkel argument: een kaart met twee sleutels:: :args
(de conforme argumenten voor de functie) en :ret
(de conforme retourwaarde van de functie).
Spec. Registreren
Naast predikaten die als specificaties functioneren, kunt u een specificatie wereldwijd registreren met clojure.spec/def
. def
vereist dat een spec die wordt geregistreerd, wordt benoemd door een naamruimte-gekwalificeerd trefwoord:
(clojure.spec/def ::odd-nums odd?)
;;=> :user/odd-nums
(clojure.spec/valid? ::odd-nums 1)
;;=> true
(clojure.spec/valid? ::odd-nums 2)
;;=> false
Eenmaal geregistreerd, kan overal in een Clojure-programma naar een specificatie worden verwezen.
De syntaxis van ::odd-nums
is een afkorting voor :user/odd-nums
, ervan uitgaande dat we in de user
. ::
kwalificeert het symbool dat eraan voorafgaat met de huidige namesapce.
In plaats van het predicaat door te geven, kunnen we de spec-naam doorgeven om valid?
, en het zal op dezelfde manier werken.
clojure.spec / en & clojure.spec / of
clojure.spec/and
& clojure.spec/or
kunnen worden gebruikt om complexere specificaties te maken met behulp van meerdere specificaties of predicaten:
(clojure.spec/def ::pos-odd (clojure.spec/and odd? pos?))
(clojure.spec/valid? ::pos-odd 1)
;;=> true
(clojure.spec/valid? ::pos-odd -3)
;;=> false
or
werkt op dezelfde manier, met één significant verschil. Bij het definiëren van een or
spec moet u elke mogelijke tak taggen met een trefwoord. Dit wordt gebruikt om specifieke vertakkingen te bieden die in foutmeldingen mislukken:
(clojure.spec/def ::big-or-small (clojure.spec/or :small #(< % 10) :big #(> % 100)))
(clojure.spec/valid? ::big-or-small 1)
;;=> true
(clojure.spec/valid? ::big-or-small 150)
;;=> true
(clojure.spec/valid? ::big-or-small 20)
;;=> false
Wanneer u een specificatie gebruikt met or
, wordt de toepasselijke specificatie geretourneerd die de waarde conform maakt:
(clojure.spec/conform ::big-or-small 5)
;; => [:small 5]
Specificaties opnemen
U kunt een record als volgt specificeren:
(clojure.spec/def ::name string?)
(clojure.spec/def ::age pos-int?)
(clojure.spec/def ::occupation string?)
(defrecord Person [name age occupation])
(clojure.spec/def ::person (clojure.spec/keys :req-un [::name ::age ::occupation]))
(clojure.spec/valid? ::person (->Person "john doe" 25 "programmer"))
;;=> true
(clojure.spec/valid? ::person (->Person "john doe" "25" "programmer"))
;;=> false
Op enig moment in de toekomst kan een lezersyntaxis of ingebouwde ondersteuning voor kwalificerende recordsleutels door de naamruimte van de records worden geïntroduceerd. Deze ondersteuning bestaat al voor kaarten.
Kaartspecificaties
U kunt een kaart specificeren door op te geven welke sleutels aanwezig moeten zijn in de kaart:
(clojure.spec/def ::name string?)
(clojure.spec/def ::age pos-int?)
(clojure.spec/def ::occupation string?)
(clojure.spec/def ::person (clojure.spec/keys :req [::name ::age ::occupation]))
(clojure.spec/valid? ::person {::name "john" ::age 25 ::occupation "programmer"})
;; => true
:req
is een vector met sleutels die aanwezig moet zijn op de kaart. U kunt aanvullende opties opgeven, zoals :opt
, een vector van sleutels die optioneel zijn.
De voorbeelden tot nu toe vereisen dat de sleutels in de naam gekwalificeerd zijn voor de naamruimte. Maar het is gebruikelijk dat kaartsleutels ongekwalificeerd zijn. In dit geval biedt clojure.spec
: req en: opt equivalenten voor niet-gekwalificeerde sleutels :req-un
en :opt-un
. Hier is hetzelfde voorbeeld, met niet-gekwalificeerde sleutels:
(clojure.spec/def ::name string?)
(clojure.spec/def ::age pos-int?)
(clojure.spec/def ::occupation string?)
(clojure.spec/def ::person (clojure.spec/keys :req-un [::name ::age ::occupation]))
(clojure.spec/valid? ::person {:name "john" :age 25 :occupation "programmer"})
;; => true
Merk op hoe de specificaties in de :req-un
vector nog steeds gekwalificeerd zijn. clojure.spec, bevestigt automatisch de niet-gekwalificeerde versies in de kaart wanneer deze aan de waarden voldoen.
namespace map letterlijke syntaxis stelt u in staat om alle toetsen van een kaart door een enkele naamruimte beknopt te kwalificeren. Bijvoorbeeld:
(clojure.spec/def ::name string?)
(clojure.spec/def ::age pos-int?)
(clojure.spec/def ::occupation string?)
(clojure.spec/def ::person (clojure.spec/keys :req [::name ::age ::occupation]))
(clojure.spec/valid? ::person #:user{:name "john" :age 25 :occupation "programmer"})
;;=> true
Let op de speciale #:
reader syntax. We volgen dit met de naamruimte waarmee we alle kaartsleutels willen kwalificeren. Deze worden vervolgens gecontroleerd aan de hand van de specificaties die overeenkomen met de opgegeven naamruimte.
collecties
U kunt collecties op verschillende manieren specificeren. coll-of stelt u in staat om collecties te specificeren en wat extra beperkingen te bieden. Hier is een eenvoudig voorbeeld:
(clojure.spec/valid? (clojure.spec/coll-of int?) [1 2 3])
;; => true
(clojure.spec/valid? (clojure.spec/coll-of int?) '(1 2 3))
;; => true
Beperkingsopties volgen de hoofdspecificatie / het predicaat voor de verzameling. U kunt het verzamelingstype beperken met :kind
als dit:
(clojure.spec/valid? (clojure.spec/coll-of int? :kind vector?) [1 2 3])
;; => true
(clojure.spec/valid? (clojure.spec/coll-of int? :kind vector?) '(1 2 3))
;; => false
Het bovenstaande is onjuist omdat de doorgegeven verzameling geen vector is.
(clojure.spec/valid? (clojure.spec/coll-of int? :kind list?) '(1 2 3))
;; => true
(clojure.spec/valid? (clojure.spec/coll-of int? :kind set?) #{1 2 3})
;; => true
(clojure.spec/valid? (clojure.spec/coll-of int? :kind set?) #{1 "2" 3})
;; => false
Het bovenstaande is onjuist omdat niet alle elementen in de set ints zijn.
Je kunt de grootte van de verzameling ook op een paar manieren beperken:
(clojure.spec/valid? (clojure.spec/coll-of int? :kind vector? :count 3) [1 2 3])
;; => true
(clojure.spec/valid? (clojure.spec/coll-of int? :kind vector? :count 3) [1 2])
;; => false
(clojure.spec/valid? (clojure.spec/coll-of int? :min-count 3 :max-count 5) [1 2 3])
;; => true
(clojure.spec/valid? (clojure.spec/coll-of int? :min-count 3 :max-count 5) [1 2])
;; => false
U kunt ook de uniekheid van de elementen in de verzameling afdwingen met :distinct
:
(clojure.spec/valid? (clojure.spec/coll-of int? :distinct true) [1 2])
;; => true
(clojure.spec/valid? (clojure.spec/coll-of int? :distinct true) [2 2])
;; => false
coll-of
zorgt ervoor dat alle elementen in een reeks worden gecontroleerd. Voor grote collecties kan dit erg inefficiënt zijn. every
gedraagt zich net als coll-of
, behalve dat het slechts een relatief klein aantal elementen van de sequenties test voor conformiteit. Dit werkt goed voor grote collecties. Hier is een voorbeeld:
(clojure.spec/valid? (clojure.spec/every int? :distinct true) [1 2 3 4 5])
;; => true
map-of
is vergelijkbaar met coll-of
, maar dan voor kaarten. Aangezien kaarten zowel sleutels als waarden hebben, moet u zowel een specificatie voor de sleutel als een specificatie voor de waarde opgeven:
(clojure.spec/valid? (clojure.spec/map-of keyword? string?) {:red "red" :green "green"})
;; => true
Net als coll-of
, map-of
checks conformiteit van alle map key / waarden. Voor grote kaarten is dit niet efficiënt. Net als coll-of
, levert map-of
every-kv
voor het efficiënt bemonsteren van een relatief klein aantal waarden van een grote kaart:
(clojure.spec/valid? (clojure.spec/every-kv keyword? string?) {:red "red" :green "green"})
;; => true
Opeenvolgingen
spec kan beschrijven en worden gebruikt met willekeurige reeksen. Het ondersteunt dit via een aantal regex-spec-bewerkingen.
(clojure.spec/valid? (clojure.spec/cat :text string? :int int?) ["test" 1])
;;=> true
cat
vereist labels voor elke specificatie die wordt gebruikt om de volgorde te beschrijven. cat beschrijft een reeks elementen en een specificatie voor elk.
alt
wordt gebruikt om te kiezen uit een aantal mogelijke specificaties voor een bepaald element in een reeks. Bijvoorbeeld:
(clojure.spec/valid? (clojure.spec/cat :text-or-int (clojure.spec/alt :text string? :int int?)) ["test"])
;;=> true
alt
vereist ook dat elke specificatie wordt aangeduid met een trefwoord.
Regex-reeksen kunnen op een aantal zeer interessante en krachtige manieren worden samengesteld om willekeurig complexe reeksbeschrijvende specificaties te maken. Hier is een iets complexer voorbeeld:
(clojure.spec/def ::complex-seq (clojure.spec/+ (clojure.spec/cat :num int? :foo-map (clojure.spec/map-of keyword? int?))))
(clojure.spec/valid? ::complex-seq [0 {:foo 3 :baz 1} 4 {:foo 4}])
;;=> true
Hier ::complex-seq
valideert een reeks van een of meer paar elementen, de eerste is een int en de tweede is een map van trefwoord tot int.