clojure
clojure.spec
Recherche…
Syntaxe
- :: est un raccourci un mot-clé qualifié par un espace de noms. Par exemple, si nous sommes dans l'espace de noms user: :: foo est un raccourci pour: user / foo
- #: ou # - syntaxe littérale de la carte pour qualifier les clés d'une carte par un espace de noms
Remarques
Clojure spec est une nouvelle bibliothèque de spécifications / contrats pour clojure disponible à partir de la version 1.9.
Les spécifications sont exploitées de diverses manières, notamment en étant incluses dans la documentation, la validation des données, la génération de données pour les tests et bien plus encore.
Utiliser un prédicat comme spécification
Toute fonction de prédicat peut être utilisée en tant que spécification. Voici un exemple simple:
(clojure.spec/valid? odd? 1)
;;=> true
(clojure.spec/valid? odd? 2)
;;=> false
le valid?
function prendra une spec et une valeur et retournera true si la valeur est conforme à la spécification et false sinon.
Un autre prédicat intéressant est défini pour l'appartenance:
(s/valid? #{:red :green :blue} :red)
;;=> true
fdef: écrire une spécification pour une fonction
Disons que nous avons la fonction suivante:
(defn nat-num-count [nums] (count (remove neg? nums)))
Nous pouvons écrire une spécification pour cette fonction en définissant une spécification de fonction du même nom:
(clojure.spec/fdef nat-num-count
:args (s/cat :nums (s/coll-of number?))
:ret integer?
:fn #(<= (:ret %) (-> % :args :nums count)))
:args
prend une spécification de regex qui décrit la séquence des arguments par une étiquette de mot-clé correspondant au nom de l'argument et à une spécification correspondante. La raison pour laquelle la spécification requise par :args
est une spécification de regex est de prendre en charge plusieurs arités pour une fonction. :ret
spécifie une spécification pour la valeur de retour de la fonction.
:fn
est une spécification qui contraint la relation entre les :args
et :ret
. Il est utilisé comme propriété lorsqu'il est exécuté via test.check. Il est appelé avec un seul argument: une carte avec deux clés:: :args
(les arguments conformes à la fonction) et :ret
(la valeur de retour de la fonction).
Enregistrement d'une spec
En plus des prédicats fonctionnant en tant que spécifications, vous pouvez enregistrer une spécification globalement en utilisant clojure.spec/def
. def
exige qu'une spécification enregistrée soit nommée par un mot clé qualifié par un espace de nommage:
(clojure.spec/def ::odd-nums odd?)
;;=> :user/odd-nums
(clojure.spec/valid? ::odd-nums 1)
;;=> true
(clojure.spec/valid? ::odd-nums 2)
;;=> false
Une fois enregistrée, une spécification peut être référencée globalement n'importe où dans un programme Clojure.
La syntaxe ::odd-nums
est un raccourci pour :user/odd-nums
, en supposant que nous sommes dans l'espace de noms de l' user
. ::
qualifiera le symbole qu'il précède avec le nom actuel.
Plutôt que de passer le prédicat, nous pouvons passer le nom de la spécification à valid?
, et cela fonctionnera de la même manière.
clojure.spec / et & clojure.spec / ou
clojure.spec/and
& clojure.spec/or
peuvent être utilisés pour créer des spécifications plus complexes, en utilisant plusieurs spécifications ou prédicats:
(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
fonctionne de manière similaire, avec une différence significative. Lors de la définition d'une or
une spécification, vous devez marquer chaque branche possible avec un mot-clé. Ceci est utilisé pour fournir des branches spécifiques qui échouent dans les messages d'erreur:
(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
Lors de la conformité d'une spécification à l'aide de or
, la spécification applicable sera renvoyée, ce qui a rendu la valeur conforme:
(clojure.spec/conform ::big-or-small 5)
;; => [:small 5]
Fiche technique
Vous pouvez spécifier un enregistrement comme suit:
(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
À un moment donné, une syntaxe de lecteur ou une prise en charge intégrée pour la qualification des clés d'enregistrement par l'espace de nom des enregistrements peut être introduite. Ce support existe déjà pour les cartes.
Spécifications de la carte
Vous pouvez spécifier une carte en spécifiant les clés qui doivent être présentes sur la carte:
(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
est un vecteur de clés devant être présent dans la carte. Vous pouvez spécifier des options supplémentaires telles que :opt
, un vecteur de clés facultatif.
Les exemples jusqu'à présent exigent que les clés du nom soient qualifiées par des espaces de noms. Mais il est courant que les clés de carte ne soient pas qualifiées. Pour ce cas, clojure.spec
fournit: req et: opt équivalents pour les clés non qualifiées :req-un
et :opt-un
. Voici le même exemple, avec des clés non qualifiées:
(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
Notez que les spécifications fournies dans le vecteur :req-un
sont toujours qualifiées. clojure.spec, confirmera automatiquement les versions non qualifiées dans la carte lors de la conformité des valeurs.
La syntaxe littérale de la carte d'espace de noms vous permet de qualifier toutes les clés d'une carte par un seul espace de noms succinct. Par exemple:
(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
Notez la syntaxe spéciale #:
reader. Nous suivons cela avec l'espace de noms que nous souhaitons qualifier par toutes les clés de la carte. Celles-ci seront ensuite vérifiées par rapport aux spécifications correspondant à l'espace de noms fourni.
Collections
Vous pouvez spécifier des collections de différentes manières. coll-of vous permet de spécifier des collections et de fournir des contraintes supplémentaires. Voici un exemple simple:
(clojure.spec/valid? (clojure.spec/coll-of int?) [1 2 3])
;; => true
(clojure.spec/valid? (clojure.spec/coll-of int?) '(1 2 3))
;; => true
Les options de contrainte suivent les spécifications / prédicats principaux de la collection. Vous pouvez contraindre le type de collection avec :kind
comme ceci:
(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
Ce qui précède est faux car la collection transmise n'est pas un vecteur.
(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
Ce qui précède est faux car tous les éléments de l'ensemble ne sont pas ints.
Vous pouvez également limiter la taille de la collection de plusieurs manières:
(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
Vous pouvez également imposer l'unicité des éléments de la collection avec :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
s'assure que tous les éléments d'une séquence sont vérifiés. Pour les grandes collections, cela peut être très inefficace. every
se comporte exactement comme coll-of
, sauf qu'il échantillonne seulement un nombre relativement petit d'éléments de la séquence pour la conformité. Cela fonctionne bien pour les grandes collections. Voici un exemple:
(clojure.spec/valid? (clojure.spec/every int? :distinct true) [1 2 3 4 5])
;; => true
map-of
est similaire à coll-of
, mais pour les cartes. Comme les cartes ont à la fois des clés et des valeurs, vous devez fournir une spécification pour la clé et une spécification pour la valeur:
(clojure.spec/valid? (clojure.spec/map-of keyword? string?) {:red "red" :green "green"})
;; => true
Comme coll-of
, la conformité de toutes les clés / valeurs coll-of
map-of
contrôles. Pour les grandes cartes, cela sera inefficace. A l'instar de coll-of
, une map-of
fournitures à every-kv
pour échantillonner efficacement un nombre relativement petit de valeurs à partir d'une grande carte:
(clojure.spec/valid? (clojure.spec/every-kv keyword? string?) {:red "red" :green "green"})
;; => true
Séquences
spec peut décrire et être utilisé avec des séquences arbitraires. Il prend en charge cela via un certain nombre d'opérations de spécification regex.
(clojure.spec/valid? (clojure.spec/cat :text string? :int int?) ["test" 1])
;;=> true
cat
nécessite des étiquettes pour chaque spécification utilisée pour décrire la séquence. cat décrit une séquence d'éléments et une spécification pour chacun.
alt
est utilisé pour choisir parmi un certain nombre de spécifications possibles pour un élément donné dans une séquence. Par exemple:
(clojure.spec/valid? (clojure.spec/cat :text-or-int (clojure.spec/alt :text string? :int int?)) ["test"])
;;=> true
alt
exige également que chaque spécification soit étiquetée par un mot-clé.
Les séquences de regex peuvent être composées de manières très intéressantes et puissantes pour créer des spécifications de description de séquence arbitrairement complexes. Voici un exemple légèrement plus complexe:
(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
Ici ::complex-seq
validera une séquence d'une ou plusieurs paires d'éléments, la première étant un int et la seconde étant une carte de mot-clé à int.