2abysses
2abysses

Reputation: 101

lisp macro to build a list of an expression and it's evaluation

I'm trying to write a macro in Common Lisp that takes any number of expressions and builds a list containing each expression followed by its evaluation in a single line. For example, if I name my macro as

(defmacro list-builder (&rest exp)
    ...)

and I run

(let ((X 1) (Y 2)) (list-builder (+ X Y) (- Y X) X))

I want it to return:

'((+ X Y) 3 (- Y X) 1 X 1)

The best I've been able to do so far is get a list of the expressions using the code

(defmacro list-builder (&rest exp)
  `',@`(',exp ,exp))

INPUT: (let ((X 1) (Y 2)) (list-builder (+ X Y) (+ Y X) X))
'((+ X Y) (+ Y X) X)

Upvotes: 2

Views: 1123

Answers (3)

Simeon Ikudabo
Simeon Ikudabo

Reputation: 2190

(defmacro list-builder (&rest args)
   `(let ((lst ',args)
          (acc nil))
      (dolist (v lst)
         (push v acc)
         (push (eval v) acc))
      (nreverse acc)))

We could create the list builder macro to take rest parameters as you did (I simply renamed them as args for pseudo code). I'd create a quoted list (lst) of the expressions within the list, and an empty list (acc) to store the expressions and whatever they evaluate to later. Then we can use dolist to iterate through our list and push each expression to the list, followed by whatever it evaluates to by running eval on the expression. Then we can finally use nreverse to get the correct order for the list.

We can then call it:

(let ((x 1)
       (y 2))
   (declare (special x))
   (declare (special y))
   (list-builder (+ x y) (- y x) x))

The result will be:

((+ X Y) 3 (- Y X) 1 X 1)
CL-USER> 

Upvotes: 0

Gwang-Jin Kim
Gwang-Jin Kim

Reputation: 9865

Of course, a lisp macro can do that. Since lisp macros provide full control over evaluation of their arguments.

You have to use macro helper functions only in cases in which you want to use recursion. Since macros have problems to call themselves recursively.

But by looping over the &rest rest argument, you can generate variadic macros (macros with arbitrary number of arguments) and still control the evaluation of each of its arguments. After some trial and error cycles (macro construction is an incremental procedure, since macros are complex structures), I obtained the

"simpler" solution:

(defmacro list-builder (&rest rest)
  `(list ,@(loop for x in `,rest
                 nconcing (list `',x x))))

Test by:

(let ((X 1) 
      (Y 2)) 
  (list-builder (+ X Y) (- Y X) X))

;; ((+ X Y) 3 (- Y X) 1 X 1)

Sometimes, in loop constructs, instead of collect/collecting, use nconc/nconcing in combination with (list ...) to have more control over how the elements are consed together. The

(list `',x x)

ensures, that the second x gets evaluated, while the first

`',x

places the content of x into the expression, while its quoting prevents the evluation of the expression placed for x.

The outer list in combination with the splicing of the loop construct into it, finally captures (prevents) the intrinsic very final evaluation of the macro body.

Upvotes: 1

Kaz
Kaz

Reputation: 58578

Strictly speaking, the macro itself cannot do that; what the macro must do is generate code in which the argument expressions are embedded in such a way that they are evaluated, and also in such a way that they are quoted.

Given (list-builder (+ x y) (+ y x) x) we would like to generate this code: (list '(+ x y) (+ x y) '(+ y x) (+ y x) 'x x).

We can split the macro into an top-level wrapper defined with defmacro and an expander function that does the bulk of the work of producing the list arguments; The macro's body just sticks the list symbol on it and returns it.

Macro helper functions have to be wrapped with a little eval-when dance in Common Lisp to make sure they are available in all conceivable situations that the macro might be processed:

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defun list-builder-expander (exprs)
    (cond
      ((null exprs) nil)
      ((atom exprs) (error "list-builder: dotted syntax unsupported":))
      (t (list* `',(car exprs) (car exprs)
                (list-builder-expander (cdr exprs)))))))

(defmacro list-builder (&rest exprs)
  (cons 'list (list-builder-expander exprs)))

A "slick" implementation, all in one defmacro, inside a single backquote expression, might go like this:

(defmacro list-builder (&rest exprs)
  `(list ,@(mapcan (lambda (expr) (list `',expr expr)) exprs)))

The "dotted syntax unsupported" check we implemented before now becomes an error out of mapcan.

The lambda turns each expression E into the list ((quote E) E). mapcan catenates these lists together to form the arguments for list, which are then spliced into the (list ...) form with ,@.

The form `',expr follows from applying the quote shorthand to `(quote ,expr).

Upvotes: 5

Related Questions