Поиск…


Синтаксис

  • :: это сокращенное ключевое слово. Например, если мы находимся в пространстве имен пользователя: :: 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.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow