Guilherme
Guilherme

Reputation: 618

Rewrite loop as a mapcar

Looking at Practical Common Lisp, we're looking at a simple automated unit test framework. We're trying to write a macro to be used as such:

(check (= (+ 1 2) 3) (= (- 1 4) 9))

This should expand to something using a previously defined function report-result. The suggested implementation is:

(defmacro check (&body forms)
  `(progn
     ,@(loop for f in forms collect `(report-result ,f ',f))))

However, that expansion seems rather procedural to me. I wanted to replace the loop with a mapcar to expand to something like this:

(mapcar #'(lambda (form) (report-result form 'form)) (list form-1 ... form-n))

However, I'm clearly lacking the macro-writing skills to do so. Can someone come up with one such macro?

In case it's relevant, this is the definition of report-result:

(defun report-result (result form)
  (format t "~:[FAIL~;pass~] ... ~a~%" result form))

Upvotes: 1

Views: 276

Answers (1)

amalloy
amalloy

Reputation: 91907

It's indeed fairly simple: you just place the collect expression into the body of your mapcar:

(defmacro check (&body forms)
  `(progn
    ,@(mapcar #'(lambda (form)
                  `(report-result ,form ',form))
              forms)))

You don't really need to know anything about the "macro-y" stuff that's going on, in order to do the replacement you want, which is simply replacing a loop with some other equivalent expression: it will work just as well in a macro context as it would outside.

If you want to expand to a mapcar you can, but there's no real reason to do so, since the list's size is known at compile time. Here's what that would look like:

(defmacro check (&body forms)
  `(let ((results (list ,@(mapcar #'(lambda (form)
                                      `(list ,form ',form))
                                  forms))))
     (mapcar #'(lambda (result)
                 (report-result (car result) (cadr result)))
             results)))

Which expands like so

> (macroexpand-1 '(check (+ 1 2) (* 2 3)))
(let ((results (list (list (+ 1 2) '(+ 1 2))
                     (list (* 2 3) '(* 2 3)))))
  (mapcar #'(lambda (result) (report-result (car result) (cadr result)))
          results))

Which as you can see is rather awkward: the macro already has the forms like (+ 1 2) available to it, but in order to preserve them to runtime for the mapcar lambda to see, you have to emit the input form twice. And you have to produce the whole list to map over, rather than just producing a list that's "finished" to begin with. Additionally, this produces a list as output, and requires having all the inputs and outputs in memory at once: the original macro with progn produced the inputs and outputs one at a time, and discarded them when finished.

Upvotes: 3

Related Questions