简体   繁体   中英

creating a macro for iterate in Common Lisp

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)))

I figured it out. Turns out I had a problem in my += macro and a couple of other places in my iterate macro. This is the final working result. I forgot about the , while i was writing the += macro. The other macro declerations where out of order.

 (defmacro += (x y)
        `(setf ,x (+ ,x ,y)))


(defmacro iterate2 (control beginExpr endExpr incrExpr &rest bodyExpr)
    (let ((incr(gensym))(end(gensym)) )
        `(do ((,incr ,incrExpr)(,end ,endExpr)(,control ,beginExpr(+= ,control ,incr)))
            ((> ,control ,end) T)
            ,@ bodyExpr
        )
    )

)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM