clojure
clojure.spec
Поиск…
Синтаксис
- :: это сокращенное ключевое слово. Например, если мы находимся в пространстве имен пользователя: :: foo - это сокращение для: user / foo
- #: или # - синтаксис map-literal для определения ключей на карте пространством имен
замечания
Clojure spec - это новая библиотека спецификаций / контрактов для clojure, доступная с версии 1.9.
Спецификации задействованы несколькими способами, включая включение в документацию, проверку данных, генерацию данных для тестирования и многое другое.
Использование предиката в качестве спецификации
Любая предикатная функция может использоваться как спецификация. Вот простой пример:
(clojure.spec/valid? odd? 1)
;;=> true
(clojure.spec/valid? odd? 2)
;;=> false
valid?
функция примет спецификацию и значение и вернет true, если значение соответствует спецификации и false в противном случае.
Еще одним интересным предикатом является членство:
(s/valid? #{:red :green :blue} :red)
;;=> true
fdef: запись спецификации для функции
Скажем, у нас есть следующая функция:
(defn nat-num-count [nums] (count (remove neg? nums)))
Мы можем написать спецификацию для этой функции, определив функцию spec с тем же именем:
(clojure.spec/fdef nat-num-count
:args (s/cat :nums (s/coll-of number?))
:ret integer?
:fn #(<= (:ret %) (-> % :args :nums count)))
:args
принимает спецификацию регулярного выражения, которая описывает последовательность аргументов меткой ключевого слова, соответствующей имени аргумента и соответствующей спецификации. Причина, по которой спецификация требует :args
- спецификация regex, предназначена для поддержки нескольких явлений для функции. :ret
указывает спецификацию для возвращаемого значения функции.
:fn
- спецификация, которая ограничивает связь между :args
и :ret
. Он используется как свойство при прохождении через test.check. Он вызывается одним аргументом: карта с двумя ключами:: :args
(согласованные аргументы функции) и :ret
(соответствующее возвращаемое значение функции).
Регистрация спецификации
В дополнение к предикатам, функционирующим в качестве спецификаций, вы можете зарегистрировать спецификацию глобально, используя clojure.spec/def
. def
требует, чтобы зарегистрированная спецификация была названа ключевым словом с именем пространства имен:
(clojure.spec/def ::odd-nums odd?)
;;=> :user/odd-nums
(clojure.spec/valid? ::odd-nums 1)
;;=> true
(clojure.spec/valid? ::odd-nums 2)
;;=> false
После регистрации спецификация может быть указана глобально в любой точке программы Clojure.
Синтаксис ::odd-nums
является сокращением для :user/odd-nums
, если мы находимся в пространстве имен user
. ::
будет квалифицировать символ, который предшествует текущему назначению.
Вместо того чтобы проходить в предикате, мы можем передать в спецификационном имени valid?
, и он будет работать одинаково.
clojure.spec / и & clojure.spec / или
clojure.spec/and
& clojure.spec/or
может использоваться для создания более сложных спецификаций с использованием нескольких спецификаций или предикатов:
(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
работает аналогично, с одной существенной разницей. При определении or
spec вы должны пометить каждую возможную ветвь ключевым словом. Это используется для предоставления определенных ветвей, которые не отображаются в сообщениях об ошибках:
(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
При согласовании спецификации, использующей or
, будет возвращена применимая спецификация, которая соответствовала бы значению:
(clojure.spec/conform ::big-or-small 5)
;; => [:small 5]
Спецификации записи
Вы можете указать запись следующим образом:
(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
В какой-то момент в будущем может быть введен синтаксис читателя или встроенная поддержка для выбора ключей записи в пространстве имен записей. Эта поддержка уже существует для карт.
Спецификации карт
Вы можете указать карту, указав, какие ключи должны присутствовать на карте:
(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
- это вектор ключей, который должен присутствовать на карте. Вы можете указать дополнительные опции, такие как :opt
, вектор ключей, которые являются необязательными.
До сих пор в примерах указывалось, что ключи в названии являются подходящими для пространства имен. Но для ключей карты принято быть неквалифицированным. В этом случае clojure.spec
предоставляет: req и: opt эквиваленты для неквалифицированных ключей :req-un
и :opt-un
. Вот тот же пример: с неквалифицированными ключами:
(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
Обратите внимание, как спецификации, предоставленные в :req-un
vector, по-прежнему квалифицированы. clojure.spec, автоматически подтвердит неквалифицированные версии на карте при согласовании значений.
Синтаксис буквенного символа пространства имен позволяет квалифицировать все ключи карты одним пространством имен лаконично. Например:
(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
Обратите внимание на специальный синтаксис #:
reader. Мы следим за этим с помощью пространства имен, которое мы хотим присвоить всем ключам карты. Затем они будут проверяться на соответствие спецификациям, соответствующим предоставленному пространству имен.
Коллекции
Вы можете создавать коллекции несколькими способами. coll-of позволяет вам создавать коллекции и предоставлять дополнительные ограничения. Вот простой пример:
(clojure.spec/valid? (clojure.spec/coll-of int?) [1 2 3])
;; => true
(clojure.spec/valid? (clojure.spec/coll-of int?) '(1 2 3))
;; => true
Параметры ограничения следуют за основным spec / predicate для коллекции. Вы можете ограничить тип коллекции с помощью :kind
вроде этого:
(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
Вышеприведенное значение неверно, потому что принятая коллекция не является вектором.
(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
Вышеприведенное значение неверно, потому что не все элементы в наборе являются int.
Вы также можете ограничить размер коллекции несколькими способами:
(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
Вы также можете обеспечить уникальность элементов в коллекции с помощью :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
гарантирует, что все элементы в последовательности будут проверены. Для больших коллекций это может быть очень неэффективным. every
ведет себя так же, как и coll-of
, за исключением того, что он только отображает относительно небольшое количество элементов последовательностей из-за соответствия. Это хорошо работает для больших коллекций. Вот пример:
(clojure.spec/valid? (clojure.spec/every int? :distinct true) [1 2 3 4 5])
;; => true
map-of
похож на coll-of
, но для карт. Так как карты имеют как ключи, так и значения, вы должны указать как спецификацию для ключа, так и спецификацию для значения:
(clojure.spec/valid? (clojure.spec/map-of keyword? string?) {:red "red" :green "green"})
;; => true
Как coll-of
, map-of
проверок соответствия всех ключевых карты / ценностей. Для больших карт это будет неэффективно. Как и coll-of
map-of
, map-of
снабжает every-kv
для эффективного отбора относительно небольшого числа значений с большой карты:
(clojure.spec/valid? (clojure.spec/every-kv keyword? string?) {:red "red" :green "green"})
;; => true
Последовательности
spec может описывать и использоваться с произвольными последовательностями. Он поддерживает это посредством ряда операций с регулярными выражениями regex.
(clojure.spec/valid? (clojure.spec/cat :text string? :int int?) ["test" 1])
;;=> true
cat
требует ярлыков для каждой спецификации, используемой для описания последовательности. cat описывает последовательность элементов и спецификацию для каждого из них.
alt
используется для выбора из числа возможных спецификаций для данного элемента в последовательности. Например:
(clojure.spec/valid? (clojure.spec/cat :text-or-int (clojure.spec/alt :text string? :int int?)) ["test"])
;;=> true
alt
также требует, чтобы каждая спецификация была помечена ключевым словом.
Последовательности регулярных выражений могут быть составлены несколькими очень интересными и мощными способами для создания произвольно сложных описаний, описывающих последовательность. Вот несколько более сложный пример:
(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
Здесь ::complex-seq
будет проверять последовательность из одной или нескольких пар элементов, первая из которых является int, а вторая является отображением ключевого слова в int.