creating a macro for iterate in Common Lisp

  • A+
Category:Languages

I am trying to practise creating macros in Common Lisp by creating a simple += macro and an iterate macro. I have managed to create the += macro easily enough and I am using it within my iterate macro, which I am having a couple of issues with. When I try to run my macro with for example

(iterate i 1 5 1 (print (list 'one i))) 

(where i is the control variable, 1 is the start value, 5 is the end value, and 1 is the increment value). I receive SETQ: variable X has no value

 (defmacro += (x y)         (list 'setf x '(+ x y)))   (defmacro iterate (control beginExp endExp incExp &rest body)     (let ( (end (gensym)) (inc (gensym)))         `(do ( (,control ,beginExp (+= ,control ,inc)) (,end ,endExp) (,inc ,incExp) )             ( (> ,control ,end) T)             ,@ body         )     ) ) 

I have tried multiple different things to fix it by messing with the , and this error makes me unsure as to whether the problem is with iterate or +=. From what I can tell += works properly.

 


Check the += expansion to find the error

You need to check the expansion:

CL-USER 3 > (defmacro += (x y)               (list 'setf x '(+ x y))) +=  CL-USER 4 > (macroexpand-1 '(+= a 1)) (SETF A (+ X Y)) T 

The macro expansion above shows that x and y are used, which is the error. We need to evaluate them inside the macro function:

CL-USER 5 > (defmacro += (x y)               (list 'setf x (list '+ x y))) +=  CL-USER 6 > (macroexpand-1 '(+= a 1)) (SETF A (+ A 1)) T 

Above looks better. Note btw. that the macro already exists in standard Common Lisp. It is called incf.

Note also that you don't need it, because the side-effect is not needed in your iterate code. We can just use the + function without setting any variable.

Style

You might want to adjust a bit more to Lisp style:

  • no camelCase -> default reader is case insensitive anyway
  • speaking variable names -> improves readability
  • documentation string in the macro/function - improves readability
  • GENSYM takes an argument string -> improves readability of generated code
  • no dangling parentheses and no space between parentheses -> makes code more compact
  • better and automatic indentation -> improves readability
  • the body is marked with &body and not with &rest -> improves automatic indentation of the macro forms using iterate
  • do does not need the += macro to update the iteration variable, since do updates the variable itself -> no side-effects needed, we only need to compute the next value
  • generally writing a good macro takes a bit more time than writing a normal function, because we are programming on the meta-level with code generation and there is more to think about and a few basic pitfalls. So, take your time, reread the code, check the expansions, write some documentation, ...

Applied to your code, it now looks like this:

(defmacro iterate (variable start end step &body body)   "Iterates VARIABLE from START to END by STEP. For each step the BODY gets executed."   (let ((end-variable  (gensym "END"))         (step-variable (gensym "STEP")))     `(do ((,variable ,start (+ ,variable ,step-variable))           (,end-variable ,end)           (,step-variable ,step))          ((> ,variable ,end-variable) t)        ,@body))) 

In Lisp the first part - variable, start, end, step - usually is written in a list. See for example DOTIMES. This makes it for example possible to make step optional and to give it a default value:

(defmacro iterate ((variable start end &optional (step 1)) &body body)   "Iterates VARIABLE from START to END by STEP. For each step the BODY gets executed."   (let ((end-variable  (gensym "END"))         (step-variable (gensym "STEP")))     `(do ((,variable ,start (+ ,variable ,step-variable))           (,end-variable ,end)           (,step-variable ,step))          ((> ,variable ,end-variable) t)        ,@body))) 

Let's see the expansion, formatted for readability. We use the function macroexpand-1, which does the macro expansion only one time - not macro expanding the generated code.

CL-USER 10 > (macroexpand-1 '(iterate (i 1 10 2)                                (print i)                                (print (* i 2)))) (DO ((I 1 (+ I #:STEP2864))      (#:END2863 10)      (#:STEP2864 2))     ((> I #:END2863) T)   (PRINT I)   (PRINT (* I 2))) T 

You can see that the symbols created by gensym are also identifiable by their name.

We can also let Lisp format the generated code, using the function pprint and giving a right margin.

CL-USER 18 > (let ((*print-right-margin* 40))                (pprint                 (macroexpand-1                  '(iterate (i 1 10 2)                     (print i)                     (print (* i 2))))))  (DO ((I 1 (+ I #:STEP2905))      (#:END2904 10)      (#:STEP2905 2))     ((> I #:END2904) T)   (PRINT I)   (PRINT (* I 2))) 

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: