在Common Lisp中创建用于迭代的宏

时间:2018-10-30 03:28:17

标签: macros lisp common-lisp

我试图通过创建简单的+=宏和iterate宏来练习在Common Lisp中创建宏。我已经设法轻松地创建了+=宏,并且在我的iterate宏中使用了它,但遇到了一些问题。当我尝试使用

例如运行宏时
(iterate i 1 5 1 (print (list 'one i)))

(其中i是控制变量,1是起始值,5是结束值,1是增量值)。我收到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
        )
    )
)

我已经尝试过多种方法来解决,的问题,而该错误使我不确定问题是iterate还是+=。据我所知+=正常工作。

2 个答案:

答案 0 :(得分:10)

检查+=扩展以查找错误

您需要检查扩展:

CL-USER 3 > (defmacro += (x y)
              (list 'setf x '(+ x y)))
+=

CL-USER 4 > (macroexpand-1 '(+= a 1))
(SETF A (+ X Y))
T

上面的宏扩展显示使用了xy,这是错误的。 我们需要在宏函数中对其进行评估:

CL-USER 5 > (defmacro += (x y)
              (list 'setf x (list '+ x y)))
+=

CL-USER 6 > (macroexpand-1 '(+= a 1))
(SETF A (+ A 1))
T

上面看起来更好。注意顺便说一句。该宏已经存在于标准Common Lisp中。它称为incf

还要注意,您不需要它,因为iterate代码中不需要副作用。我们可以直接使用+函数而无需设置任何变量。

样式

您可能希望对Lisp样式进行更多调整:

  • 没有camelCase->默认阅读器仍然不区分大小写
  • 说出变量名->提高可读性
  • 宏/函数中的
  • 文档字符串-提高了可读性
  • GENSYM接受参数字符串->提高生成代码的可读性
  • 没有悬挂的括号,括号之间没有空格->使代码更紧凑
  • 更好的自动缩进->提高可读性
  • 正文用&body而不是&rest标记->使用iterate改进了宏形式的自动缩进
  • do不需要+=宏来更新迭代变量,因为do会更新变量本身->不需要副作用,我们只需要计算下一个值
  • 通常,编写一个良好的宏要比编写一个正常的函数花费更多的时间,因为我们正在使用代码生成在元级别上进行编程,还有更多需要考虑的方面和一些基本的陷阱。所以,花点时间,重新阅读代码,检查扩展,写一些文档,...

应用于您的代码,现在看起来像这样:

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

在Lisp中,第一部分-变量,开始,结束,步骤-通常写在列表中。例如,请参见DOTIMES。例如,这使得可以使step为可选并为其提供默认值:

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

让我们看一下扩展的格式,以提高可读性。我们使用函数macroexpand-1,该函数只进行一次宏扩展-而不是对所生成的代码进行宏扩展。

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

您会看到gensym创建的符号也可以通过其名称来识别。

我们还可以使用函数pprint让Lisp格式化生成的代码,并留出适当的边距。

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

答案 1 :(得分:2)

我知道了。原来我在+ =宏中有问题,在迭代宏中还有其他几个地方。这是最终的工作结果。我在编写+ =宏时忘记了,。其他的宏观清算混乱。

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

)