Что означает параметр retag в s/multi-spec?

Можете ли вы объяснить на примерах, как это retag влияние параметров multi-spec создание? я нахожу multi-spec документацию трудно переварить.

2 ответа

Решение

Из строки документации:

retag используется во время генерации, чтобы пометить сгенерированные значения соответствующими тегами. retag может быть либо ключевым словом, с которым будет связан тег dispatch-тега, либо fn сгенерированного значения и dispatch-тега, который должен возвращать соответствующее повторно маркированное значение.

Если retag является ключевым словом (как в примере руководства по спецификациям),multi-spec внутренне создает здесь функцию, которая используется в функции реализации генератора. Например, эти два объявления мульти-спецификации функционально эквивалентны:

(s/def :event/event (s/multi-spec event-type :event/type))
(s/def :event/event (s/multi-spec event-type
                                  (fn [genv tag]
                                    (assoc genv :event/type tag))))

Проходя retag функция не выглядит очень полезной опцией, учитывая пример руководства, но более полезна при использовании multi-spec для не-карт. Например, если вы хотите использовать multi-spec с s/cat например, чтобы специфицировать функцию args:

(defmulti foo first)
(defmethod foo :so/one [_]
  (s/cat :typ #{:so/one} :num number?))
(defmethod foo :so/range [_]
  (s/cat :typ #{:so/range} :lo number? :hi number?))

foo принимает два или три аргумента, в зависимости от первого аргумента. Если мы попытаемся multi-spec это наивно используя s/cat ключевое слово / тег, оно не будет работать:

(s/def :so/foo (s/multi-spec foo :typ))
(sgen/sample (s/gen :so/foo))
;; ClassCastException clojure.lang.LazySeq cannot be cast to clojure.lang.Associative

Здесь можно пройти retag функция полезна:

(s/def :so/foo (s/multi-spec foo (fn [genv _tag] genv)))
(sgen/sample (s/gen :so/foo))
;=>
;((:so/one -0.5)
; (:so/one -0.5)
; (:so/range -1 -2.0)
; (:so/one -1)
; (:so/one 2.0)
; (:so/range 1.875 -4)
; (:so/one -1)
; (:so/one 2.0)
; (:so/range 0 3)
; (:so/one 0.8125))

Я согласен, что документы краткие!

Я хотел создать multi-specd карта с тегом, который может иметь несколько значений. Я обнаружил, что второй аргумент передан retag Функция на самом деле является тегом отправки, а не назначенным тегом (точно так же, как в документах говорится, в ретроспективе). Это было причиной s/gen генерировать карты, помеченные только (не по умолчанию) вариантами отправки мультиметода, а не с полным диапазоном, охватываемым спецификацией тега.

(s/def ::tag #{:a :b :c :d})
(s/def ::example-key keyword?)
(s/def ::different-key keyword?)

(defmulti tagmm :tag)
(defmethod tagmm :a [_]
  (s/keys :req-un [::tag ::example-key]))
(defmethod tagmm :default [_] ; this is `defmulti`'s :default
  (s/keys :req-un [::tag ::different-key]))

(s/def ::example (s/multi-spec tagmm :tag))

(gen/sample (s/gen ::example))
;=> only gives examples with {:tag :a, ...}

Предоставление retag который просто проигнорировал свой второй аргумент и возвратил сгенерированное значение, заставил генератор работать как ожидалось.

(s/def ::example (s/multi-spec tagmm (fn [gen-v tag] gen-v)))
;=> now gives examples from every ::tag

Трудно работать, но оно того стоит!

Другие вопросы по тегам