Начнем с обычной последовательности
(require '[clojure.spec :as spec]
'[clojure.spec.gen :as gen])
(spec/def ::cat (spec/cat :sym symbol? :str string? :kws (spec/* keyword?)))
что соответствует векторам
(spec/conform ::cat '[af "5"])
=> {:sym af, :str "5"}
(spec/conform ::cat '[af "5" :key])
=> {:sym af, :str "5", :kws [:key]}
но и списки
(spec/conform ::cat '(af "5"))
=> {:sym af, :str "5"}
(spec/conform ::cat '(af "5" :key))
=> {:sym af, :str "5", :kws [:key]}
Если мы хотим ограничить это, мы можем попробовать использовать spec/tuple
; но, к сожалению, он соответствует только векторам фиксированной длины, т.е. требует, чтобы как минимум пустой список был последней частью кортежа:
(spec/def ::tuple (spec/tuple symbol? string? (spec/* keyword?)))
(spec/conform ::tuple '[af "5"])
=> :clojure.spec/invalid
(spec/exercise ::tuple)
=> ([[r "" ()] [r "" []]] [[kE "" (:M)] [kE "" [:M]]] ...)
Мы также можем попробовать добавить дополнительное условие к ::cat
с помощью spec/and
:
(spec/def ::and-cat
(spec/and vector? (spec/cat :sym symbol? :str string? :kws (spec/* keyword?))))
что соответствует отлично
(spec/conform ::and-cat '[af "5"])
=> {:sym af, :str "5"}
(spec/conform ::and-cat '[af "5" :key])
=> {:sym af, :str "5", :kws [:key]}
(spec/conform ::and-cat '(af "5" :key))
=> :clojure.spec/invalid
но, к сожалению, не может генерировать свои собственные данные, поскольку генератор для spec/cat
создает только списки, которые, конечно, не будут соответствовать предикату vector?
:
(spec/exercise ::and-cat)
=> Couldn't satisfy such-that predicate after 100 tries.
Итак, подведем итог: как написать спецификацию, которая способна одновременно принимать и генерировать векторы, такие как [hi "there"]
[my "dear" :friend]
?
Можно также перефразировать вопрос так: «Есть ли альтернатива spec/cat
, которая генерирует векторы вместо списков?» или "Можно ли передать аргумент :kind в spec/cat
?" или «Могу ли я прикрепить к спецификации генератор, который берет выходные данные исходного генератора и преобразует их в вектор?».