Как создать одно и то же значение для двух разных путей в спецификации?

Я пытаюсь научиться пользоваться overrides с s/gen,

у меня есть ::parent карта, которая содержит ::child карта. И родитель, и ребенок имеют общие ключи. Требуется, чтобы ключи имели одинаковое значение между родителем и потомком, например {:a 1 :b 2 :child {:a 1 :b 2}, Я знаю, что это кажется излишним, но проблемная область требует этого.

Код ниже генерирует примеры, но вышеупомянутое требование не выполняется.

Есть ли способ использовать одно и то же сгенерированное значение в двух местах?

(ns blah
  (:require [clojure.spec.alpha :as s]
            [clojure.spec.gen.alpha :as gen])) 

(s/def ::a (s/int-in 1 5))
(s/def ::b (s/int-in 1 6))

(s/def ::child
  (s/keys :req-un [::a ::b]))

(defn- parent-gen []
  (let [a #(s/gen ::a)
        b #(s/gen ::b)]
    (s/gen ::parent-nogen
           ; overrides map follows
           {::a a ::b b
            ::child #(s/gen ::child
                            ; another overrides map
                            {::a a ::b b})))

(s/def ::parent-nogen
  (s/keys :req-un [::a ::b ::child]))

(s/def ::parent
  (s/with-gen ::parent-nogen parent-gen))

(gen/sample (s/gen ::parent))

1 ответ

Решение

Вы можете сделать это с помощью test.check's fmap:

(s/def ::a (s/int-in 1 5))
(s/def ::b (s/int-in 1 6))
(s/def ::child (s/keys :req-un [::a ::b]))
(s/def ::parent (s/keys :req-un [::a ::b ::child]))
(gen/sample
  (s/gen ::parent
         {::parent ;; override default gen with fmap'd version
          #(gen/fmap
            (fn [{:keys [a b child] :as p}]
              (assoc p :child (assoc child :a a :b b)))
            (s/gen ::parent))}))
=>
({:a 1, :b 2, :child {:a 1, :b 2}}
 {:a 2, :b 2, :child {:a 2, :b 2}}
 {:a 1, :b 1, :child {:a 1, :b 1}}
 {:a 3, :b 2, :child {:a 3, :b 2}}
 {:a 2, :b 4, :child {:a 2, :b 4}}
 {:a 4, :b 4, :child {:a 4, :b 4}}
 {:a 3, :b 3, :child {:a 3, :b 3}}
 {:a 4, :b 4, :child {:a 4, :b 4}}
 {:a 3, :b 4, :child {:a 3, :b 4}}
 {:a 3, :b 4, :child {:a 3, :b 4}})

fmap берет на себя функцию f и генератор genи возвращает новый генератор, который применяется f к каждому значению, сгенерированному из gen, Здесь мы передаем это генератор по умолчанию для ::parentи функция, которая берет эти родительские карты и копирует соответствующие ключи в :child карта.

Если вы хотите, чтобы эта спецификация обеспечивала это равенство (помимо просто генерации), вам нужно добавить s/and к ::parent spec с предикатом, чтобы проверить, что:

(s/def ::parent
  (s/and (s/keys :req-un [::a ::b ::child])
         #(= (select-keys % [:a :b])
             (select-keys (:child %) [:a :b]))))

Изменить: вот еще один способ сделать то же самое с gen/let что позволяет более "естественно" let-подобный синтаксис:

(gen/sample
  (gen/let [{:keys [a b] :as parent} (s/gen ::parent)
            child (s/gen ::child)]
    (assoc parent :child (assoc child :a a :b b))))
Другие вопросы по тегам