Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 581
Valentin Waeselynck Avatar asked Apr 22 '14 10:04

Valentin Waeselynck


1 Answers

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
like image 162
Michał Marczyk Avatar answered Oct 04 '22 20:10

Michał Marczyk