2
votes

I'm trying to write a macro function that would function the same way as when in Common Lisp:

(defmacro when2 (&rest args)   
   `(if  (car (quote ,args)) (progn (quote ,args)) nil)
)

But it's far from functioning like the original when:

> (when2 nil 2 3 4)
nil
> (when2 1 2 3 4)
(1 2 3 4)
> (when2 (< 1 2) 2 3 4)
((< 1 2) 2 3 4)

What are the sources of my mistakes?

Thanks!

Edit: after siehe-falz's reply, I tried

(macroexpand '(when2 1 2 3 4))

which gave

(if 1 (progn '(2 3 4)) nil) 

and made me realize that I should not get that quote and those parentheses after progn.

So, I came up with

(defmacro when2 (test &body args)   
   `(if  ,test (progn ,args) nil)
)

but it still gives me this error and I don't get why:

*** - eval: 2 is not a function name; try using a symbol instead
3

3 Answers

7
votes

Macros transform code, and code is data.

Let's see how we transform code by hand and how macros can help simplify that.

Example

Here is a list you want to transform:

(when2 test 1 2 3 4)

It is a list of 6 atomic values, a mix of symbols and integers. Let's call this list input.

Here, we want to produce this code:

(if test (progn 1 2 3 4) nil)

Extracting values

From input:

  • You can access test by taking its second value.
  • You can access (1 2 3 4) by taking the cdr twice (i.e. cddr)

Building code

Knowing this, you can build the resulting list:

(let ((input '(when2 test 1 2 3 4)))
  (list 'if
        (second input)
        (list* 'progn (cddr input))
        nil))

=> (if test (progn 1 2 3 4) nil)

I used LIST* to build a list which starts with PROGN and is followed by another list.

General solution

In the above example, you can see that we don't rely much on the exact content of our input list, but only its general shape. In fact, we can make input a parameter, and we obtain the transformation function we want:

(lambda (input)
  (list 'if
        (second input)
        (list* 'progn (cddr input))
        nil))

Backquote

The backquote syntax is a shorter way of writing the above; the backquoted code is not evaluated and acts as a template where you place expressions that are evaluated (a little bit like string interpolation, except saner). You switch from quoted to evaluated forms:

  • `(a ,b) is equivalent to (list 'a b)
  • `(a b ,@list e f), with list the list (c d) gives (a b c d e f), placing all elements in list at the same level as the ones surrounding it. This is the splice operator.

Instead of the above function, we could write:

(lambda (input)
  `(if ,(second input) (progn ,@(cddr input)) nil))

Destructuring code

Macros further simplify writing those code transformation functions by providing powerful Macro Lambda Lists. Instead of explicitly calling (second input) or (cddr input), you use pattern matching; you declare what shape the input code should have, and the macro binds variables to each subpart of the input, while transforming code:

(defmacro when2 (test &body expressions)
  `(if ,test (progn ,@expressions) nil))

Here, the name of the macro, its argument and the body of code that is included in it are easily declared and used in the expansion code. See DEFMACRO.

The effect is the same as building code from lists of atoms by hand, but both the pattern-matching syntax and the quasi-quotation syntax make writing macros easier.

5
votes

Without knowing too much about macros, I think you should splice the args into the body of the progn using ,@:

(defmacro when2 (test &body args)   
  `(if ,test (progn ,@args) nil))

I would suggest you should read the following reference carefully for a deeper understanding about macros in CL.

http://www.gigamonkeys.com/book/macros-standard-control-constructs.html

3
votes

Use macro-expand to debug your macro. For example

(macroexpand '(when2 nil 2 3 4))

Maybe you can make your task easier by writing your macro like this:

(defmacro when2 (test &rest args) ...)