let forms : How to access destructured symbols in a macro?

强颜欢笑 提交于 2019-12-08 21:21:22

问题


I'm trying to write a macro which expands to a let form with destructuring. My problem is that I would like to have the list of the symbols that are defined in the let form, including those obtained by destruturing.

Use case

I'm trying to factor out this kind of behavior, for validation for example :

(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)
       ))

Proposed solution

It would be really nice to achieve this through some sort of and-let macro which I could call like this:

(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))

What I'm missing

But I'm missing some way of accessing the list of the symbols that are bound in the let form. If I had something like a list-bound-symbols function, I could do it like this :

(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)
     ))) 

Has anyone got a clue how I might do this?


回答1:


Destructuring is handled by the clojure.core/destructure function. It's public, so we can call it ourselves and extract the names of all locals, including those naming intermediate results used in destructuring:

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

Seems to work:

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



回答2:


You can do most of this with a function that deals with the map directly.

(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))))

For example

(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

You can add any extraneous variables, a in your example, to the map beforehand. This would needlessly test for as truthiness. No harm done?



来源:https://stackoverflow.com/questions/23216696/let-forms-how-to-access-destructured-symbols-in-a-macro

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!