clojure
clojure.spec
Szukaj…
Składnia
- :: to skrót słowa kluczowego kwalifikującego się do przestrzeni nazw. Np. Jeśli jesteśmy w przestrzeni nazw użytkownik: :: foo to skrót od: user / foo
- #: lub # - dosłowna składnia mapy dla kwalifikujących się kluczy na mapie według przestrzeni nazw
Uwagi
Specyfikacja Clojure to nowa biblioteka specyfikacji / kontraktów dla Clojure dostępna od wersji 1.9.
Specyfikacje są wykorzystywane na wiele sposobów, w tym uwzględniane w dokumentacji, sprawdzania poprawności danych, generowania danych do testowania i innych.
Używanie predykatu jako specyfikacji
Każda funkcja predykatu może być używana jako specyfikacja. Oto prosty przykład:
(clojure.spec/valid? odd? 1)
;;=> true
(clojure.spec/valid? odd? 2)
;;=> false
valid?
funkcja przyjmie specyfikację i wartość i zwróci true, jeśli wartość jest zgodna ze specyfikacją, a false w przeciwnym razie.
Kolejnym interesującym predykatem jest ustawienie członkostwa:
(s/valid? #{:red :green :blue} :red)
;;=> true
fdef: pisanie specyfikacji dla funkcji
Powiedzmy, że mamy następującą funkcję:
(defn nat-num-count [nums] (count (remove neg? nums)))
Możemy napisać specyfikację dla tej funkcji, definiując specyfikację funkcji o tej samej nazwie:
(clojure.spec/fdef nat-num-count
:args (s/cat :nums (s/coll-of number?))
:ret integer?
:fn #(<= (:ret %) (-> % :args :nums count)))
:args
przyjmuje specyfikację wyrażenia regularnego, która opisuje sekwencję argumentów według etykiety słowa kluczowego odpowiadającej nazwie argumentu i odpowiedniej specyfikacji. Powodem, dla którego specyfikacja wymagana przez :args
jest specyfikacją wyrażeń regularnych, jest obsługa wielu araży dla funkcji. :ret
określa specyfikację zwracanej wartości funkcji.
:fn
to specyfikacja, która ogranicza związek między :args
a :ret
. Jest używany jako właściwość podczas uruchamiania przez test.check. Jest wywoływany z jednym argumentem: mapą z dwoma kluczami:: :args
(zgodne argumenty funkcji) i :ret
(zgodna wartość zwracana przez funkcję).
Rejestracja specyfikacji
Oprócz predykatów funkcjonujących jako specyfikacje, możesz zarejestrować specyfikację globalnie za pomocą clojure.spec/def
. def
wymaga, aby rejestrowana specyfikacja była nazwana słowem kluczowym kwalifikowanym w przestrzeni nazw:
(clojure.spec/def ::odd-nums odd?)
;;=> :user/odd-nums
(clojure.spec/valid? ::odd-nums 1)
;;=> true
(clojure.spec/valid? ::odd-nums 2)
;;=> false
Po zarejestrowaniu specyfikacja może być przywoływana globalnie w dowolnym miejscu w programie Clojure.
Składnia ::odd-nums
jest skrótem dla :user/odd-nums
, zakładając, że jesteśmy w przestrzeni nazw user
. ::
kwalifikuje symbol, który poprzedza z bieżącym plikiem nazw.
Zamiast przekazać predykat, możemy przekazać nazwę specyfikacji, aby była valid?
i będzie działać w ten sam sposób.
clojure.spec / i & clojure.spec / lub
clojure.spec/and
& clojure.spec/or
mogą być używane do tworzenia bardziej złożonych specyfikacji, przy użyciu wielu specyfikacji lub predykatów:
(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
działa podobnie, z jedną znaczącą różnicą. Podczas definiowania specyfikacji or
specyfikacji należy oznaczyć każdą możliwą gałąź słowem kluczowym. Jest to używane w celu zapewnienia określonych gałęzi, które nie działają w komunikatach o błędach:
(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
W przypadku zgodności specyfikacji za pomocą or
, zwrócona zostanie odpowiednia specyfikacja, dzięki której wartość będzie zgodna:
(clojure.spec/conform ::big-or-small 5)
;; => [:small 5]
Nagrywaj specyfikacje
Możesz określić rekord w następujący sposób:
(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
W pewnym momencie w przyszłości może zostać wprowadzona składnia czytnika lub wbudowana obsługa kwalifikujących się kluczy rekordów według przestrzeni nazw rekordów. To wsparcie już istnieje dla map.
Specyfikacja mapy
Możesz określić mapę, określając, które klucze powinny znajdować się na mapie:
(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
jest wektorem kluczy wymaganych na mapie. Możesz określić dodatkowe opcje, takie jak :opt
, wektor kluczy, które są opcjonalne.
Dotychczasowe przykłady wymagają, aby klucze w nazwie były kwalifikowane do przestrzeni nazw. Ale klucze mapy są często niewykwalifikowane. W tym przypadku clojure.spec
zapewnia: req i: opt odpowiedniki dla niekwalifikowanych kluczy :req-un
i :opt-un
. Oto ten sam przykład z niekwalifikowanymi kluczami:
(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
Zauważ, że specyfikacje podane w wektorze :req-un
są nadal kwalifikowane. clojure.spec, automatycznie potwierdzi niekwalifikowane wersje na mapie, gdy są zgodne wartości.
dosłowna składnia mapy przestrzeni nazw pozwala zwięźle zakwalifikować wszystkie klucze mapy za pomocą pojedynczej przestrzeni nazw. Na przykład:
(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
Zwróć uwagę na specjalną składnię #:
reader. Podążamy za tym z przestrzenią nazw, według której chcemy zakwalifikować wszystkie klucze mapy. Zostaną one następnie porównane ze specyfikacjami odpowiadającymi podanej przestrzeni nazw.
Kolekcje
Możesz określić kolekcje na wiele sposobów. coll-of pozwala określić kolekcje i zapewnić dodatkowe ograniczenia. Oto prosty przykład:
(clojure.spec/valid? (clojure.spec/coll-of int?) [1 2 3])
;; => true
(clojure.spec/valid? (clojure.spec/coll-of int?) '(1 2 3))
;; => true
Opcje ograniczeń są zgodne z główną specyfikacją / predykatem dla kolekcji. Możesz ograniczyć typ kolekcji za pomocą :kind
takiego:
(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
Powyższe jest fałszywe, ponieważ przekazana kolekcja nie jest wektorem.
(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
Powyższe jest fałszywe, ponieważ nie wszystkie elementy w zestawie są ints.
Możesz także ograniczyć rozmiar kolekcji na kilka sposobów:
(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
Możesz także wymusić unikalność elementów w kolekcji za pomocą :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
zapewnia sprawdzenie wszystkich elementów w sekwencji. W przypadku dużych kolekcji może to być bardzo nieefektywne. every
zachowuje się jak coll-of
, z wyjątkiem tego, że próbkuje tylko stosunkowo niewielką liczbę elementów sekwencji w celu uzyskania zgodności. Działa to dobrze w przypadku dużych kolekcji. Oto przykład:
(clojure.spec/valid? (clojure.spec/every int? :distinct true) [1 2 3 4 5])
;; => true
map-of
jest podobny do coll-of
, ale dla map. Ponieważ mapy zawierają zarówno klucze, jak i wartości, musisz podać zarówno specyfikację klucza, jak i specyfikację wartości:
(clojure.spec/valid? (clojure.spec/map-of keyword? string?) {:red "red" :green "green"})
;; => true
Jak coll-of
, map-of
kontrole zgodności wszystkich kluczowych Mapa / wartości. W przypadku dużych map będzie to nieefektywne. Podobnie jak coll-of
, map-of
zapasów every-kv
do skutecznego próbkowania stosunkowo niewielkiej liczby wartości z dużej mapy:
(clojure.spec/valid? (clojure.spec/every-kv keyword? string?) {:red "red" :green "green"})
;; => true
Sekwencje
spec może opisywać i stosować z dowolnymi sekwencjami. Obsługuje to poprzez szereg operacji specyfikacji wyrażeń regularnych.
(clojure.spec/valid? (clojure.spec/cat :text string? :int int?) ["test" 1])
;;=> true
cat
wymaga etykiet dla każdej specyfikacji użytej do opisania sekwencji. cat opisuje sekwencję elementów i specyfikację dla każdego z nich.
alt
służy do wybierania spośród wielu możliwych specyfikacji dla danego elementu w sekwencji. Na przykład:
(clojure.spec/valid? (clojure.spec/cat :text-or-int (clojure.spec/alt :text string? :int int?)) ["test"])
;;=> true
alt
wymaga również, aby każda specyfikacja była oznaczona słowem kluczowym.
Sekwencje regeksów można komponować na kilka bardzo interesujących i potężnych sposobów tworzenia dowolnie złożonych specyfikacji opisujących sekwencje. Oto nieco bardziej złożony przykład:
(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
Tutaj ::complex-seq
potwierdzi sekwencję jednej lub więcej par elementów, z których pierwszy to int, a drugi to mapa słów kluczowych na int.