This is a read-only archive of lispforum.com. The forum was locked to new users and posts and is preserved here as static HTML from a database snapshot taken on 2019-09-07.

loop through forms in a macro

4 posts · 4213 views

The task:

Time is a macro that can be wrapped around any Lisp call to time how long it takes. It will print out various statistics: (time (sleep 2))
Write a macro (time-many &body forms) that is like TIME but takes multiple forms as arguments. For each argument, it first prints the form being timed and then the stats (by calling TIME itself). (time-many (sin 20.0) (sleep 1) (dotimes (i 100000000) (sqrt i)))

My solution:
(defmacro time-many (&body forms)
  (let ((form (gensym)))
    `(dolist (,form ,forms)
       (format "Timing: ~a~%" ,form)
       (time ,form))))
But when I run it:
[2]> (time-many (sin 20.0) (sleep 1) (dotimes (i 100000000) (sqrt i)))
*** - EVAL: (SIN 20.0) is not a function name; try using a symbol instead
What am I doing wrong???

Re: loop through forms in a macro

A couple tips when working on macros: First, print out the expansion, either using macroexpand or by injecting print statements into the body. Second, try splitting the macro into a "syntax wrapper" that calls a normal function to manipulate the form. This helps with visibility into expansion and makes things easier to debug.

The issue you are seeing is a subtle quoting bug. In the dolist, you need to protect the body list from evaluation as a function call. Hence the error "(SIN 20.0) is not a function name". It is literally trying to run the function named "(SIN 20.0)".

You also forgot to set the format destination. I specified t (standard output) below.
(defmacro time-many (&body forms)
  (let ((form (gensym)))
    `(dolist (,form ',forms)
       (format t "Timing: ~a~%" ,form)
       (time ,form))))
One bug remains. The format line prints the form without evaluating it. The "(time ,form)" line needs to actually evaluate the form.

The simple fix is to call eval.

A bigger change is to perform the dolist during the macroexpansion. This approach could do the string formatting at compile time and also eliminate the gensym.

Re: loop through forms in a macro

The macro gets the code unevaluated and you should use that when making the output, but the forms also needs to be in the resulting code, wrapped in time.
Here is one way to do it:
(defmacro time-many (&body forms)
  `(progn ,@(loop :for form :in forms
                  :collect `(princ ,(format nil "Timing: ~a" form))
                  :collect `(time ,form))))
Testing that it does what you want:
(macroexpand '(time-many (sin 20.0) (sleep 1) (dotimes (i 100000000) (sqrt i))))
==>
(progn 
  (princ "Timing: (sin 20.0)") 
  (time (sin 20.0)) 
  (princ "Timing: (sleep 1)")
  (time (sleep 1)) 
  (princ "Timing: (dotimes (i 100000000) (sqrt i))")
  (time (dotimes (i 100000000) (sqrt i))))
I'm the author of two useless languages that uses BF as target machine.
Currently I'm planning a Scheme compiler :p

Re: loop through forms in a macro

Thank you guys.
I think, I understand it now a little better...