clojure
clojure.spec
Suche…
Syntax
- :: ist eine Abkürzung für ein mit dem Namensraum qualifiziertes Schlüsselwort. ZB wenn wir im Namespace Benutzer sind: :: foo ist eine Abkürzung für: user / foo
- #: oder # - Map-Literal-Syntax für qualifizierende Schlüssel in einer Map anhand eines Namespaces
Bemerkungen
Clojure spec ist eine neue Spezifikations- / Vertragsbibliothek für Clojure, die ab Version 1.9 verfügbar ist.
Spezifikationen werden auf verschiedene Weise genutzt, z. B. zur Dokumentation, zur Datenvalidierung, zum Generieren von Testdaten und mehr.
Verwenden eines Prädikats als Spezifikation
Jede Prädikatfunktion kann als Spezifikation verwendet werden. Hier ist ein einfaches Beispiel:
(clojure.spec/valid? odd? 1)
;;=> true
(clojure.spec/valid? odd? 2)
;;=> false
das valid?
function nimmt eine Spezifikation und einen Wert und gibt true zurück, wenn der Wert der Spezifikation entspricht, andernfalls false
Ein weiteres interessantes Prädikat ist die Set-Mitgliedschaft:
(s/valid? #{:red :green :blue} :red)
;;=> true
fdef: Eine Spezifikation für eine Funktion schreiben
Nehmen wir an, wir haben folgende Funktion:
(defn nat-num-count [nums] (count (remove neg? nums)))
Wir können eine Spezifikation für diese Funktion schreiben, indem Sie eine Funktionsspezifikation mit demselben Namen definieren:
(clojure.spec/fdef nat-num-count
:args (s/cat :nums (s/coll-of number?))
:ret integer?
:fn #(<= (:ret %) (-> % :args :nums count)))
:args
nimmt eine Regex-Spezifikation, die die Folge von Argumenten durch eine Schlüsselwortbezeichnung beschreibt, die dem Argumentnamen und einer entsprechenden Spezifikation entspricht. Der Grund, warum die Spezifikation von :args
eine Regex-Spezifikation ist, ist die Unterstützung mehrerer Arities für eine Funktion. :ret
gibt eine Spezifikation für den Rückgabewert der Funktion an.
:fn
ist eine Spezifikation, die die Beziehung zwischen den :args
und der :ret
. Sie wird beim Durchlaufen von test.check als Eigenschaft verwendet. Es wird mit einem einzigen Argument aufgerufen: einer Map mit zwei Schlüsseln :args
(die konformen Argumente der Funktion) und :ret
(der konforme Rückgabewert der Funktion).
Eine Spezifikation registrieren
Zusätzlich zu Prädikaten, die als Spezifikationen dienen, können Sie eine Spezifikation global mit clojure.spec/def
registrieren. def
setzt def
, dass eine zu registrierende Spezifikation durch ein mit Namensraum qualifiziertes Schlüsselwort benannt wird:
(clojure.spec/def ::odd-nums odd?)
;;=> :user/odd-nums
(clojure.spec/valid? ::odd-nums 1)
;;=> true
(clojure.spec/valid? ::odd-nums 2)
;;=> false
Nach der Registrierung kann eine Spezifikation überall in einem Clojure-Programm weltweit referenziert werden.
Die ::odd-nums
Syntax ist eine Abkürzung für :user/odd-nums
, vorausgesetzt, wir befinden uns im user
. ::
qualifiziert das Symbol, das vor dem aktuellen Namen steht.
Anstatt das Prädikat zu übergeben, können wir den Spezifikationsnamen als valid?
und es wird genauso funktionieren.
clojure.spec / und & clojure.spec / oder
clojure.spec/and
& clojure.spec/or
können zum Erstellen komplexer Spezifikationen verwendet werden, wobei mehrere Spezifikationen oder Prädikate verwendet werden:
(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
funktioniert ähnlich, mit einem signifikanten Unterschied. Bei der Definition einer or
Spezifikation müssen Sie jeden möglichen Zweig mit einem Schlüsselwort kennzeichnen. Dies wird verwendet, um bestimmte Zweige bereitzustellen, in denen Fehlernachrichten fehlschlagen:
(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
Beim Anpassen einer Spezifikation mithilfe von or
wird die entsprechende Spezifikation zurückgegeben, wodurch der Wert übereinstimmt:
(clojure.spec/conform ::big-or-small 5)
;; => [:small 5]
Daten aufzeichnen
Sie können einen Datensatz wie folgt angeben:
(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
Irgendwann in der Zukunft kann eine Lesersyntax oder eine integrierte Unterstützung zum Qualifizieren von Datensatzschlüsseln durch den Namensraum der Datensätze eingeführt werden. Diese Unterstützung besteht bereits für Karten.
Kartenspezifikationen
Sie können eine Karte angeben, indem Sie angeben, welche Schlüssel in der Karte vorhanden sein sollen:
(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
ist ein Vektor von Schlüsseln, die in der Karte vorhanden sein müssen. Sie können zusätzliche Optionen angeben, z. :opt
, einen Vektor von Schlüsseln, die optional sind.
Die bisherigen Beispiele setzen voraus, dass die Schlüssel im Namen für den Namespace qualifiziert sind. Es ist jedoch üblich, dass Kartenschlüssel nicht qualifiziert sind. Für diesen Fall stellt clojure.spec
Verfügung: req und: opt Entsprechungen für nicht qualifizierte Schlüssel clojure.spec
:req-un
und :opt-un
. Hier das gleiche Beispiel mit unqualifizierten Schlüsseln:
(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
Beachten Sie, wie die Angaben im noch nicht qualifizierten :req-un
Vektor angegeben werden. clojure.spec bestätigt automatisch die unqualifizierten Versionen in der Karte, wenn die Werte angepasst werden.
Mit der Namespace-Map-Literal-Syntax können Sie alle Schlüssel einer Map durch einen einzigen Namespace kurz qualifizieren. Zum Beispiel:
(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
Beachten Sie die spezielle #:
Leser-Syntax. Wir folgen diesem mit dem Namensraum, nach dem alle Kartenschlüssel qualifiziert werden sollen. Diese werden dann mit den Angaben des angegebenen Namespaces verglichen.
Sammlungen
Sie können Sammlungen auf verschiedene Arten angeben. Mit coll-of können Sie Sammlungen spezifizieren und einige zusätzliche Einschränkungen angeben. Hier ist ein einfaches Beispiel:
(clojure.spec/valid? (clojure.spec/coll-of int?) [1 2 3])
;; => true
(clojure.spec/valid? (clojure.spec/coll-of int?) '(1 2 3))
;; => true
Einschränkungsoptionen folgen der Hauptspezifikation / dem Prädikat für die Sammlung. Sie können den Auflistungstyp einschränken mit :kind
wie folgt:
(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
Das obige ist falsch, da die übergebene Sammlung kein Vektor ist.
(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
Das obige ist falsch, da nicht alle Elemente in der Gruppe Ints sind.
Sie können die Größe der Sammlung auf verschiedene Weise einschränken:
(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
Sie können die Eindeutigkeit der Elemente in der Auflistung auch erzwingen mit :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
stellt sicher, dass alle Elemente in einer Sequenz geprüft werden. Bei großen Sammlungen kann dies sehr ineffizient sein. every
verhält sich wie ein coll-of
, außer dass nur eine relativ kleine Anzahl der Elemente der Sequenzen zur Konformität abgetastet wird. Dies funktioniert gut für große Sammlungen. Hier ist ein Beispiel:
(clojure.spec/valid? (clojure.spec/every int? :distinct true) [1 2 3 4 5])
;; => true
map-of
ist ähnlich wie coll-of
, aber für Karten. Da Maps sowohl Schlüssel als auch Werte haben, müssen Sie sowohl eine Spezifikation für den Schlüssel als auch eine Spezifikation für den Wert angeben:
(clojure.spec/valid? (clojure.spec/map-of keyword? string?) {:red "red" :green "green"})
;; => true
Wie coll-of
map-of
Prüfungen wird die Übereinstimmung aller Zuordnungsschlüssel / -werte mit coll-of
map-of
überprüft. Bei großen Karten ist dies ineffizient. Wie coll-of
map-of
Lieferungen werden every-kv
Werte für das effiziente Abtasten einer relativ kleinen Anzahl von Werten aus einer großen Karte erstellt:
(clojure.spec/valid? (clojure.spec/every-kv keyword? string?) {:red "red" :green "green"})
;; => true
Sequenzen
spec kann beschreiben und mit beliebigen Sequenzen verwendet werden. Es unterstützt dies durch eine Reihe von regulären Ausdrücken.
(clojure.spec/valid? (clojure.spec/cat :text string? :int int?) ["test" 1])
;;=> true
cat
erfordert Kennzeichnungen für jede Spezifikation, die zur Beschreibung der Sequenz verwendet wird. cat beschreibt eine Abfolge von Elementen und eine Spezifikation für jedes Element.
alt
wird verwendet, um aus einer Reihe möglicher Spezifikationen für ein bestimmtes Element in einer Sequenz auszuwählen. Zum Beispiel:
(clojure.spec/valid? (clojure.spec/cat :text-or-int (clojure.spec/alt :text string? :int int?)) ["test"])
;;=> true
alt
erfordert auch, dass jede Spezifikation mit einem Schlüsselwort gekennzeichnet ist.
Regex-Sequenzen können auf einige sehr interessante und leistungsfähige Arten zusammengestellt werden, um beliebig komplexe sequenzbeschreibende Spezifikationen zu erstellen. Hier ist ein etwas komplexeres Beispiel:
(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 wird ::complex-seq
eine Sequenz von einem oder mehreren ::complex-seq
validieren, wobei das erste ein int und das zweite eine Zuordnung des Schlüsselworts zu int ist.