Как динамически определять спецификации?

#clojure #clojure.spec

Вопрос:

Я хочу сгенерировать набор спецификаций на основе некоторых данных, которые я извлекаю из запроса. Я хотел бы динамически определить некоторые спецификации на основе данных, которые я получаю.

 (def my-key :frame-data/pretty_name) ;;imagine this and the validator aren't hardcoded
(def validator string?)
(s/def my-key validator?) ;; defines my-ns/my-key, instead of `:frame-data/pretty_name.
(s/describe my-key) ;; string? ;;which sorta works, but its looking up `my-ns/my-key` instead of :frame-data/pretty_name
 

Моя цель-иметь спецификацию, которая выглядит так, как я написал:

 (s/def :frame-data/pretty_name string?)
 

Я новичок в clojure, поэтому у меня нет четкого представления о том, как это можно сделать, но я попробовал несколько вещей:

 (s/def (eval my-key) validator) ;;Assert Failed: k must be a namespaced keyword or resolveable symbol

(definemacro def-spec [key validator]
  '(s/def ~key ~validator))
(def-spec my-key validator) ;; my-ns/my-key ;; returns the same as earlier
 

и много вариаций на этот счет, но я не уверен, как можно динамически определить спецификацию, но мне кажется, что так и должно быть.

Ответ №1:

Вы можете выполнить макрорасширение clojure.spec.alpha/def формы, чтобы увидеть, до чего она расширяется:

 (macroexpand `(s/def :frame-data/pretty_name string?))
;; => (clojure.spec.alpha/def-impl (quote :frame-data/pretty_name) (quote clojure.core/string?) string?)
 

Или взгляните на исходный код clojure.spec.alpha/def .

Затем напишите свой собственный макрос, в котором не указан ключ спецификации, чтобы его можно было оценить:

 (defmacro defspec [k spec-form]
  `(s/def-impl ~k (quote ~spec-form) ~spec-form))
 

Пример:

 (defspec my-key validator)

(s/valid? my-key "abc")
;; => true

(s/valid? my-key 123)
;; => false

(s/describe my-key)
;; => validator
 

Если вам не нравится , что (s/describe my-key) возвращается validator , попробуйте заменить (quote ~spec-form) в определении макроса просто ~spec-form , может быть, вам это понравится больше.

Комментарии:

1. Похоже, это больше соответствует моим потребностям, спасибо. Замена на просто ~spec-form , вместо печати «валидатор» печатает #object[clojure.core#string_QMARK...] вместо этого. Гораздо менее читабельно, но, возможно, это мои единственные два варианта.

Ответ №2:

Я бы хотел получить лучший ответ, но, похоже, это работает, только s/describe возвращается validator вместо string?

 (eval `(s/def ~my-key validator)) ;; :frame-data/pretty_name
(s/describe my-key) ;; validator
(s/valid? my-key "some string") ;; true
(s/valid? my-key 123) ;; false
 

В качестве альтернативы работает странно выглядящий макрос, но двойная тильда выглядит странно, и странно использовать eval:

 (defmacro define-spec [spec-key validator]
  `(eval `(s/def ~~spec-key validator)))

(define-spec my-key validator)  ;; :frame-data/pretty_name