Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write a recursive macro call on a &REST parameter in Lisp?

I've been writing some simple test cases for one of my assignments, and have built up a bit of a test suite using macros. I have run-test and run-test-section and so on. I'd like run-test-section to take a number of parameters which are run-test invocations and count up the number of PASSes and FAILs.

run-test returns T on PASS, and NIL on FAIL.

What I'm looking to do right now is write a macro that takes a &REST parameter, and invokes each of the elements of this list, finally returning the number of TRUE values.

This is what I currently have:

(defmacro count-true (&rest forms)
`(cond
    ((null ,forms)
      0)
    ((car ,forms)
      (1+ (count-true (cdr ,forms))))
    (T
      (count-true (cdr ,forms)))))

However this puts my REPL into an infinite loop. Might someone be able to point out how I can more effectively manipulate the arguments. Is this even a good idea? Is there a better approach?

edit:

As is noted in responses, a macro is not needed in this case. Using the inbuilt COUNT will suffice. There is useful information in the responses on recursive macro calls, however.

like image 811
Willi Ballenthin Avatar asked Feb 10 '10 15:02

Willi Ballenthin


2 Answers

At macro expansion time, cdr is not evaluated. So (count-true t t nil) hits an infinite expansion like this:

(count-true t t nil)
=>
(1+ (count-true (cdr (t t t nil))))
=>
(1+ (1+ (count-true (cdr (cdr (t t t nil))))))
=>
(1+ (1+ (1+ (count-true (cdr (cdr (cdr (t t t nil))))))))
=> ...

Well, actually this happens for both recursive branches at once. So it blows up even faster than the example.

A better idea?

  1. Trying writing the same thing as a function first. Figure out where you have to put the lambdas to delay evaluation. Then abstract the function into a macro so you can leave out the lambdas.

    The point of writing a function first, by the way, is that sometimes you'll find that a function is good enough. Writing a macro where a function would do is a mistake.

  2. Generally, when you write macros in Common Lisp, start with a loop rather than recursion. Recursive macros are tricky (and usually wrong :).

Edit:

Here is a more correct (but much longer) example:

(count-true t nil) =>
(cond
  ((null '(t nil)) 0)
  ((car '(t nil)) (1+ (count-true (cdr '(t nil)))))
  (T (count-true (cdr '(t nil)))))
=>
(cond
  ((null '(t nil)) 0)
  ((car '(t nil)) (1+ (1+ (count-true (cdr (cdr '(t nil)))))))
  (T (count-true (cdr (cdr '(t nil))))))
=>
(cond
  ((null '(t nil)) 0)
  ((car '(t nil)) (1+ (1+ (1+ (count-true (cdr (cdr (cdr '(t nil)))))))))
  (T (count-true (cdr (cdr (cdr '(t nil)))))))
=>
(cond
  ((null '(t nil)) 0)
  ((car '(t nil)) (1+ (1+ (1+ (1+ (count-true (cdr (cdr (cdr (cdr '(t nil)))))))))))
  (T (count-true (cdr (cdr (cdr (cdr '(t nil))))))))
like image 117
Nathan Shively-Sanders Avatar answered Sep 19 '22 16:09

Nathan Shively-Sanders


Forget recursive macros. They are a pain and really only for advanced Lisp users.

A simple, non-recursive version:

(defmacro count-true (&rest forms)
  `(+
    ,@(loop for form in forms
            collect `(if ,form 1 0))))

CL-USER 111 > (macroexpand '(count-true (plusp 3) (zerop 2)))
(+ (IF (PLUSP 3) 1 0) (IF (ZEROP 2) 1 0))

Well, here is a recursive macro:

(defmacro count-true (&rest forms)
  (if forms
      `(+ (if ,(first forms) 1 0)
          (count-true ,@(rest forms)))
    0))
like image 24
Rainer Joswig Avatar answered Sep 21 '22 16:09

Rainer Joswig