1

For a program, I'm trying to use a macro such as this one to simplify my code downstream and avoid repeating the same code again and again:

(defmacro destructure (values &body body) `(let* ((other-values (rest values)) (age (getf other-values :age)) (len (getf other-values :len)) (all (getf other-values :all))) (progn ,@(loop for e in body collect `(,@e))))) 

It is supposed to work with a list such as this:

'(name :age 1 :len 2 :all '(1 2 3 4 5)) 

The idea is that I should be able to run this code:

(destructure '(name :age 1 :len 2 :all '(1 2 3 4 5)) (type-of age) (first all)) 

Or, using a variable, like this:

(setf *values* '(name :age 1 :len 2 :all '(1 2 3 4 5))) (destructure *values* (type-of age) (first all)) 

Instead of having to access the different elements each time. Of course this is a simplified example, but the list that I have to work on is reality is much longer.

I'm finding it extremely hard to do this. Basically, the code above does not work (unless I cheat by setfing a global variable "values" to hold my list) because the macro is treating the symbol "values" just as that, without expanding the list that it should point to.

On the other hand, I can't use a regular function because then the instructions I'm passing in the body are immediately executed, but I have to place them in the let.

I'm new enough to the language and I believe that I'm probably missing something, and that there probably is a way to achieve it. Is there?

2
  • Related: stackoverflow.com/q/53252761/850781 Commented Nov 18, 2018 at 17:52
  • A good start would be writing the expansion of your macro by hand. Commented Nov 18, 2018 at 17:54

2 Answers 2

4

First of all, for your problem you can use destructuring-bind:

(destructuring-bind (name &key age len all) '(name :age 1 :len 2 :all (1 2 3 4 5)) (list (type-of age) (type-of len) (type-of name) (first all))) ⇒ (BIT (INTEGER 0 4611686018427387903) SYMBOL 1) 

Now, some notes about your code:

You have put the reference to values inside a backquote, so it is not a reference to the macro argument, but instead a free reference of the expanded form to some outside variable named values. What you probably meant to do:

(defmacro destructure (values &body body) `(let* ((other-values ,(rest values))) (age (getf other-values :age)) (len (getf other-values :len)) (all (getf other-values :all))) …)) 

This has a problem in that it shadows any variable named other-values from outside the macro call form. You should use gensyms to avoid that:

(defmacro destructure (values &body body) (let ((other-values (gensym "other-values"))) `(let* ((,other-values ,(rest values))) (age (getf other-values :age)) (len (getf other-values :len)) (all (getf other-values :all))) …)) 

A note about your code template:

(progn ,@(loop for e in body collect `(,@e)) 

simplifies to

(progn ,@(loop for e in body collect e)) 

simplifies to

(progn ,@body) 

and since the body of a let already is an implicit progn:

,@body 

Finally, your example data:

(name :age 1 :len 2 :all '(1 2 3 4 5)) 

is actually:

(name :age 1 :len 2 :all (quote (1 2 3 4 5))) 

The reader always expands ' to a quote form. When you nest quote forms, you are almost always doing something wrong.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for the complete explanation, that was some food for though (and I spent the past hour mostly researching and playing around to let it all sink in). In the end I settled for a macro that wraps around a destructuring bind just so that the final code is more legible, because the macro has a name that makes sense in the program. Really, thank you.
3

You need to evaluate the values variable in the macro:

? (defmacro destructure (values &body body) `(let* ((other-values (rest ,values)) (age (getf other-values :age)) (len (getf other-values :len)) (all (getf other-values :all))) (progn ,@body))) DESTRUCTURE ? (destructure '(name :age 1 :len 2 :all '(1 2 3 4 5)) (print (type-of age)) (print (first all))) ;Compiler warnings : ; In an anonymous lambda form at position 0: Unused lexical variable LEN BIT QUOTE QUOTE 

The QUOTE in the data is not what you want. Quoted data does not need internal quotes.

? (destructure '(name :age 1 :len 2 :all (1 2 3 4 5)) (print (type-of age)) (print (first all))) ;Compiler warnings : ; In an anonymous lambda form at position 0: Unused lexical variable LEN BIT 1 1 

It also works with a variable:

? (let ((values '(name :age 1 :len 2 :all (1 2 3 4 5)))) (destructure values (print (type-of age)) (print (first all)))) ;Compiler warnings : ; In an anonymous lambda form at position 59: Unused lexical variable LEN BIT 1 1 ? 

You can also declare the three variables as ignorable. This will silence the warnings above.

What to fix?

  • The variable other-values is visible in the body.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.