检查 += 扩展以查找错误
你需要检查扩展:
CL-USER 3 > (defmacro += (x y)
(list 'setf x '(+ x y)))
+=
CL-USER 4 > (macroexpand-1 '(+= a 1))
(SETF A (+ X Y))
T
上面的宏展开显示使用了x和y,就是报错。
我们需要在宏函数中评估它们:
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 风格进行更多调整:
- no 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创建的符号也可以通过它们的名称来识别。
我们也可以让 Lisp 格式化生成的代码,使用函数pprint 并给出右边距。
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)))