let формы: как получить доступ к деструктурированным символам в макросе?

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

Случай использования

Я пытаюсь выделить такое поведение, например, для проверки:

(let [a (foo bar)
      {x :x,
       y :y,
       {u :u, v: v :as nested-map} :nested} some-map]
  (and x y nested-map u v ; testing truthiness
       (valid-a? a)
       (valid-x? x)
       (valid-y? y)
       (valid-nested? nested-map)
       (valid-u-and-v? u v)
       ))

Предложенное решение

Было бы очень хорошо добиться этого с помощью какого-то and-let макрос, который я мог бы назвать так:

(and-let [a (foo bar)
          {x :x,
           y :y,
           {u :u, v: v :as nested-map} :nested} some-map]
         (valid-a? a)
         (valid-x? x)
         (valid-nested? nested-map)
         (valid-u-and-v? u v))

Чего мне не хватает

Но мне не хватает какого-то способа доступа к списку символов, которые связаны в форме let. Если бы у меня было что-то вроде list-bound-symbols функция, я мог бы сделать это так:

(defmacro and-let
  "Expands to an AND close that previouly checks that the values declared in bindings are truthy, followed by the tests."
  [bindings & tests]
  (let [bound-symbols (list-bound-symbols bindings) ;; what I'm missing
        ]
    `(let ~bindings
       (and 
         ~@bound-symbols
         ~@tests)
     ))) 

Кто-нибудь понял, как я могу это сделать?

2 ответа

Решение

Разрушение обрабатывается clojure.core/destructure функция. Он общедоступный, поэтому мы можем назвать его сами и извлечь имена всех местных жителей, в том числе имена промежуточных результатов, используемых при деструктуризации:

(defmacro and-let [bindings & tests]
  (let [destructured (destructure bindings)]
    `(let ~destructured
       (and ~@(take-nth 2 destructured)
            ~@tests))))

Кажется, работает:

(let [foo nil]
  (and-let [a 1
            [b c] [2 3]]
    (nil? foo)))
;= true

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

(defn validate [vm ; validation map
                dm ; data map
                ]
  (and
    (every? identity (map #(% dm) (flatten (keys vm))))
    (every? identity (map (fn [[k vf]]
                            (if (vector? k)
                              (apply vf (map #(% dm) k))
                              (vf (k dm))))
                          vm))))

Например

(validate {:s string?, :n number? :m number? [:m :n] > } {:s "Hello!", :m 5, :n 3})
; true

(validate {:s string?, :n number? :m number? [:m :n] > } {:s "Hello!", :m 5, :n 6})
; false

(validate {:s string?, :n number? :m number? :u \b [:m :n] > } {:s "Hello!", :m 5, :n 6})
; false

Вы можете добавить любые посторонние переменные, a в вашем примере, на карту заранее. Это будет ненужным тестом для aс правдивостью Никто не пострадал?

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