clojure
clojure.spec
Buscar..
Sintaxis
- :: es una abreviatura de palabra clave calificada para el espacio de nombres. Por ejemplo, si estamos en el espacio de nombres de usuario: :: foo es una abreviatura de: user / foo
- #: o # - sintaxis literal del mapa para calificar claves en un mapa por un espacio de nombres
Observaciones
Clojure spec es una nueva biblioteca de especificación / contratos para clojure disponible a partir de la versión 1.9.
Las especificaciones se aprovechan de varias maneras, incluida la inclusión en la documentación, la validación de datos, la generación de datos para pruebas y más.
Usando un predicado como una especificación
Cualquier función de predicado se puede utilizar como una especificación. Aquí hay un ejemplo simple:
(clojure.spec/valid? odd? 1)
;;=> true
(clojure.spec/valid? odd? 2)
;;=> false
el valid?
la función tomará una especificación y un valor y devolverá verdadero si el valor se ajusta a la especificación y falso de lo contrario.
Otro predicado interesante es la membresía establecida:
(s/valid? #{:red :green :blue} :red)
;;=> true
fdef: escribiendo una especificación para una función
Digamos que tenemos la siguiente función:
(defn nat-num-count [nums] (count (remove neg? nums)))
Podemos escribir una especificación para esta función definiendo una especificación de función con el mismo nombre:
(clojure.spec/fdef nat-num-count
:args (s/cat :nums (s/coll-of number?))
:ret integer?
:fn #(<= (:ret %) (-> % :args :nums count)))
:args
toma una especificación de expresiones regulares que describe la secuencia de argumentos por una etiqueta de palabra clave correspondiente al nombre del argumento y una especificación correspondiente. La razón por la que la especificación requerida por :args
es una especificación de expresiones regulares es para admitir múltiples aridades para una función. :ret
especifica una especificación para el valor de retorno de la función.
:fn
es una especificación que restringe la relación entre :args
y :ret
. Se utiliza como una propiedad cuando se ejecuta a través de test.check. Se llama con un solo argumento: un mapa con dos claves:: :args
(los argumentos conformes a la función) y :ret
(el valor de retorno conformado de la función).
Registro de una especificación
Además de los predicados que funcionan como especificaciones, puede registrar una especificación global usando clojure.spec/def
. def
requiere que una especificación que se está registrando sea nombrada por una palabra clave calificada para el espacio de nombres:
(clojure.spec/def ::odd-nums odd?)
;;=> :user/odd-nums
(clojure.spec/valid? ::odd-nums 1)
;;=> true
(clojure.spec/valid? ::odd-nums 2)
;;=> false
Una vez registrada, una especificación puede ser referenciada globalmente en cualquier lugar en un programa de Clojure.
La sintaxis de ::odd-nums
es una abreviatura de :user/odd-nums
, asumiendo que estamos en el espacio de nombres del user
. ::
calificará el símbolo que precede con el actual nombre.
En lugar de pasar el predicado, ¿podemos pasar el nombre de la especificación a valid?
, y funcionará de la misma manera.
clojure.spec / y & clojure.spec / or
clojure.spec/and
& clojure.spec/or
puede usarse para crear especificaciones más complejas, utilizando múltiples especificaciones o predicados:
(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
funciona de manera similar, con una diferencia significativa. Al definir una or
espec., Debe etiquetar cada rama posible con una palabra clave. Esto se usa para proporcionar ramas específicas que fallan en los mensajes de error:
(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
Al conformar una especificación utilizando or
, se devolverá la especificación aplicable que hizo que el valor sea conforme:
(clojure.spec/conform ::big-or-small 5)
;; => [:small 5]
Especificaciones de registro
Puede especificar un registro de la siguiente manera:
(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
En algún momento en el futuro, se puede introducir una sintaxis de lector o un soporte integrado para calificar las claves de registro por el espacio de nombres de los registros. Este soporte ya existe para los mapas.
Especificaciones del mapa
Puede especificar un mapa especificando qué claves deben estar presentes en el mapa:
(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
es un vector de claves que deben estar presentes en el mapa. Puede especificar opciones adicionales como :opt
, un vector de claves que son opcionales.
Los ejemplos hasta ahora requieren que las claves en el nombre estén calificadas para el espacio de nombres. Pero es común que las claves del mapa no estén calificadas. Para este caso, clojure.spec
proporciona: req y: opt equivalentes para claves no calificadas :req-un
y :opt-un
. Aquí está el mismo ejemplo, con claves no calificadas:
(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
Observe cómo las especificaciones proporcionadas en el vector :req-un
aún calificadas. clojure.spec, confirmará automáticamente las versiones no calificadas en el mapa al conformar los valores.
La sintaxis literal del mapa de espacio de nombres le permite calificar de manera sucinta todas las claves de un mapa por un solo espacio de nombres. Por ejemplo:
(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
Fíjate en la #:
especial #:
sintaxis del lector. Seguimos esto con el espacio de nombres por el que deseamos calificar todas las claves de mapa. Estos se compararán con las especificaciones correspondientes al espacio de nombres proporcionado.
Colecciones
Puede especificar colecciones de varias maneras. coll-of le permite especificar colecciones y proporcionar algunas restricciones adicionales. Aquí hay un ejemplo 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
Las opciones de restricción siguen la especificación / predicado principal de la colección. Puede restringir el tipo de colección con :kind
como este:
(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
Lo anterior es falso porque la colección pasada no es un vector.
(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
Lo anterior es falso porque no todos los elementos en el conjunto son ints.
También puede restringir el tamaño de la colección de varias maneras:
(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
También puede imponer la unicidad de los elementos de la colección 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
asegura que todos los elementos en una secuencia son verificados. Para grandes colecciones, esto puede ser muy ineficiente. every
comporta como una coll-of
, excepto que solo muestrea un número relativamente pequeño de elementos de las secuencias para su conformidad. Esto funciona bien para grandes colecciones. Aquí hay un ejemplo:
(clojure.spec/valid? (clojure.spec/every int? :distinct true) [1 2 3 4 5])
;; => true
map-of
es similar a coll-of
, pero para mapas. Como los mapas tienen tanto claves como valores, debe proporcionar una especificación para la clave y una especificación para el valor:
(clojure.spec/valid? (clojure.spec/map-of keyword? string?) {:red "red" :green "green"})
;; => true
Al igual que la coll-of
, la map-of
controles cumple con todas las claves / valores del mapa. Para mapas grandes esto será ineficiente. Al igual que la coll-of
, map-of
suministra every-kv
para muestrear de manera eficiente un número relativamente pequeño de valores de un mapa grande:
(clojure.spec/valid? (clojure.spec/every-kv keyword? string?) {:red "red" :green "green"})
;; => true
Secuencias
La especificación puede describirse y usarse con secuencias arbitrarias. Es compatible con esto a través de una serie de operaciones de especificación de expresiones regulares.
(clojure.spec/valid? (clojure.spec/cat :text string? :int int?) ["test" 1])
;;=> true
cat
requiere etiquetas para cada especificación utilizada para describir la secuencia. El gato describe una secuencia de elementos y una especificación para cada uno.
alt
se utiliza para elegir entre una serie de posibles especificaciones para un elemento dado en una secuencia. Por ejemplo:
(clojure.spec/valid? (clojure.spec/cat :text-or-int (clojure.spec/alt :text string? :int int?)) ["test"])
;;=> true
alt
también requiere que cada especificación esté etiquetada por una palabra clave.
Las secuencias Regex se pueden componer de formas muy interesantes y potentes para crear especificaciones de descripciones de secuencias arbitrariamente complejas. Aquí hay un ejemplo un poco más complejo:
(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
Aquí ::complex-seq
validará una secuencia de uno o más pares de elementos, el primero es un int y el segundo es un mapa de palabra clave para int.