Reputation: 101
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
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
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 loop
ing 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
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