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

I wrote some simple test cases for one of my assignments and created some test cases using macros. I have run-test and run-test-section etc. I would like the run-test-section take a series of parameters which are run-test invocations and count the number of PASSES and FAIL.

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

Now I want to write a macro that takes the &REST parameter and calls each of the items in this list, and finally returns the number of TRUE values.

This is what I 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 in an infinite loop. Can someone please point out how I can manipulate the arguments more efficiently. Is that even a good idea? Is there a better approach?

edit:

As noted in the answers, a macro is not needed in this case. Using the built-in COUNT will suffice. However, answers to recursive macro calls have useful information.

+6
macros lisp recursion common-lisp
source share
6 answers

At the point in time of macro expansion, cdr not evaluated. So (count-true tt nil) gets into an infinite extension like this:

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

Well, actually it happens for both recursive branches at the same time. Thus, it explodes even faster than an example.

Best idea

  • Trying to write the same thing as the function first. Find out where you should put lambdas to defer evaluation. Then abstract the function into a macro so that you can leave the lambdas.

    The point of writing a function, by the way, is that sometimes you will find that the function is good enough. Writing a macro in which the function will be executed is an error.

  • As a rule, when writing macros in Common Lisp, start with loop , not recursion. Recursive macros are complex (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)))))))) 
+5
source share

Forget about recursive macros. This is a pain and is only valid for advanced Lisp users.

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)) 
+4
source share

The problem is that your macro expands to an infinite form. The system will try to deploy all of this during the macrodistribution phase and, therefore, must run out of memory.

Note that the test for the end of the list of forms is never evaluated, but expressed as code. It must be outside the expression of the return line. And, as another person explained, (cdr forms) should also be evaluated at the time of macro expansion, and not as compilation code.

those. something like this (unverified):

 (defmacro count-true (&rest forms) (if forms `(if (car ',forms) (1+ (count-true ,@(cdr forms))) (count-true ,@(cdr forms))) 0)) 
+3
source share

I believe that you have two false impressions as to which macros are there.

Macros are written for expansion, functions to execute. If you write a recursive macro, it will expand recursively without executing any of the code that it produces. A macro doesn't look like a built-in function at all!

When you write a macro, it can expand to function calls. When you write a macro, you do not dare to "macro", where functions will not be available. It makes no sense to write count-true as a macro.

+2
source share

This is a nice application for easy macro recursion:

 (defmacro count-true (&rest forms) (cond ((null forms) 0) ((endp (rest forms)) `(if ,(first forms) 1 0)) (t `(+ (count-true ,(first forms)) (count-true ,@(rest forms)))))) 

Tests:

 [2]> (count-true) 0 [3]> (count-true nil) 0 [4]> (count-true t) 1 [5]> (count-true nil t) 1 [6]> (count-true t nil) 1 [7]> (count-true tt) 2 [8]> (count-true nil nil) 0 [9]> (macroexpand '(count-true)) 0 ; T [10]> (macroexpand '(count-true x)) (IF X 1 0) ; T [11]> (macroexpand '(count-true xy)) (+ (COUNT-TRUE X) (COUNT-TRUE Y)) ; T [12]> (macroexpand '(count-true xyz)) (+ (COUNT-TRUE X) (COUNT-TRUE YZ)) ; T 

A macro should talk about syntax input and generate a code that performs the calculation; You should not mix between code generation and evaluation.

You are mistaken right after you do this:

 `(cond ((null ,forms ...) ...) 

you push the metasyntactic calculation (how many forms do we have in the syntax?) into the generated code template is evaluated at runtime. In this basic case, you have the correct pieces, but they are not set correctly. In my solution, I have cond only in the macro object itself, and not in the backquote:

 (cond ((null forms) ...) ...) 

Basically:

 (cond (<if the syntax is like this> <generate this>) (<if the syntax is like that> <generate that>) ...) 

If you do not know what to do, write the code that you want to write to the macro. For example:

 ;; I want this semantics: (if (blah) 1 0) ;; count 1 if (blah) is true, else 0 ;; But I want it with this syntax: (count-true (blah)) 

Ok, so for this exact case we will write:

 (defmacro count-true (single-form) `(if ,single-form 1 0)) 

Done! Suppose we want to support (count-true) without any form.

 Wanted Syntax Translation (count-true) 0 (count-true x) (if x 1 0) 

When there is a form, the if extension remains, but when there is no form, we just need a constant zero. Easy, make the argument optional:

 (defmacro count-true (&optional (single-form nil have-single-form)) (if have-single-form `(if ,single-form 1 0) ;; same as before 0)) ;; otherwise zero 

Finally, we turn to the N-ary form:

 Wanted Syntax Translation (count-true) 0 (count-true x) (if x 1 0) (count-true xy) (+ (if x 1 0) (if y 1 0)) ^^^^^^^^^^ ^^^^^^^^^^ 

But! now note that the underlined terms correspond to the output of a single case.

 (count-true xy) (+ (count-true x) (count-true y)) 

What summarizes

 (count-true xyz ...) (+ (count-true x) (count-true yz ...)) 

This corresponds to a direct code generation pattern with car/cdr recursion:

 `(+ (count-true ,CAR) (count-true ,*CDR)) 
+1
source share

As others have said, avoid recursive macros. If you want to do this in a function, you can use apply :

 (defun count-true (&rest forms) (cond ((null forms) 0) (t (+ 1 (apply #'count-true (cdr forms)))))) 
0
source share

All Articles