clojure
clojure.spec
Sök…
Syntax
- :: är ett kortfattat nyckelord-kvalificerat nyckelord. Exempelvis om vi befinner oss i namnutrymme: :: foo är ett kort för: användare / foo
- #: eller # - karta-bokstavlig syntax för kvalificeringstangenter på en karta med ett namnområde
Anmärkningar
Clojure spec är ett nytt specifikations / kontrakt bibliotek för clojure tillgängligt från version 1.9.
Specifikationer är utnyttjade på flera sätt inklusive inkluderande i dokumentation, datavalidering, generering av data för testning och mer.
Använda ett predikat som en specifik
Varje predikatfunktion kan användas som en specifik. Här är ett enkelt exempel:
(clojure.spec/valid? odd? 1)
;;=> true
(clojure.spec/valid? odd? 2)
;;=> false
det valid?
funktionen kommer att ta en spec och ett värde och returnera true om värdet överensstämmer med spec och falskt annars.
Ett annat intressant predikat är medlemskap:
(s/valid? #{:red :green :blue} :red)
;;=> true
fdef: skriva en spec för en funktion
Låt oss säga att vi har följande funktion:
(defn nat-num-count [nums] (count (remove neg? nums)))
Vi kan skriva en specifikation för den här funktionen genom att definiera en funktionsspecifikation med samma namn:
(clojure.spec/fdef nat-num-count
:args (s/cat :nums (s/coll-of number?))
:ret integer?
:fn #(<= (:ret %) (-> % :args :nums count)))
:args
tar en regex-specifikation som beskriver sekvensen av argument med en nyckelordetikett som motsvarar argumentnamnet och motsvarande spec. Anledningen till att specifikationen krävs av :args
är en regex-specifikation är att stödja flera arities för en funktion. :ret
anger en specifikation för funktionens returvärde.
:fn
är en spec som begränsar förhållandet mellan :args
och :ret
. Den används som en egenskap när den körs genom test.check. Det kallas med ett enda argument: en karta med två nycklar :args
(de överensstämmande argumenten för funktionen) och :ret
(funktionens överensstämmda returvärde).
Registrerar en specifik
Förutom att predikat fungerar som specifikationer kan du registrera en specifik globalt med clojure.spec/def
. def
kräver att en specifik som registreras namnges av ett namnområde-kvalificerat nyckelord:
(clojure.spec/def ::odd-nums odd?)
;;=> :user/odd-nums
(clojure.spec/valid? ::odd-nums 1)
;;=> true
(clojure.spec/valid? ::odd-nums 2)
;;=> false
När det har registrerats kan en specifik refereras globalt var som helst i ett Clojure-program.
Syntaxen ::odd-nums
är en kortfattning för :user/odd-nums
, förutsatt att vi befinner oss i user
. ::
kommer att kvalificera den symbol som den föregår med den nuvarande namnutrymmet.
I stället för att överföra predikatet, kan vi överföra specifikamnet till valid?
, och det fungerar på samma sätt.
clojure.spec / och & clojure.spec / eller
clojure.spec/and
& clojure.spec/or
kan användas för att skapa mer komplexa specifikationer med flera specifikationer eller predikat:
(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
fungerar på liknande sätt, med en betydande skillnad. När du definierar en or
specifik måste du märka varje möjlig gren med ett nyckelord. Detta används för att tillhandahålla specifika grenar som misslyckas i felmeddelanden:
(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
Vid överensstämmelse med en specifik med or
, kommer den tillämpliga specifikationen att returneras vilket gjorde att värdet överensstämmer med:
(clojure.spec/conform ::big-or-small 5)
;; => [:small 5]
Spela in specifikationer
Du kan ange en post på följande sätt:
(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
Vid någon tidpunkt i framtiden kan en läsersyntax eller inbyggt stöd för kvalificering av inspelningsnycklar med postens namnutrymme införas. Detta stöd finns redan för kartor.
Kartuppgifter
Du kan specificera en karta genom att ange vilka nycklar som ska finnas på kartan:
(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
är en vektor av nycklar som krävs för att finnas på kartan. Du kan ange ytterligare alternativ som :opt
, en vektor med nycklar som är valfria.
Exemplen hittills kräver att nycklarna i namnet är namnutrymmeskvalificerade. Men det är vanligt att kartnycklar är okvalificerade. För detta fall tillhandahåller clojure.spec
: req och: opt ekvivalenter för okvalificerade nycklar :req-un
och :opt-un
. Här är samma exempel med okvalificerade nycklar:
(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
Lägg märke till hur specifikationerna i :req-un
vektorn fortfarande är kvalificerade. clojure.spec, kommer automatiskt att bekräfta de okvalificerade versionerna på kartan när värdena överensstämmer.
bokstavlig syntax med namnutrymmet gör att du snabbt kan kvalificera alla nycklar på en karta med ett enda namnutrymme. Till exempel:
(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
Lägg märke till den speciella #:
läsersyntaxen. Vi följer detta med det namnutrymme som vi vill kvalificera alla kartnycklar med. Dessa kommer sedan att kontrolleras mot specifikationerna som motsvarar det angivna namnområdet.
samlingar
Du kan specificera samlingar på flera sätt. coll-of låter dig specificera samlingar och ge några ytterligare begränsningar. Här är ett enkelt exempel:
(clojure.spec/valid? (clojure.spec/coll-of int?) [1 2 3])
;; => true
(clojure.spec/valid? (clojure.spec/coll-of int?) '(1 2 3))
;; => true
Begränsningsalternativ följer huvudspecifikationen / predikatet för samlingen. Du kan begränsa kollektionstypen med :kind
så här:
(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
Ovanstående är falskt eftersom samlingen som skickas in inte är en vektor.
(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
Ovanstående är falskt eftersom inte alla element i uppsättningen är ints.
Du kan också begränsa kollektionens storlek på några sätt:
(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
Du kan också säkerställa att elementen i samlingen är unika med :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äkerställer att alla element i en sekvens kontrolleras. För stora samlingar kan detta vara mycket ineffektivt. every
uppför sig precis som en coll-of
, förutom att det bara provar ett relativt litet antal av sekvensernas element från för överensstämmelse. Detta fungerar bra för stora samlingar. Här är ett exempel:
(clojure.spec/valid? (clojure.spec/every int? :distinct true) [1 2 3 4 5])
;; => true
map-of
liknar coll-of
, men för kartor. Eftersom kartor har både nycklar och värden måste du ange både en specifik för nyckeln och en specifik för värdet:
(clojure.spec/valid? (clojure.spec/map-of keyword? string?) {:red "red" :green "green"})
;; => true
Liksom coll-of
, map-of
kontroller överensstämmelse av alla kart nyckel / värden. För stora kartor kommer detta att vara ineffektivt. Liksom coll-of
map-of
leveranser every-kv
för effektivt sampling av ett relativt litet antal värden från en stor karta:
(clojure.spec/valid? (clojure.spec/every-kv keyword? string?) {:red "red" :green "green"})
;; => true
sekvenser
spec kan beskriva och användas med godtyckliga sekvenser. Det stöder detta via ett antal regex spec-operationer.
(clojure.spec/valid? (clojure.spec/cat :text string? :int int?) ["test" 1])
;;=> true
cat
kräver etiketter för varje spec som används för att beskriva sekvensen. katt beskriver en sekvens av element och en specifik för var och en.
alt
används för att välja mellan ett antal möjliga specifikationer för ett givet element i en sekvens. Till exempel:
(clojure.spec/valid? (clojure.spec/cat :text-or-int (clojure.spec/alt :text string? :int int?)) ["test"])
;;=> true
alt
kräver också att varje spec är märkt med ett nyckelord.
Regex-sekvenser kan komponeras på några mycket intressanta och kraftfulla sätt att skapa godtyckligt komplexa sekvensbeskrivande specifikationer. Här är ett lite mer komplex exempel:
(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
Här ::complex-seq
kommer att validera en sekvens av ett eller flera par av element, den första är en int och den andra är en karta med nyckelord till int.