Macros 在Common Lisp中创建用于迭代的宏
我试图通过创建一个简单的Macros 在Common Lisp中创建用于迭代的宏,macros,lisp,common-lisp,Macros,Lisp,Common Lisp,我试图通过创建一个简单的+=宏和一个迭代宏来练习在Common Lisp中创建宏。我已经很容易地创建了+=宏,并且在我的迭代宏中使用了它,我遇到了一些问题。当我尝试运行宏时,例如 (iterate i 1 5 1 (print (list 'one i))) (其中,i是控制变量,1是起始值,5是结束值,1是增量值)。我收到SETQ:变量X没有值 (defmacro += (x y) (list 'setf x '(+ x y))) (defmacro iterate (
+=
宏和一个迭代
宏来练习在Common Lisp中创建宏。我已经很容易地创建了+=
宏,并且在我的迭代
宏中使用了它,我遇到了一些问题。当我尝试运行宏时,例如
(iterate i 1 5 1 (print (list 'one i)))
(其中,i
是控制变量,1
是起始值,5
是结束值,1
是增量值)。我收到SETQ:变量X没有值
(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
)
)
)
我尝试了多种不同的方法来修复它,方法是弄乱
,
,而这个错误使我无法确定问题是在迭代
还是+=
中。据我所知,+=
工作正常。我找到了答案。原来我的+=宏和迭代宏中的其他几个地方都有问题。这是最终的工作结果。在编写+=宏时,我忘记了,
。其他的宏观偏差是无序的
(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
)
)
)
检查
+=
扩展以查找错误
您需要检查扩展:
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
上面看起来更好。请注意,该宏已存在于标准公共Lisp中。它被称为incf
还要注意的是,您不需要它,因为在迭代代码中不需要副作用。我们可以只使用+
函数而不设置任何变量
风格
您可能需要对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中,第一部分变量start end step通常写在一个列表中。参见示例DOTIMES
。例如,这使得可以将步骤
设置为可选,并为其提供默认值:
(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)))
您的+=
宏是incf
的不卫生克隆<代码>(增量x增量)
将x
位置增量为delta
。但是,与您的+=
不同,incf
只计算x
一次。而且,你有一个bug在里面;setf
赋值右侧的x
名称是硬编码的,因为它位于带引号的列表中。如果我们使用(+=ab)
,生成的代码是(setf a(quote(+xy))
。您可以使用(macroexpand'(+=ab))
@Rainer\u Joswig检查这是编写宏的一个很好的小教程。谢谢!