我试图通过创建简单的+=
宏和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
还是+=
。据我所知+=
正常工作。
答案 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
上面的宏扩展显示使用了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样式进行更多调整:
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
)
)
)