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
с правдивостью Никто не пострадал?