clojure
clojure.spec
Ricerca…
Sintassi
- :: è una abbreviazione di una parola chiave qualificata per lo spazio dei nomi. Ad esempio se siamo nello spazio dei nomi utente: :: foo è una scorciatoia per: user / foo
- #: o # - sintassi letterale per le chiavi qualificanti in una mappa da uno spazio dei nomi
Osservazioni
Clojure spec è una nuova libreria di specifiche / contratti per clojure disponibile dalla versione 1.9.
Le specifiche vengono sfruttate in vari modi, tra cui l'inclusione nella documentazione, la convalida dei dati, la generazione di dati per il test e altro ancora.
Utilizzo di un predicato come specifica
Qualsiasi funzione di predicato può essere utilizzata come specifica. Ecco un semplice esempio:
(clojure.spec/valid? odd? 1)
;;=> true
(clojure.spec/valid? odd? 2)
;;=> false
il valid?
la funzione prenderà una specifica e un valore e restituirà true se il valore è conforme alle specifiche e false altrimenti.
Un altro predicato interessante è l'appartenenza al set:
(s/valid? #{:red :green :blue} :red)
;;=> true
fdef: scrivere una specifica per una funzione
Diciamo che abbiamo la seguente funzione:
(defn nat-num-count [nums] (count (remove neg? nums)))
Possiamo scrivere una specifica per questa funzione definendo una specifica della funzione con lo stesso nome:
(clojure.spec/fdef nat-num-count
:args (s/cat :nums (s/coll-of number?))
:ret integer?
:fn #(<= (:ret %) (-> % :args :nums count)))
:args
prende una specifica regex che descrive la sequenza di argomenti da un'etichetta di parola chiave corrispondente al nome dell'argomento e una specifica corrispondente. Il motivo per cui le specifiche richieste da :args
è una specifica regex è il supporto di più origini per una funzione. :ret
specifica una specifica per il valore di ritorno della funzione.
:fn
è una specifica che vincola la relazione tra :args
e :ret
. Viene utilizzato come proprietà quando viene eseguito attraverso test.check. Viene chiamato con un singolo argomento: una mappa con due chiavi:: :args
(gli argomenti conformi alla funzione) e :ret
(il valore di ritorno conforme della funzione).
Registrazione di una specifica
Oltre ai predicati che funzionano come specifiche, è possibile registrare una specifica globalmente utilizzando clojure.spec/def
. def
richiede che una specifica che viene registrata sia nominata da una parola chiave qualificata nello spazio dei nomi:
(clojure.spec/def ::odd-nums odd?)
;;=> :user/odd-nums
(clojure.spec/valid? ::odd-nums 1)
;;=> true
(clojure.spec/valid? ::odd-nums 2)
;;=> false
Una volta registrato, una specifica può essere referenziata globalmente ovunque in un programma Clojure.
La sintassi ::odd-nums
è una scorciatoia per :user/odd-nums
, assumendo che ci troviamo nello spazio dei nomi user
. ::
qualificherà il simbolo che precede con la namesapce corrente.
Piuttosto che passare nel predicato, possiamo passare il nome della specifica a valid?
e funzionerà allo stesso modo.
clojure.spec / and & clojure.spec / o
clojure.spec/and
& clojure.spec/or
può essere utilizzato per creare specifiche più complesse, utilizzando più specifiche o predicati:
(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
funziona allo stesso modo, con una differenza significativa. Quando si definisce una or
una specifica, è necessario contrassegnare ogni ramo possibile con una parola chiave. Questo è usato per fornire rami specifici che falliscono nei messaggi di errore:
(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
Quando si conforma una specifica usando or
, verrà restituita la specifica applicabile che ha reso il valore conforme:
(clojure.spec/conform ::big-or-small 5)
;; => [:small 5]
Registra le specifiche
È possibile specificare un record come segue:
(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
A un certo punto, in futuro, potrebbe essere introdotta una sintassi del lettore o un supporto integrato per le chiavi di registrazione idonee dallo spazio dei nomi dei record. Questo supporto esiste già per le mappe.
Specifiche della mappa
Puoi specificare una mappa specificando quali chiavi dovrebbero essere presenti nella mappa:
(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
è un vettore di chiavi che devono essere presenti nella mappa. È possibile specificare ulteriori opzioni come :opt
, un vettore di chiavi che sono opzionali.
Gli esempi finora richiedono che le chiavi nel nome siano qualificate per lo spazio dei nomi. Ma è comune che le chiavi della mappa non siano qualificate. Per questo caso, clojure.spec
fornisce: req e: opt equivalents per le chiavi non qualificate :req-un
e :opt-un
. Ecco lo stesso esempio, con chiavi non qualificate:
(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
Si noti come le specifiche fornite nel :req-un
vettore come ancora qualificato. clojure.spec, confermerà automaticamente le versioni non qualificate nella mappa quando si conformano i valori.
la sintassi letterale della mappa dello spazio dei nomi consente di qualificare in modo succinto tutte le chiavi di una mappa da un singolo spazio dei nomi. Per esempio:
(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
Notare il numero speciale #:
sintassi del lettore. Lo seguiamo con lo spazio dei nomi per cui desideriamo qualificare tutte le chiavi della mappa. Questi verranno quindi confrontati con le specifiche corrispondenti allo spazio dei nomi fornito.
collezioni
Puoi specificare le raccolte in diversi modi. coll-of consente di spec collezioni e fornire alcuni vincoli aggiuntivi. Ecco un semplice esempio:
(clojure.spec/valid? (clojure.spec/coll-of int?) [1 2 3])
;; => true
(clojure.spec/valid? (clojure.spec/coll-of int?) '(1 2 3))
;; => true
Le opzioni di vincolo seguono le specifiche / il predicato principali per la raccolta. Puoi vincolare il tipo di raccolta con :kind
come questo:
(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
Quanto sopra è falso perché la collezione passata non è un vettore.
(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
Quanto sopra è falso perché non tutti gli elementi nel set sono interi.
Puoi anche limitare la dimensione della raccolta in pochi modi:
(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
Puoi anche far rispettare l'unicità degli elementi nella raccolta con :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
assicura che tutti gli elementi di una sequenza siano controllati. Per le collezioni di grandi dimensioni, questo può essere molto inefficiente. every
comporta come coll-of
, eccetto che campiona solo un numero relativamente piccolo di elementi delle sequenze per la conformità. Questo funziona bene per le grandi collezioni. Ecco un esempio:
(clojure.spec/valid? (clojure.spec/every int? :distinct true) [1 2 3 4 5])
;; => true
map-of
è simile a coll-of
, ma per le mappe. Poiché le mappe hanno sia chiavi che valori, devi fornire sia una specifica per la chiave che una specifica per il valore:
(clojure.spec/valid? (clojure.spec/map-of keyword? string?) {:red "red" :green "green"})
;; => true
Come coll-of
, map-of
conformità ai controlli di tutte le chiavi / valori della mappa. Per le mappe di grandi dimensioni questo sarà inefficiente. Come coll-of
, map-of
supplies every-kv
per campionare in modo efficiente un numero relativamente piccolo di valori da una grande mappa:
(clojure.spec/valid? (clojure.spec/every-kv keyword? string?) {:red "red" :green "green"})
;; => true
sequenze
le specifiche possono descrivere ed essere usate con sequenze arbitrarie. Supporta questo tramite una serie di operazioni spec regex.
(clojure.spec/valid? (clojure.spec/cat :text string? :int int?) ["test" 1])
;;=> true
cat
richiede etichette per ogni specifica utilizzata per descrivere la sequenza. cat descrive una sequenza di elementi e una specifica per ognuno.
alt
è usato per scegliere tra un numero di possibili specifiche per un dato elemento in una sequenza. Per esempio:
(clojure.spec/valid? (clojure.spec/cat :text-or-int (clojure.spec/alt :text string? :int int?)) ["test"])
;;=> true
alt
richiede anche che ogni specifica sia etichettata da una parola chiave.
Le sequenze di Regex possono essere composte in alcuni modi molto interessanti e potenti per creare specifiche di descrizione della sequenza arbitrariamente complesse. Ecco un esempio un po 'più complesso:
(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
Qui ::complex-seq
convaliderà una sequenza di una o più coppie di elementi, la prima essendo una int e la seconda una mappa di parola chiave in int.